Compare commits

...

72 Commits

Author SHA1 Message Date
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
208 changed files with 2689 additions and 1372 deletions

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 -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 -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 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/ install -m 644 rgbds.7 gbz80.7 /usr/local/share/man/man7/

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 on: pull_request
jobs: jobs:

View File

@@ -1,4 +1,4 @@
name: "Create release artifacts" name: Create release artifacts
on: on:
push: push:
tags: tags:
@@ -24,30 +24,32 @@ jobs:
run: | # Turn "vX.Y.Z" into "X.Y.Z" run: | # Turn "vX.Y.Z" into "X.Y.Z"
VERSION="${{ github.ref_name }}" VERSION="${{ github.ref_name }}"
echo "version=${VERSION#v}" >> $GITHUB_ENV echo "version=${VERSION#v}" >> $GITHUB_ENV
- uses: actions/checkout@v4 - name: Checkout repo
uses: actions/checkout@v4
- name: Install deps - name: Install deps
run: .github/scripts/get_win_deps.ps1 run: .github/scripts/get_win_deps.ps1
- uses: actions/cache@v4 - name: Check libraries cache
id: cache id: cache
uses: actions/cache@v4
with: with:
path: | path: |
zbuild zbuild
pngbuild pngbuild
key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }} key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }}
- name: Build zlib - name: Build zlib
if: steps.cache.outputs.cache-hit != 'true'
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll` run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
cmake -S zlib -B zbuild -A ${{ matrix.platform }} -Wno-dev -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON cmake -S zlib -B zbuild -A ${{ matrix.platform }} -Wno-dev -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
cmake --build zbuild --config Release -j cmake --build zbuild --config Release -j
if: steps.cache.outputs.cache-hit != 'true'
- name: Install zlib - name: Install zlib
run: | run: |
cmake --install zbuild cmake --install zbuild
- name: Build libpng - name: Build libpng
if: steps.cache.outputs.cache-hit != 'true'
shell: bash shell: bash
run: | 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 -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 cmake --build pngbuild --config Release -j
if: steps.cache.outputs.cache-hit != 'true'
- name: Install libpng - name: Install libpng
run: | run: |
cmake --install pngbuild cmake --install pngbuild
@@ -74,7 +76,8 @@ jobs:
run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z" run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z"
VERSION="${{ github.ref_name }}" VERSION="${{ github.ref_name }}"
echo "version=${VERSION#v}" >> $GITHUB_ENV echo "version=${VERSION#v}" >> $GITHUB_ENV
- uses: actions/checkout@v4 - name: Checkout repo
uses: actions/checkout@v4
- name: Install deps - name: Install deps
shell: bash shell: bash
run: | run: |
@@ -104,7 +107,8 @@ jobs:
run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z" run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z"
VERSION="${{ github.ref_name }}" VERSION="${{ github.ref_name }}"
echo "version=${VERSION#v}" >> $GITHUB_ENV echo "version=${VERSION#v}" >> $GITHUB_ENV
- uses: actions/checkout@v4 - name: Checkout repo
uses: actions/checkout@v4
- name: Install deps - name: Install deps
shell: bash shell: bash
run: | run: |
@@ -133,17 +137,19 @@ jobs:
run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z" run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z"
VERSION="${{ github.ref_name }}" VERSION="${{ github.ref_name }}"
echo "version=${VERSION#v}" >> $GITHUB_ENV echo "version=${VERSION#v}" >> $GITHUB_ENV
- uses: actions/checkout@v4 - name: Checkout repo
uses: actions/checkout@v4
- name: Package sources - name: Package sources
run: | run: |
make dist Q= make dist Q=
ls ls
- uses: actions/download-artifact@v4 - name: Download Linux binaries
uses: actions/download-artifact@v4
- name: Release - name: Release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
body: | 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. 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) 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... draft: true # Don't publish the release quite yet...

View File

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

View File

@@ -1,4 +1,4 @@
name: "Regression testing" name: Regression testing
on: on:
- push - push
- pull_request - pull_request
@@ -17,7 +17,8 @@ jobs:
fail-fast: false fail-fast: false
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v4 - name: Checkout repo
uses: actions/checkout@v4
- name: Install deps - name: Install deps
shell: bash shell: bash
run: | run: |
@@ -32,9 +33,7 @@ jobs:
run: | run: |
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} -DSANITIZERS=ON -DMORE_WARNINGS=ON cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} -DSANITIZERS=ON -DMORE_WARNINGS=ON
cmake --build build -j --verbose cmake --build build -j --verbose
cp build/src/rgb{asm,link,fix,gfx} .
sudo cmake --install build --verbose sudo cmake --install build --verbose
cmake --install build --verbose --component "Test support programs"
- name: Package binaries - name: Package binaries
run: | run: |
mkdir bins mkdir bins
@@ -58,8 +57,8 @@ jobs:
with: with:
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }} path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
key: ${{ matrix.os }}-${{ steps.test-deps-cache-params.outputs.hash }} 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 continue-on-error: true
run: | run: |
test/fetch-test-deps.sh test/fetch-test-deps.sh
@@ -75,7 +74,8 @@ jobs:
macos-static: macos-static:
runs-on: macos-14 runs-on: macos-14
steps: steps:
- uses: actions/checkout@v4 - name: Checkout repo
uses: actions/checkout@v4
- name: Install deps - name: Install deps
shell: bash shell: bash
run: | run: |
@@ -109,8 +109,8 @@ jobs:
with: with:
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }} path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
key: ${{ matrix.os }}-${{ steps.test-deps-cache-params.outputs.hash }} 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 continue-on-error: true
run: | run: |
test/fetch-test-deps.sh test/fetch-test-deps.sh
@@ -138,30 +138,32 @@ jobs:
fail-fast: false fail-fast: false
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v4 - name: Checkout repo
uses: actions/checkout@v4
- name: Install deps - name: Install deps
run: .github/scripts/get_win_deps.ps1 run: .github/scripts/get_win_deps.ps1
- uses: actions/cache@v4 - name: Check libraries cache
id: cache id: cache
uses: actions/cache@v4
with: with:
path: | path: |
zbuild zbuild
pngbuild pngbuild
key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }} key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }}
- name: Build zlib - name: Build zlib
if: steps.cache.outputs.cache-hit != 'true'
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll` run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
cmake -S zlib -B zbuild -A ${{ matrix.platform }} -Wno-dev -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON cmake -S zlib -B zbuild -A ${{ matrix.platform }} -Wno-dev -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
cmake --build zbuild --config Release -j cmake --build zbuild --config Release -j
if: steps.cache.outputs.cache-hit != 'true'
- name: Install zlib - name: Install zlib
run: | run: |
cmake --install zbuild cmake --install zbuild
- name: Build libpng - name: Build libpng
if: steps.cache.outputs.cache-hit != 'true'
shell: bash shell: bash
run: | 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 -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 cmake --build pngbuild --config Release -j
if: steps.cache.outputs.cache-hit != 'true'
- name: Install libpng - name: Install libpng
run: | run: |
cmake --install pngbuild 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 -S . -B build -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release -j --verbose cmake --build build --config Release -j --verbose
cmake --install build --verbose --prefix install_dir cmake --install build --verbose --prefix install_dir
cmake --install build --verbose --component "Test support programs"
- name: Package binaries - name: Package binaries
shell: bash shell: bash
run: | run: |
@@ -196,8 +197,8 @@ jobs:
with: with:
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }} path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
key: ${{ matrix.os }}-${{ matrix.bits }}-${{ steps.test-deps-cache-params.outputs.hash }} 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 shell: bash
continue-on-error: true continue-on-error: true
run: | run: |
@@ -229,7 +230,8 @@ jobs:
env: env:
DIST_DIR: win${{ matrix.bits }} DIST_DIR: win${{ matrix.bits }}
steps: steps:
- uses: actions/checkout@v4 - name: Checkout repo
uses: actions/checkout@v4
- name: Install deps - name: Install deps
shell: bash shell: bash
run: | run: |
@@ -273,7 +275,8 @@ jobs:
fail-fast: false fail-fast: false
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v4 - name: Checkout repo
uses: actions/checkout@v4
- name: Retrieve binaries - name: Retrieve binaries
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
@@ -303,8 +306,8 @@ jobs:
with: with:
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }} path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
key: mingw-${{ matrix.bits }}-${{ steps.test-deps-cache-params.outputs.hash }} 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 shell: bash
continue-on-error: true continue-on-error: true
run: | run: |

View File

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

1
.gitignore vendored
View File

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

View File

@@ -1,11 +1,14 @@
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
# 3.9 required for LTO checks # 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 project(rgbds
LANGUAGES CXX) LANGUAGES CXX)
include(CTest)
# get real path of source and binary directories # get real path of source and binary directories
get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH) get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH)
get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH) get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH)
@@ -41,7 +44,6 @@ else()
# does not recognize this yet. # does not recognize this yet.
add_compile_options(-Wno-gnu-zero-variadic-macro-arguments) add_compile_options(-Wno-gnu-zero-variadic-macro-arguments)
endif() endif()
add_definitions(-D_POSIX_C_SOURCE=200809L)
if(SANITIZERS) if(SANITIZERS)
set(SAN_FLAGS -fsanitize=shift -fsanitize=integer-divide-by-zero set(SAN_FLAGS -fsanitize=shift -fsanitize=integer-divide-by-zero
-fsanitize=unreachable -fsanitize=vla-bound -fsanitize=unreachable -fsanitize=vla-bound
@@ -59,7 +61,7 @@ else()
if(MORE_WARNINGS) if(MORE_WARNINGS)
add_compile_options(-Werror -Wextra add_compile_options(-Werror -Wextra
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond -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 -Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused
-Wshadow # TODO: -Wshadow=compatible-local? -Wshadow # TODO: -Wshadow=compatible-local?
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 -Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1
@@ -88,6 +90,8 @@ endif(GIT)
find_package(PkgConfig) find_package(PkgConfig)
if(MSVC OR NOT PKG_CONFIG_FOUND) if(MSVC OR NOT PKG_CONFIG_FOUND)
# fallback to find_package # 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) find_package(PNG REQUIRED)
else() else()
pkg_check_modules(LIBPNG REQUIRED libpng) pkg_check_modules(LIBPNG REQUIRED libpng)
@@ -99,6 +103,7 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True) set(CMAKE_CXX_STANDARD_REQUIRED True)
add_subdirectory(src) add_subdirectory(src)
set(CMAKE_CTEST_ARGUMENTS "--verbose")
add_subdirectory(test) add_subdirectory(test)
# By default, build in Release mode; Debug mode must be explicitly requested # By default, build in Release mode; Debug mode must be explicitly requested
@@ -121,6 +126,7 @@ set(man1 "man/rgbasm.1"
"man/rgbgfx.1" "man/rgbgfx.1"
"man/rgblink.1") "man/rgblink.1")
set(man5 "man/rgbasm.5" set(man5 "man/rgbasm.5"
"man/rgbasm-old.5"
"man/rgblink.5" "man/rgblink.5"
"man/rgbds.5") "man/rgbds.5")
set(man7 "man/gbz80.7" 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 ```sh
test_downstream <owner> <repo> <makefile target> <build file> <sha1 hash of build file> 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:11-slim
LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds
ARG version=0.9.0-rc1 ARG version=0.9.0
WORKDIR /rgbds WORKDIR /rgbds
COPY . . COPY . .

View File

@@ -26,19 +26,16 @@ PNGLDLIBS := `${PKG_CONFIG} --libs-only-l libpng`
# Note: if this comes up empty, `version.cpp` will automatically fall back to last release number # Note: if this comes up empty, `version.cpp` will automatically fall back to last release number
VERSION_STRING := `git --git-dir=.git describe --tags --dirty --always 2>/dev/null` VERSION_STRING := `git --git-dir=.git describe --tags --dirty --always 2>/dev/null`
WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option \ WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option -Wno-gnu-zero-variadic-macro-arguments
-Wno-gnu-zero-variadic-macro-arguments
# Overridable CXXFLAGS # Overridable CXXFLAGS
CXXFLAGS ?= -O3 -flto -DNDEBUG CXXFLAGS ?= -O3 -flto -DNDEBUG
# Non-overridable CXXFLAGS # Non-overridable CXXFLAGS
REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++2a -I include \ REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++2a -I include -fno-exceptions -fno-rtti
-D_POSIX_C_SOURCE=200809L -fno-exceptions -fno-rtti
# Overridable LDFLAGS # Overridable LDFLAGS
LDFLAGS ?= LDFLAGS ?=
# Non-overridable LDFLAGS # Non-overridable LDFLAGS
REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} \ REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} -DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
-DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
# Wrapper around bison that passes flags depending on what the version supports # Wrapper around bison that passes flags depending on what the version supports
BISON := src/bison.sh BISON := src/bison.sh
@@ -188,7 +185,7 @@ install: all
$Qinstall ${STRIP} -m ${BINMODE} rgbfix ${DESTDIR}${bindir}/rgbfix${SUFFIX} $Qinstall ${STRIP} -m ${BINMODE} rgbfix ${DESTDIR}${bindir}/rgbfix${SUFFIX}
$Qinstall ${STRIP} -m ${BINMODE} rgbgfx ${DESTDIR}${bindir}/rgbgfx${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/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/ $Qinstall -m ${MANMODE} man/rgbds.7 man/gbz80.7 ${DESTDIR}${mandir}/man7/
# Target used to check for suspiciously missing changed files. # Target used to check for suspiciously missing changed files.
@@ -204,7 +201,7 @@ checkdiff:
develop: develop:
$Q${MAKE} WARNFLAGS="${WARNFLAGS} -Werror -Wextra \ $Q${MAKE} WARNFLAGS="${WARNFLAGS} -Werror -Wextra \
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond \ -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 \ -Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused -Wshadow \
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \ -Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
-Wno-format-nonliteral -Wno-strict-overflow -Wno-unused-but-set-variable \ -Wno-format-nonliteral -Wno-strict-overflow -Wno-unused-but-set-variable \

View File

@@ -3,13 +3,16 @@
This describes for the maintainers of RGBDS how to publish a new release on This describes for the maintainers of RGBDS how to publish a new release on
GitHub. GitHub.
1. Update, commit, and push [include/version.hpp](include/version.hpp) with 1. Update the following files, then commit and push.
values for `PACKAGE_VERSION_MAJOR`, `PACKAGE_VERSION_MINOR`, You can use <code>git commit -m "Release <i>&lt;version&gt;</i>"</code> and `git push origin master`.
`PACKAGE_VERSION_PATCH`, and `PACKAGE_VERSION_RC`, as well as
[Dockerfile](Dockerfile) with a value for `ARG version`. Only define - [include/version.hpp](include/version.hpp): set appropriate values for `PACKAGE_VERSION_MAJOR`,
`PACKAGE_VERSION_RC` if you are publishing a release candidate! You can `PACKAGE_VERSION_MINOR`, `PACKAGE_VERSION_PATCH`, and `PACKAGE_VERSION_RC`.
use <code>git commit -m "Release <i>&lt;version&gt;</i>"</code> and **Only** define `PACKAGE_VERSION_RC` if you are publishing a release candidate!
`git push origin master`. - [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>, 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> 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) 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). 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 1. Clone [rgbds-www](https://github.com/gbdev/rgbds-www). You can use
`git clone https://github.com/gbdev/rgbds-www.git`. `git clone https://github.com/gbdev/rgbds-www.git`.
@@ -52,8 +57,8 @@ GitHub.
If you do not have `groff` installed, you can change If you do not have `groff` installed, you can change
`groff -Tpdf -mdoc -wall` to `mandoc -Tpdf -I os=Linux` in `groff -Tpdf -mdoc -wall` to `mandoc -Tpdf -I os=Linux` in
[.github/actions/get-pages.sh](.github/actions/get-pages.sh) and it [maintainer/man_to_html.sh](https://github.com/gbdev/rgbds-www/blob/master/maintainer/man_to_html.sh)
will suffice. and it will suffice.
4. Commit and push the documentation. You can use <code>git commit -m 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` "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! 6. Click the "Publish release" button to publish it!
7. Update the `release` branch. You can use `git push origin release`. 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

@@ -192,6 +192,8 @@ _rgbasm_completions() {
shift-amount shift-amount
truncation truncation
unmapped-char unmapped-char
unmatched-directive
unterminated-load
user user
all all
extra extra

View File

@@ -26,6 +26,8 @@ _rgbasm_warnings() {
'shift-amount:Warn when a shift'\''s operand it negative or \> 32' 'shift-amount:Warn when a shift'\''s operand it negative or \> 32'
'truncation:Warn when implicit truncation loses bits' 'truncation:Warn when implicit truncation loses bits'
'unmapped-char:Warn on unmapped character' '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' 'user:Warn when executing the WARN built-in'
) )
# TODO: handle `no-` and `error=` somehow? # TODO: handle `no-` and `error=` somehow?

View File

@@ -18,6 +18,7 @@ void charmap_New(std::string const &name, std::string const *baseName);
void charmap_Set(std::string const &name); void charmap_Set(std::string const &name);
void charmap_Push(); void charmap_Push();
void charmap_Pop(); void charmap_Pop();
void charmap_CheckStack();
void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value); void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value);
bool charmap_HasChar(std::string const &input); bool charmap_HasChar(std::string const &input);
std::vector<int32_t> charmap_Convert(std::string const &input); std::vector<int32_t> charmap_Convert(std::string const &input);

View File

@@ -30,8 +30,8 @@ struct FileStackNode {
// Meaningless at the root level, but gets written to the object file anyway, so init it // Meaningless at the root level, but gets written to the object file anyway, so init it
uint32_t lineNo = 0; uint32_t lineNo = 0;
// Set only if referenced: ID within the object file, -1 if not output yet // Set only if referenced: ID within the object file, `UINT32_MAX` if not output yet
uint32_t ID = -1; uint32_t ID = UINT32_MAX;
// REPT iteration counts since last named node, in reverse depth order // REPT iteration counts since last named node, in reverse depth order
std::vector<uint32_t> &iters() { return data.get<std::vector<uint32_t>>(); } std::vector<uint32_t> &iters() { return data.get<std::vector<uint32_t>>(); }

View File

@@ -14,5 +14,6 @@ void opt_Parse(char const *option);
void opt_Push(); void opt_Push();
void opt_Pop(); void opt_Pop();
void opt_CheckStack();
#endif // RGBDS_ASM_OPT_HPP #endif // RGBDS_ASM_OPT_HPP

View File

@@ -53,7 +53,7 @@ struct Expression {
void makeUnaryOp(RPNCommand op, Expression &&src); void makeUnaryOp(RPNCommand op, Expression &&src);
void makeBinaryOp(RPNCommand op, Expression &&src1, Expression const &src2); void makeBinaryOp(RPNCommand op, Expression &&src1, Expression const &src2);
void makeCheckHRAM(); bool makeCheckHRAM();
void makeCheckRST(); void makeCheckRST();
void checkNBit(uint8_t n) const; void checkNBit(uint8_t n) const;

View File

@@ -70,7 +70,8 @@ void sect_SetLoadSection(
SectionSpec const &attrs, SectionSpec const &attrs,
SectionModifier mod SectionModifier mod
); );
void sect_EndLoadSection(); void sect_EndLoadSection(char const *cause);
void sect_CheckLoadClosed();
Section *sect_GetSymbolSection(); Section *sect_GetSymbolSection();
uint32_t sect_GetSymbolOffset(); 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_EndSection();
void sect_PushSection(); void sect_PushSection();
void sect_PopSection(); void sect_PopSection();
void sect_CheckStack();
#endif // RGBDS_ASM_SECTION_HPP #endif // RGBDS_ASM_SECTION_HPP

View File

@@ -45,7 +45,7 @@ struct Symbol {
> >
data; 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 uint32_t defIndex; // Ordering of the symbol in the state file
bool isDefined() const { return type != SYM_REF; } bool isDefined() const { return type != SYM_REF; }
@@ -55,7 +55,7 @@ struct Symbol {
bool isConstant() const { bool isConstant() const {
if (type == SYM_LABEL) { if (type == SYM_LABEL) {
Section const *sect = getSection(); Section const *sect = getSection();
return sect && sect->org != (uint32_t)-1; return sect && sect->org != UINT32_MAX;
} }
return type == SYM_EQU || type == SYM_VAR; return type == SYM_EQU || type == SYM_VAR;
} }

View File

@@ -5,31 +5,30 @@
extern unsigned int nbErrors, maxErrors; extern unsigned int nbErrors, maxErrors;
enum WarningState { WARNING_DEFAULT, WARNING_DISABLED, WARNING_ENABLED, WARNING_ERROR };
enum WarningID { enum WarningID {
WARNING_ASSERT, // Assertions WARNING_ASSERT, // Assertions
WARNING_BACKWARDS_FOR, // `for` loop with backwards range WARNING_BACKWARDS_FOR, // `FOR` loop with backwards range
WARNING_BUILTIN_ARG, // Invalid args to builtins WARNING_BUILTIN_ARG, // Invalid args to builtins
WARNING_CHARMAP_REDEF, // Charmap entry re-definition WARNING_CHARMAP_REDEF, // Charmap entry re-definition
WARNING_DIV, // Division undefined behavior WARNING_DIV, // Undefined division behavior
WARNING_EMPTY_DATA_DIRECTIVE, // `db`, `dw` or `dl` directive without data in ROM WARNING_EMPTY_DATA_DIRECTIVE, // `db`, `dw` or `dl` directive without data in ROM
WARNING_EMPTY_MACRO_ARG, // Empty macro argument WARNING_EMPTY_MACRO_ARG, // Empty macro argument
WARNING_EMPTY_STRRPL, // Empty second argument in `STRRPL` WARNING_EMPTY_STRRPL, // Empty second argument in `STRRPL`
WARNING_LARGE_CONSTANT, // Constants too large WARNING_LARGE_CONSTANT, // Constants too large
WARNING_MACRO_SHIFT, // Shift past available arguments in macro WARNING_MACRO_SHIFT, // `SHIFT` past available arguments in macro
WARNING_NESTED_COMMENT, // Comment-start delimiter in a block comment WARNING_NESTED_COMMENT, // Comment-start delimiter in a block comment
WARNING_OBSOLETE, // Obsolete things WARNING_OBSOLETE, // Obsolete/deprecated things
WARNING_SHIFT, // Shifting undefined behavior WARNING_SHIFT, // Undefined `SHIFT` behavior
WARNING_SHIFT_AMOUNT, // Strange shift amount WARNING_SHIFT_AMOUNT, // Strange `SHIFT` amount
WARNING_USER, // User warnings 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, NB_PLAIN_WARNINGS,
// Warnings past this point are "parametric" warnings, only mapping to a single flag // Warnings past this point are "parametric" warnings, only mapping to a single flag
#define PARAM_WARNINGS_START NB_PLAIN_WARNINGS
// Treating string as number may lose some bits // 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, WARNING_NUMERIC_STRING_2,
// Purging an exported symbol or label // Purging an exported symbol or label
WARNING_PURGE_1, WARNING_PURGE_1,
@@ -41,20 +40,24 @@ enum WarningID {
WARNING_UNMAPPED_CHAR_1, WARNING_UNMAPPED_CHAR_1,
WARNING_UNMAPPED_CHAR_2, 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, 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; extern bool warningsAreErrors;
void processWarningFlag(char const *flag); void processWarningFlag(char const *flag);

View File

@@ -43,11 +43,11 @@ private:
// Generic field accessors; for internal use only. // Generic field accessors; for internal use only.
template<typename T> template<typename T>
auto &field() { auto &field() {
return pick((T *)nullptr); return pick(static_cast<T *>(nullptr));
} }
template<typename T> template<typename T>
auto const &field() const { auto const &field() const {
return pick((T *)nullptr); return pick(static_cast<T *>(nullptr));
} }
public: public:

View File

@@ -53,7 +53,7 @@ struct Options {
static constexpr uint8_t VERB_LOG_ACT = 2; // Log actions before doing them static constexpr uint8_t VERB_LOG_ACT = 2; // Log actions before doing them
static constexpr uint8_t VERB_INTERM = 3; // Print some intermediate results 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_DEBUG = 4; // Internals are logged
static constexpr uint8_t VERB_UNMAPPED = 5; // Unused so far static constexpr uint8_t VERB_TRACE = 5; // Step-by-step algorithm details
static constexpr uint8_t VERB_VVVVVV = 6; // What, can't I have a little fun? static constexpr uint8_t VERB_VVVVVV = 6; // What, can't I have a little fun?
[[gnu::format(printf, 3, 4)]] void verbosePrint(uint8_t level, char const *fmt, ...) const; [[gnu::format(printf, 3, 4)]] void verbosePrint(uint8_t level, char const *fmt, ...) const;
@@ -107,20 +107,18 @@ struct Palette {
uint8_t size() const; uint8_t size() const;
}; };
namespace detail { // Flipping tends to happen fairly often, so take a bite out of dcache to speed it up
template<typename T, T... i> static constexpr auto flipTable = ([]() constexpr {
static constexpr auto flipTable(std::integer_sequence<T, i...>) { std::array<uint16_t, 256> table{};
return std::array{[](uint8_t byte) { for (uint16_t i = 0; i < table.size(); i++) {
// To flip all the bits, we'll flip both nibbles, then each nibble half, etc. // 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 & 0b0000'1111) << 4 | (byte & 0b1111'0000) >> 4;
byte = (byte & 0b0011'0011) << 2 | (byte & 0b1100'1100) >> 2; byte = (byte & 0b0011'0011) << 2 | (byte & 0b1100'1100) >> 2;
byte = (byte & 0b0101'0101) << 1 | (byte & 0b1010'1010) >> 1; byte = (byte & 0b0101'0101) << 1 | (byte & 0b1010'1010) >> 1;
return byte; table[i] = byte;
}(i)...}; }
} return table;
} // 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>());
#endif // RGBDS_GFX_MAIN_HPP #endif // RGBDS_GFX_MAIN_HPP

View File

@@ -6,19 +6,15 @@
#include <tuple> #include <tuple>
#include <vector> #include <vector>
#include "defaultinitalloc.hpp" #include "defaultinitvec.hpp"
struct Palette; struct Palette;
class ProtoPalette; 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> std::tuple<DefaultInitVec<size_t>, size_t>
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes); overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes);
} // namespace packing
#endif // RGBDS_GFX_PAL_PACKING_HPP #endif // RGBDS_GFX_PAL_PACKING_HPP

View File

@@ -12,20 +12,16 @@
struct Palette; struct Palette;
namespace sorting { void sortIndexed(
void indexed(
std::vector<Palette> &palettes, std::vector<Palette> &palettes,
int palSize, int palSize,
png_color const *palRGB, png_color const *palRGB,
int palAlphaSize, int palAlphaSize,
png_byte *palAlpha png_byte *palAlpha
); );
void grayscale( void sortGrayscale(
std::vector<Palette> &palettes, std::array<std::optional<Rgba>, 0x8001> const &colors std::vector<Palette> &palettes, std::array<std::optional<Rgba>, 0x8001> const &colors
); );
void rgb(std::vector<Palette> &palettes); void sortRgb(std::vector<Palette> &palettes);
} // namespace sorting
#endif // RGBDS_GFX_PAL_SORTING_HPP #endif // RGBDS_GFX_PAL_SORTING_HPP

View File

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

View File

@@ -28,7 +28,7 @@ struct Rgba {
_5to8(cgbColor), _5to8(cgbColor),
_5to8(cgbColor >> 5), _5to8(cgbColor >> 5),
_5to8(cgbColor >> 10), _5to8(cgbColor >> 10),
(uint8_t)(cgbColor & 0x8000 ? 0x00 : 0xFF), static_cast<uint8_t>(cgbColor & 0x8000 ? 0x00 : 0xFF),
}; };
} }
@@ -40,8 +40,8 @@ struct Rgba {
auto shl = [](uint8_t val, unsigned shift) { return static_cast<uint32_t>(val) << shift; }; auto shl = [](uint8_t val, unsigned shift) { return static_cast<uint32_t>(val) << shift; };
return shl(red, 24) | shl(green, 16) | shl(blue, 8) | shl(alpha, 0); return shl(red, 24) | shl(green, 16) | shl(blue, 8) | shl(alpha, 0);
} }
friend bool operator==(Rgba const &lhs, Rgba const &rhs) { return lhs.toCSS() == rhs.toCSS(); } bool operator==(Rgba const &rhs) const { return 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(); }
/* /*
* CGB colors are RGB555, so we use bit 15 to signify that the color is transparent instead * CGB colors are RGB555, so we use bit 15 to signify that the color is transparent instead

View File

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

View File

@@ -56,4 +56,9 @@
#define setmode(fd, mode) (0) #define setmode(fd, mode) (0)
#endif #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 #endif // RGBDS_PLATFORM_HPP

View File

@@ -8,7 +8,6 @@ extern "C" {
#define PACKAGE_VERSION_MAJOR 0 #define PACKAGE_VERSION_MAJOR 0
#define PACKAGE_VERSION_MINOR 9 #define PACKAGE_VERSION_MINOR 9
#define PACKAGE_VERSION_PATCH 0 #define PACKAGE_VERSION_PATCH 0
#define PACKAGE_VERSION_RC 1
char const *get_package_version_string(); char const *get_package_version_string();
} }

File diff suppressed because it is too large Load Diff

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

@@ -0,0 +1,375 @@
'\" e
.\"
.\" SPDX-License-Identifier: MIT
.\"
.Dd December 25, 2024
.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 syntax bugs that were fixed, 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] .
.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 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 .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd September 18, 2024 .Dd December 25, 2024
.Dt RGBASM 1 .Dt RGBASM 1
.Os .Os
.Sh NAME .Sh NAME
@@ -200,7 +200,7 @@ section for a list of warnings.
Disable all warning output, even when turned into errors. Disable all warning output, even when turned into errors.
.It Fl X Ar max_errors , Fl \-max-errors Ar max_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; 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. disables this behavior.
The default is 100 if The default is 100 if
.Nm .Nm
@@ -212,13 +212,19 @@ The following options alter the way warnings are processed.
.Bl -tag -width Ds .Bl -tag -width Ds
.It Fl Werror .It Fl Werror
Make all warnings into errors. Make all warnings into errors.
This can be negated as
.Fl Wno-error
to prevent turning all warnings into errors.
.It Fl Werror= .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 A warning's name is appended
.Pq example: Fl Werror=obsolete , .Pq example: Fl Werror=obsolete ,
and this warning is implicitly enabled and turned into an error. and this warning is implicitly enabled and turned into an error.
This is an error if used with a meta warning, such as This can be negated as
.Fl Werror=all . .Fl Wno-error=
to prevent turning a specified warning into an error, even if
.Fl Werror
is in effect.
.El .El
.Pp .Pp
The following warnings are The following warnings are
@@ -240,6 +246,10 @@ Note that each of these flag also has a negation (for example,
.Fl Wcharmap-redef .Fl Wcharmap-redef
enables the warning that enables the warning that
.Fl Wno-charmap-redef .Fl Wno-charmap-redef
disables; and
.Fl Wall
enables every warning that
.Fl Wno-all
disables). disables).
Only the non-default flag is listed here. Only the non-default flag is listed here.
Ignoring the Ignoring the
@@ -291,6 +301,13 @@ This warning is enabled by
Warn when shifting macro arguments past their limits. Warn when shifting macro arguments past their limits.
This warning is enabled by This warning is enabled by
.Fl Wextra . .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 .It Fl Wno-obsolete
Warn when obsolete constructs such as the Warn when obsolete constructs such as the
.Ic _PI .Ic _PI
@@ -344,7 +361,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. Warn when a character goes through charmap conversion but has no defined mapping.
.Fl Wunmapped-char=0 .Fl Wunmapped-char=0
or or
.Fl Wunmapped-char .Fl Wno-unmapped-char
disables this warning. disables this warning.
.Fl Wunmapped-char=1 .Fl Wunmapped-char=1
or just or just
@@ -353,6 +370,24 @@ only warns if the active charmap is not empty.
.Fl Wunmapped-char=2 .Fl Wunmapped-char=2
warns if the active charmap is empty, and/or is not the default charmap warns if the active charmap is empty, and/or is not the default charmap
.Sq main . .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 .It Fl Wno-user
Warn when the Warn when the
.Ic WARN .Ic WARN
@@ -392,6 +427,7 @@ Please report bugs on
.Xr rgbfix 1 , .Xr rgbfix 1 ,
.Xr rgbgfx 1 , .Xr rgbgfx 1 ,
.Xr gbz80 7 , .Xr gbz80 7 ,
.Xr rgbasm-old 5 ,
.Xr rgbds 5 , .Xr rgbds 5 ,
.Xr rgbds 7 .Xr rgbds 7
.Sh HISTORY .Sh HISTORY

View File

@@ -2,7 +2,7 @@
.\" .\"
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd September 18, 2024 .Dd December 25, 2024
.Dt RGBASM 5 .Dt RGBASM 5
.Os .Os
.Sh NAME .Sh NAME
@@ -41,7 +41,6 @@ or
Labels tie a name to a specific location within a section (see Labels tie a name to a specific location within a section (see
.Sx Labels .Sx Labels
below). below).
They must come first in the line.
.Pp .Pp
Instructions are assembled into Game Boy opcodes. Instructions are assembled into Game Boy opcodes.
Multiple instructions on one line can be separated by double colons Multiple instructions on one line can be separated by double colons
@@ -254,7 +253,7 @@ Although, for these examples,
.Ic STRFMT .Ic STRFMT
would be more appropriate; see would be more appropriate; see
.Sx String expressions .Sx String expressions
further below. below.
.Sh EXPRESSIONS .Sh EXPRESSIONS
An expression can be composed of many things. An expression can be composed of many things.
Numeric expressions are always evaluated using signed 32-bit math. 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. section.
However, some operators can be constant even with non-constant operands, as explained in However, some operators can be constant even with non-constant operands, as explained in
.Sx Operators .Sx Operators
further below. below.
.Pp .Pp
The instructions in the macro-language generally require constant expressions. The instructions in the macro-language generally require constant expressions.
.Ss Numeric formats .Ss Numeric formats
There are a number of numeric formats. There are a number of numeric formats.
.Bl -column -offset indent "Precise fixed-point" "Prefix" .Bl -column -offset indent "Precise fixed-point" "Possible prefixes"
.It Sy Format type Ta Sy Prefix Ta Sy Accepted characters .It Sy Format type Ta Sy Possible prefixes Ta Sy Accepted characters
.It Hexadecimal Ta $ Ta 0123456789ABCDEF
.It Decimal Ta none Ta 0123456789 .It Decimal Ta none Ta 0123456789
.It Octal Ta & Ta 01234567 .It Hexadecimal Ta Li $ , 0x , 0X Ta 0123456789ABCDEF
.It Binary Ta % Ta 01 .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 Fixed-point Ta none Ta 01234.56789
.It Precise fixed-point Ta none Ta 12.34q8 .It Precise fixed-point Ta none Ta 12.34q8
.It Character constant Ta none Ta \(dqABYZ\(dq .It Character constant Ta none Ta \(dqABYZ\(dq
.It Game Boy graphics Ta \` Ta 0123 .It Game Boy graphics Ta Li \` Ta 0123
.El .El
.Pp .Pp
Underscores are also accepted in numbers, except at the beginning of one. Underscores are also accepted in numbers, except at the beginning of one.
@@ -310,26 +309,35 @@ is equivalent to
.Pp .Pp
You can also use symbols, which are implicitly replaced with their value. You can also use symbols, which are implicitly replaced with their value.
.Ss Operators .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 "!= == <= >= < >" .Bl -column -offset indent "!= == <= >= < >"
.It Sy Operator Ta Sy Meaning .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 FUNC() Ta Built-in function call
.It Li ** Ta Exponent .It Li ** Ta Exponentiation
.It Li ~ + - Ta Unary complement/plus/minus .It Li + - ~ \&! Ta Unary plus, minus (negation), complement (bitwise negation), and Boolean negation
.It Li * / % Ta Multiply/divide/modulo .It Li * / % Ta Multiplication, division, and modulo (remainder)
.It Li << Ta Shift left .It Li << >> >>> Ta Bit shifts (left, sign-extended right, zero-extended right)
.It Li >> Ta Signed shift right (sign-extension) .It Li & \&| ^ Ta Bitwise AND/OR/XOR
.It Li >>> Ta Unsigned shift right (zero-extension) .It Li + - Ta Addition and subtraction
.It Li & \&| ^ Ta Binary and/or/xor .It Li == != < > <= >= Ta Comparisons
.It Li + - Ta Add/subtract .It Li && Ta Boolean AND
.It Li != == <= >= < > Ta Comparison .It Li || Ta Boolean OR
.It Li && || Ta Boolean and/or
.It Li \&! Ta Unary not
.El .El
.Pp .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 ~ .Sq ~
complements a value by inverting all its bits. complements a value by inverting all 32 of its bits.
.Pp .Pp
.Sq % .Sq %
is used to get the remainder of the corresponding division, so that 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. Besides operators, there are also some functions which have more specialized uses.
.Bl -column "BITWIDTH(n)" .Bl -column "BITWIDTH(n)"
.It Sy Name Ta Sy Operation .It Sy Name Ta Sy Operation
.It Fn HIGH n Ta Equivalent to Ql Ar n No & $FF . .It Fn HIGH n Ta Equivalent to Ql Po Ns Ar n No & $FF00 Pc >> 8 .
.It Fn LOW 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 .EQ
delim $$ delim $$
.EN .EN
@@ -904,7 +912,7 @@ SECTION "LOAD example", ROMX
CopyCode: CopyCode:
ld de, RAMCode ld de, RAMCode
ld hl, RAMLocation ld hl, RAMLocation
ld c, RAMLocation.end - RAMLocation ld c, RAMCode.end - RAMCode
\&.loop \&.loop
ld a, [de] ld a, [de]
inc de inc de
@@ -928,8 +936,8 @@ RAMLocation:
\&.string \&.string
db "Hello World!\e0" db "Hello World!\e0"
\&.end
ENDL ENDL
\&.end
.Ed .Ed
.Pp .Pp
A A
@@ -939,7 +947,9 @@ block feels similar to a
declaration because it creates a new one. 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. 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 .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 You will notice the
.Sq RAMCode .Sq RAMCode
and and
@@ -951,15 +961,30 @@ You cannot nest
.Ic LOAD .Ic LOAD
blocks, nor can you change or stop the current section within them. blocks, nor can you change or stop the current section within them.
.Pp .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 .Ic LOAD
blocks can use the blocks can use the
.Ic UNION .Ic UNION
or or
.Ic FRAGMENT .Ic FRAGMENT
modifiers, as described below. modifiers as described in
.Sx Unionized sections
below.
.Ss Unionized sections .Ss Unionized sections
When you're tight on RAM, you may want to define overlapping static memory allocations, as explained in the When you're tight on RAM, you may want to define overlapping static memory allocations, as explained in the
.Sx Unions .Sx Allocating overlapping spaces in RAM
section. section.
However, a However, a
.Ic UNION .Ic UNION
@@ -1000,7 +1025,7 @@ or
.El .El
.Pp .Pp
Different declarations of the same unionized section are not appended, but instead overlaid on top of each other, just like 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. Similarly, the size of an unionized section is the largest of all its declarations.
.Ss Section fragments .Ss Section fragments
Section fragments are sections with a small twist: when several of the same name are encountered, they are concatenated instead of producing an error. Section fragments are sections with a small twist: when several of the same name are encountered, they are concatenated instead of producing an error.
@@ -1123,7 +1148,7 @@ otherwise, it is said to be
.Dq exported , .Dq exported ,
explained in explained in
.Sx Exporting and importing symbols .Sx Exporting and importing symbols
further below). below).
More than one dot in label names is not allowed. More than one dot in label names is not allowed.
.Pp .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 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 Refer to the spec at
.Lk https://reproducible-builds.org/docs/source-date-epoch/ reproducible-builds.org . .Lk https://reproducible-builds.org/docs/source-date-epoch/ reproducible-builds.org .
.Sh DEFINING DATA .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 .Ss Defining constant data in ROM
.Ic DB .Ic DB
defines a list of bytes that will be stored in the final image. defines a list of bytes that will be stored in the final image.
@@ -1660,7 +1644,7 @@ can be used in a
/ /
.Ic SRAM .Ic SRAM
section. section.
.Ss Including binary files .Ss Including binary data files
You probably have some graphics, level data, etc. you'd like to include. You probably have some graphics, level data, etc. you'd like to include.
Use Use
.Ic INCBIN .Ic INCBIN
@@ -1684,7 +1668,48 @@ INCBIN "data.bin", 78, 256
.Pp .Pp
The length argument is optional. The length argument is optional.
If only the start position is specified, the bytes from the start position until the end of the file will be included. If only the start position is specified, the bytes from the start position until the end of the file will be included.
.Ss Unions .Ss 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. Unions allow multiple static memory allocations to overlap, like unions in C.
This does not increase the amount of memory available, but allows re-using the same memory region for different purposes. This does not increase the amount of memory available, but allows re-using the same memory region for different purposes.
.Pp .Pp
@@ -1745,6 +1770,37 @@ Unions may be used in any section, but they may only contain space-allocating di
.Ic DS .Ic DS
(see (see
.Sx Statically allocating space in RAM ) . .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 .Sh THE MACRO LANGUAGE
.Ss Invoking macros .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. 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 .Pp
.Ic OPT .Ic OPT
can modify the options can modify the options
.Cm b , g , p , Q , .Cm b , g , p , Q , r ,
and and
.Cm r . .Cm W .
.Pp .Pp
.Ic POPO .Ic POPO
and and
@@ -2306,37 +2362,6 @@ PUSHO b.X, g.oOX
DW `..ooOOXX DW `..ooOOXX
POPO POPO
.Ed .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 .Sh SEE ALSO
.Xr rgbasm 1 , .Xr rgbasm 1 ,
.Xr rgblink 1 , .Xr rgblink 1 ,
@@ -2344,6 +2369,7 @@ is a shorthand for
.Xr rgbfix 1 , .Xr rgbfix 1 ,
.Xr rgbgfx 1 , .Xr rgbgfx 1 ,
.Xr gbz80 7 , .Xr gbz80 7 ,
.Xr rgbasm-old 5 ,
.Xr rgbds 5 , .Xr rgbds 5 ,
.Xr rgbds 7 .Xr rgbds 7
.Sh HISTORY .Sh HISTORY

View File

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

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd September 18, 2024 .Dd December 25, 2024
.Dt RGBDS 7 .Dt RGBDS 7
.Os .Os
.Sh NAME .Sh NAME

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd September 18, 2024 .Dd December 25, 2024
.Dt RGBFIX 1 .Dt RGBFIX 1
.Os .Os
.Sh NAME .Sh NAME

View File

@@ -2,7 +2,7 @@
.\" .\"
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd September 18, 2024 .Dd December 25, 2024
.Dt RGBGFX 1 .Dt RGBGFX 1
.Os .Os
.Sh NAME .Sh NAME
@@ -10,7 +10,7 @@
.Nd Game Boy graphics converter .Nd Game Boy graphics converter
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm .Nm
.Op Fl CmOuVZ .Op Fl CmOuVXYZ
.Op Fl v Op Fl v No ... .Op Fl v Op Fl v No ...
.Op Fl a Ar attrmap | Fl A .Op Fl a Ar attrmap | Fl A
.Op Fl b Ar base_ids .Op Fl b Ar base_ids
@@ -229,9 +229,8 @@ The second number pair specifies how many tiles to process horizontally and vert
.Pp .Pp
.Fl L Sy is ignored in reverse mode , No no padding is inserted . .Fl L Sy is ignored in reverse mode , No no padding is inserted .
.It Fl m , Fl \-mirror-tiles .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. 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 Useful with a tile map and attribute map together (see
.Fl a .Fl a
and and
@@ -239,6 +238,8 @@ and
to keep track of the duplicated tiles and the dimension(s) mirrored. to keep track of the duplicated tiles and the dimension(s) mirrored.
Implies Implies
.Fl u . .Fl u .
Equivalent to
.Fl XY .
.It Fl N Ar nb_tiles , Fl \-nb-tiles Ar nb_tiles .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. Set a maximum number of tiles that can be placed in each VRAM bank.
.Ar nb_tiles .Ar nb_tiles
@@ -353,6 +354,10 @@ Some internal debug printing is enabled.
The verbosity level does not go past 6. The verbosity level does not go past 6.
.Pp .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. 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 .It Fl x Ar quantity , Fl \-trim-end Ar quantity
Do not output the last Do not output the last
.Ar quantity .Ar quantity
@@ -373,6 +378,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 Note also that the tiles that don't get output will not count towards
.Fl N Ap s .Fl N Ap s
limit. 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 .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). 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. 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 .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd September 18, 2024 .Dd December 25, 2024
.Dt RGBLINK 1 .Dt RGBLINK 1
.Os .Os
.Sh NAME .Sh NAME

View File

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

View File

@@ -91,10 +91,11 @@ foreach(PROG "asm" "fix" "gfx" "link")
${rgb${PROG}_src} ${rgb${PROG}_src}
${common_src} ${common_src}
) )
if(SUFFIX)
set_target_properties(rgb${PROG} PROPERTIES SUFFIX ${SUFFIX})
endif()
install(TARGETS rgb${PROG} RUNTIME DESTINATION bin) 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() endforeach()
if(LIBPNG_FOUND) # pkg-config if(LIBPNG_FOUND) # pkg-config

View File

@@ -53,7 +53,7 @@ bool charmap_ForEach(
mappings[nodeIdx] = mapping; mappings[nodeIdx] = mapping;
for (unsigned c = 0; c < 256; c++) { for (unsigned c = 0; c < 256; c++) {
if (size_t nextIdx = node.next[c]; nextIdx) if (size_t nextIdx = node.next[c]; nextIdx)
prefixes.push({nextIdx, mapping + (char)c}); prefixes.push({nextIdx, mapping + static_cast<char>(c)});
} }
} }
mapFunc(charmap.name); mapFunc(charmap.name);
@@ -64,7 +64,7 @@ bool charmap_ForEach(
} }
void charmap_New(std::string const &name, std::string const *baseName) { 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 (baseName != nullptr) {
if (auto search = charmapMap.find(*baseName); search == charmapMap.end()) if (auto search = charmapMap.find(*baseName); search == charmapMap.end())
@@ -82,7 +82,7 @@ void charmap_New(std::string const &name, std::string const *baseName) {
charmapMap[name] = charmapList.size(); charmapMap[name] = charmapList.size();
Charmap &charmap = charmapList.emplace_back(); Charmap &charmap = charmapList.emplace_back();
if (baseIdx != (size_t)-1) if (baseIdx != SIZE_MAX)
charmap.nodes = charmapList[baseIdx].nodes; // Copies `charmapList[baseIdx].nodes` charmap.nodes = charmapList[baseIdx].nodes; // Copies `charmapList[baseIdx].nodes`
else else
charmap.nodes.emplace_back(); // Zero-init the root node charmap.nodes.emplace_back(); // Zero-init the root node
@@ -113,6 +113,12 @@ void charmap_Pop() {
charmapStack.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) { void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value) {
if (mapping.empty()) { if (mapping.empty()) {
error("Cannot map an empty string\n"); error("Cannot map an empty string\n");
@@ -123,7 +129,7 @@ void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value) {
size_t nodeIdx = 0; size_t nodeIdx = 0;
for (char c : mapping) { 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; size_t nextIdx = nextIdxRef;
if (!nextIdx) { if (!nextIdx) {
@@ -151,7 +157,7 @@ bool charmap_HasChar(std::string const &input) {
size_t nodeIdx = 0; size_t nodeIdx = 0;
for (char c : input) { 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 false;
@@ -178,7 +184,7 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output
size_t inputIdx = 0; size_t inputIdx = 0;
for (size_t nodeIdx = 0; inputIdx < input.length();) { 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; break;

View File

@@ -29,7 +29,7 @@ static int32_t double2fix(double d, int32_t q) {
return 0; return 0;
if (isinf(d)) if (isinf(d))
return d < 0 ? INT32_MIN : INT32_MAX; return d < 0 ? INT32_MIN : INT32_MAX;
return (int32_t)round(d * pow(2.0, q)); return static_cast<int32_t>(round(d * pow(2.0, q)));
} }
static double turn2rad(double t) { static double turn2rad(double t) {

View File

@@ -186,7 +186,8 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
useExact = true; 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); error("Formatting type '%c' with exact flag '#'\n", useType);
if (useType != 'f' && hasFrac) if (useType != 'f' && hasFrac)
error("Formatting type '%c' with fractional width\n", useType); error("Formatting type '%c' with fractional width\n", useType);
@@ -249,15 +250,16 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
} }
double fval = fabs(value / pow(2.0, usePrec)); double fval = fabs(value / pow(2.0, usePrec));
if (useExact) if (int fracWidthArg = static_cast<int>(useFracWidth); useExact)
snprintf(valueBuf, sizeof(valueBuf), "%.*fq%zu", (int)useFracWidth, fval, usePrec); snprintf(valueBuf, sizeof(valueBuf), "%.*fq%zu", fracWidthArg, fval, usePrec);
else else
snprintf(valueBuf, sizeof(valueBuf), "%.*f", (int)useFracWidth, fval); snprintf(valueBuf, sizeof(valueBuf), "%.*f", fracWidthArg, fval);
} else if (useType == 'd') { } else if (useType == 'd') {
// Decimal numbers may be formatted with a '-' sign by `snprintf`, so `abs` prevents that, // Decimal numbers may be formatted with a '-' sign by `snprintf`, so `abs` prevents that,
// with a special case for `INT32_MIN` since `labs(INT32_MIN)` is UB. The sign will be // with a special case for `INT32_MIN` since `labs(INT32_MIN)` is UB. The sign will be
// printed later from `signChar`. // 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); snprintf(valueBuf, sizeof(valueBuf), "%" PRIu32, uval);
} else { } else {
char const *spec = useType == 'u' ? "%" PRIu32 char const *spec = useType == 'u' ? "%" PRIu32

View File

@@ -162,7 +162,7 @@ bool yywrap() {
// If the node is referenced outside this context, we can't edit it, so duplicate it // If the node is referenced outside this context, we can't edit it, so duplicate it
if (context.fileInfo.use_count() > 1) { if (context.fileInfo.use_count() > 1) {
context.fileInfo = std::make_shared<FileStackNode>(*context.fileInfo); 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(); std::vector<uint32_t> &fileInfoIters = context.fileInfo->iters();
@@ -170,8 +170,10 @@ bool yywrap() {
// If this is a FOR, update the symbol value // If this is a FOR, update the symbol value
if (context.isForLoop && fileInfoIters.front() <= context.nbReptIters) { if (context.isForLoop && fileInfoIters.front() <= context.nbReptIters) {
// Avoid arithmetic overflow runtime error // Avoid arithmetic overflow runtime error
uint32_t forValue = (uint32_t)context.forValue + (uint32_t)context.forStep; uint32_t forValue =
context.forValue = forValue <= INT32_MAX ? forValue : -(int32_t)~forValue - 1; 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); Symbol *sym = sym_AddVar(context.forName, context.forValue);
// This error message will refer to the current iteration // This error message will refer to the current iteration
@@ -347,9 +349,9 @@ void fstk_RunFor(
uint32_t count = 0; uint32_t count = 0;
if (step > 0 && start < stop) if (step > 0 && start < stop)
count = ((int64_t)stop - start - 1) / step + 1; count = (static_cast<int64_t>(stop) - start - 1) / step + 1;
else if (step < 0 && stop < start) else if (step < 0 && stop < start)
count = ((int64_t)start - stop - 1) / -(int64_t)step + 1; count = (static_cast<int64_t>(start) - stop - 1) / -static_cast<int64_t>(step) + 1;
else if (step == 0) else if (step == 0)
error("FOR cannot have a step value of 0\n"); error("FOR cannot have a step value of 0\n");

View File

@@ -86,7 +86,7 @@ static char *mapFile(int fd, std::string const &path, size_t size) {
printf("mmap(%s, MAP_PRIVATE) failed, retrying with MAP_SHARED\n", path.c_str()); printf("mmap(%s, MAP_PRIVATE) failed, retrying with MAP_SHARED\n", path.c_str());
mappingAddr = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0); mappingAddr = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0);
} }
return mappingAddr != MAP_FAILED ? (char *)mappingAddr : nullptr; return mappingAddr != MAP_FAILED ? static_cast<char *>(mappingAddr) : nullptr;
} }
struct FileUnmapDeleter { struct FileUnmapDeleter {
@@ -102,7 +102,7 @@ struct FileUnmapDeleter {
using namespace std::literals; using namespace std::literals;
// Bison 3.6 changed token "types" to "kinds"; cast to int for simple compatibility // Bison 3.6 changed token "types" to "kinds"; cast to int for simple compatibility
#define T_(name) (int)yy::parser::token::name #define T_(name) static_cast<int>(yy::parser::token::name)
struct Token { struct Token {
int type; int type;
@@ -329,6 +329,8 @@ static std::unordered_map<std::string, int, CaseInsensitive, CaseInsensitive> ke
{"OPT", T_(POP_OPT) }, {"OPT", T_(POP_OPT) },
}; };
static auto ldio = keywordDict.find("LDIO");
static bool isWhitespace(int c) { static bool isWhitespace(int c) {
return c == ' ' || c == '\t'; return c == ' ' || c == '\t';
} }
@@ -422,7 +424,7 @@ bool LexerState::setFileAsNextState(std::string const &filePath, bool updateStat
bool isMmapped = false; bool isMmapped = false;
if (size_t size = (size_t)statBuf.st_size; statBuf.st_size > 0) { if (size_t size = static_cast<size_t>(statBuf.st_size); statBuf.st_size > 0) {
// Try using `mmap` for better performance // Try using `mmap` for better performance
if (char *mappingAddr = mapFile(fd, path, size); mappingAddr != nullptr) { if (char *mappingAddr = mapFile(fd, path, size); mappingAddr != nullptr) {
close(fd); close(fd);
@@ -541,7 +543,7 @@ size_t BufferedContent::readMore(size_t startIndex, size_t nbChars) {
size += nbReadChars; size += nbReadChars;
// `nbReadChars` cannot be negative, so it's fine to cast to `size_t` // `nbReadChars` cannot be negative, so it's fine to cast to `size_t`
return (size_t)nbReadChars; return static_cast<size_t>(nbReadChars);
} }
void lexer_SetMode(LexerMode mode) { void lexer_SetMode(LexerMode mode) {
@@ -707,20 +709,20 @@ int LexerState::peekChar() {
// This is `.peekCharAhead()` modified for zero lookahead distance // This is `.peekCharAhead()` modified for zero lookahead distance
for (Expansion &exp : expansions) { for (Expansion &exp : expansions) {
if (exp.offset < exp.size()) if (exp.offset < exp.size())
return (uint8_t)(*exp.contents)[exp.offset]; return static_cast<uint8_t>((*exp.contents)[exp.offset]);
} }
if (content.holds<ViewedContent>()) { if (content.holds<ViewedContent>()) {
auto &view = content.get<ViewedContent>(); auto &view = content.get<ViewedContent>();
if (view.offset < view.span.size) if (view.offset < view.span.size)
return (uint8_t)view.span.ptr[view.offset]; return static_cast<uint8_t>(view.span.ptr[view.offset]);
} else { } else {
auto &cbuf = content.get<BufferedContent>(); auto &cbuf = content.get<BufferedContent>();
if (cbuf.size == 0) if (cbuf.size == 0)
cbuf.refill(); cbuf.refill();
assume(cbuf.offset < LEXER_BUF_SIZE); assume(cbuf.offset < LEXER_BUF_SIZE);
if (cbuf.size > 0) if (cbuf.size > 0)
return (uint8_t)cbuf.buf[cbuf.offset]; return static_cast<uint8_t>(cbuf.buf[cbuf.offset]);
} }
// If there aren't enough chars, give up // If there aren't enough chars, give up
@@ -736,21 +738,21 @@ int LexerState::peekCharAhead() {
// and `.peekCharAhead()` will continue with its parent // and `.peekCharAhead()` will continue with its parent
assume(exp.offset <= exp.size()); assume(exp.offset <= exp.size());
if (exp.offset + distance < exp.size()) if (exp.offset + distance < exp.size())
return (uint8_t)(*exp.contents)[exp.offset + distance]; return static_cast<uint8_t>((*exp.contents)[exp.offset + distance]);
distance -= exp.size() - exp.offset; distance -= exp.size() - exp.offset;
} }
if (content.holds<ViewedContent>()) { if (content.holds<ViewedContent>()) {
auto &view = content.get<ViewedContent>(); auto &view = content.get<ViewedContent>();
if (view.offset + distance < view.span.size) if (view.offset + distance < view.span.size)
return (uint8_t)view.span.ptr[view.offset + distance]; return static_cast<uint8_t>(view.span.ptr[view.offset + distance]);
} else { } else {
auto &cbuf = content.get<BufferedContent>(); auto &cbuf = content.get<BufferedContent>();
assume(distance < LEXER_BUF_SIZE); assume(distance < LEXER_BUF_SIZE);
if (cbuf.size <= distance) if (cbuf.size <= distance)
cbuf.refill(); cbuf.refill();
if (cbuf.size > distance) if (cbuf.size > distance)
return (uint8_t)cbuf.buf[(cbuf.offset + distance) % LEXER_BUF_SIZE]; return static_cast<uint8_t>(cbuf.buf[(cbuf.offset + distance) % LEXER_BUF_SIZE]);
} }
// If there aren't enough chars, give up // If there aren't enough chars, give up
@@ -797,11 +799,9 @@ static int peek() {
} else if (c == '{' && !lexerState->disableInterpolation) { } else if (c == '{' && !lexerState->disableInterpolation) {
// If character is an open brace, do symbol interpolation // If character is an open brace, do symbol interpolation
shiftChar(); shiftChar();
if (auto str = readInterpolation(0); str) { if (auto str = readInterpolation(0); str) {
beginExpansion(str, *str); beginExpansion(str, *str);
} }
return peek(); return peek();
} }
@@ -1032,11 +1032,12 @@ static uint32_t readFractionalPart(uint32_t integer) {
precision = fixPrecision; precision = fixPrecision;
} }
if (integer >= ((uint64_t)1 << (32 - precision))) if (integer >= (1ULL << (32 - precision)))
warning(WARNING_LARGE_CONSTANT, "Magnitude of fixed-point constant is too large\n"); warning(WARNING_LARGE_CONSTANT, "Magnitude of fixed-point constant is too large\n");
// Cast to unsigned avoids undefined overflow behavior // Cast to unsigned avoids undefined overflow behavior
uint32_t fractional = (uint32_t)round((double)value / divisor * pow(2.0, precision)); uint32_t fractional =
static_cast<uint32_t>(round(static_cast<double>(value) / divisor * pow(2.0, precision)));
return (integer << precision) | fractional; return (integer << precision) | fractional;
} }
@@ -1170,9 +1171,13 @@ static Token readIdentifier(char firstChar, bool raw) {
// Attempt to check for a keyword if the identifier is not raw // Attempt to check for a keyword if the identifier is not raw
if (!raw) { if (!raw) {
if (auto search = keywordDict.find(identifier); search != keywordDict.end()) if (auto search = keywordDict.find(identifier); search != keywordDict.end()) {
if (search == ldio) {
warning(WARNING_OBSOLETE, "LDIO is deprecated; use LDH\n");
}
return Token(search->second); return Token(search->second);
} }
}
// Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot // Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot
if (identifier.find_first_not_of('.') == identifier.npos) if (identifier.find_first_not_of('.') == identifier.npos)
@@ -1201,9 +1206,9 @@ static std::shared_ptr<std::string> readInterpolation(size_t depth) {
if (c == '{') { // Nested interpolation if (c == '{') { // Nested interpolation
shiftChar(); shiftChar();
auto str = readInterpolation(depth + 1); if (auto str = readInterpolation(depth + 1); str) {
beginExpansion(str, *str); beginExpansion(str, *str);
}
continue; // Restart, reading from the new buffer continue; // Restart, reading from the new buffer
} else if (c == EOF || c == '\r' || c == '\n' || c == '"') { } else if (c == EOF || c == '\r' || c == '\n' || c == '"') {
error("Missing }\n"); error("Missing }\n");
@@ -1373,6 +1378,7 @@ static std::string readString(bool raw) {
// Line continuation // Line continuation
case ' ': case ' ':
case '\t':
case '\r': case '\r':
case '\n': case '\n':
discardLineContinuation(); discardLineContinuation();
@@ -1505,6 +1511,7 @@ static void appendStringLiteral(std::string &str, bool raw) {
// Line continuation // Line continuation
case ' ': case ' ':
case '\t':
case '\r': case '\r':
case '\n': case '\n':
discardLineContinuation(); discardLineContinuation();
@@ -1731,7 +1738,25 @@ static Token yylex_NORMAL() {
// Handle numbers // Handle numbers
case '0': // Decimal or fixed-point number case '0': // Decimal, fixed-point, or base-prefix number
switch (peek()) {
case 'x':
case 'X':
shiftChar();
return Token(T_(NUMBER), readHexNumber());
case 'o':
case 'O':
shiftChar();
return Token(T_(NUMBER), readNumber(8, 0));
case 'b':
case 'B':
shiftChar();
return Token(T_(NUMBER), readBinaryNumber());
}
[[fallthrough]];
// Decimal or fixed-point number
case '1': case '1':
case '2': case '2':
case '3': case '3':
@@ -1849,7 +1874,19 @@ static Token yylex_NORMAL() {
} }
} }
if (token.type == T_(ID) && (lexerState->atLineStart || peek() == ':')) // This is a "lexer hack"! We need it to distinguish between label definitions
// (which start with `LABEL`) and macro invocations (which start with `ID`).
//
// If we had one `IDENTIFIER` token, the parser would need to perform "lookahead"
// to determine which rule applies. But since macros need to enter "raw" mode to
// parse their arguments, which may not even be valid tokens in "normal" mode, we
// cannot use lookahead to check for the presence of a `COLON`.
//
// Instead, we have separate `ID` and `LABEL` tokens, lexing as a `LABEL` if a ':'
// character *immediately* follows the identifier. Thus, at the beginning of a line,
// "Label:" and "mac:" are treated as label definitions, but "Label :" and "mac :"
// are treated as macro invocations.
if (token.type == T_(ID) && peek() == ':')
token.type = T_(LABEL); token.type = T_(LABEL);
return token; return token;
@@ -1970,6 +2007,7 @@ backslash:
break; break;
case ' ': case ' ':
case '\t':
case '\r': case '\r':
case '\n': case '\n':
discardLineContinuation(); discardLineContinuation();

View File

@@ -55,10 +55,10 @@ void MacroArgs::appendArg(std::shared_ptr<std::string> arg) {
void MacroArgs::shiftArgs(int32_t count) { void MacroArgs::shiftArgs(int32_t count) {
if (size_t nbArgs = args.size(); 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"); warning(WARNING_MACRO_SHIFT, "Cannot shift macro arguments past their end\n");
shift = nbArgs; 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"); warning(WARNING_MACRO_SHIFT, "Cannot shift macro arguments past their beginning\n");
shift = 0; shift = 0;
} else { } else {

View File

@@ -112,7 +112,7 @@ int main(int argc, char *argv[]) {
// Support SOURCE_DATE_EPOCH for reproducible builds // Support SOURCE_DATE_EPOCH for reproducible builds
// https://reproducible-builds.org/docs/source-date-epoch/ // https://reproducible-builds.org/docs/source-date-epoch/
if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch) if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch)
now = (time_t)strtoul(sourceDateEpoch, nullptr, 0); now = static_cast<time_t>(strtoul(sourceDateEpoch, nullptr, 0));
Defer closeDependFile{[&] { Defer closeDependFile{[&] {
if (dependFile) if (dependFile)
@@ -293,7 +293,7 @@ int main(int argc, char *argv[]) {
break; break;
case 'W': case 'W':
processWarningFlag(musl_optarg); opt_W(musl_optarg);
break; break;
case 'w': case 'w':
@@ -381,8 +381,13 @@ int main(int argc, char *argv[]) {
nbErrors = 1; nbErrors = 1;
sect_CheckUnionClosed(); sect_CheckUnionClosed();
sect_CheckLoadClosed();
sect_CheckSizes(); sect_CheckSizes();
charmap_CheckStack();
opt_CheckStack();
sect_CheckStack();
if (nbErrors != 0) if (nbErrors != 0)
errx("Assembly aborted (%u error%s)!", nbErrors, nbErrors == 1 ? "" : "s"); errx("Assembly aborted (%u error%s)!", nbErrors, nbErrors == 1 ? "" : "s");

View File

@@ -13,8 +13,6 @@
#include "asm/section.hpp" #include "asm/section.hpp"
#include "asm/warning.hpp" #include "asm/warning.hpp"
static constexpr size_t numWarningStates = sizeof(warningStates);
struct OptStackEntry { struct OptStackEntry {
char binary[2]; char binary[2];
char gbgfx[4]; char gbgfx[4];
@@ -22,7 +20,7 @@ struct OptStackEntry {
uint8_t fillByte; uint8_t fillByte;
bool warningsAreErrors; bool warningsAreErrors;
size_t maxRecursionDepth; size_t maxRecursionDepth;
WarningState warningStates[numWarningStates]; Diagnostics warningStates;
}; };
static std::stack<OptStackEntry> stack; static std::stack<OptStackEntry> stack;
@@ -160,7 +158,7 @@ void opt_Push() {
// Both of these pulled from warning.hpp // Both of these pulled from warning.hpp
entry.warningsAreErrors = warningsAreErrors; entry.warningsAreErrors = warningsAreErrors;
memcpy(entry.warningStates, warningStates, numWarningStates); entry.warningStates = warningStates;
entry.maxRecursionDepth = maxRecursionDepth; // Pulled from fstack.h entry.maxRecursionDepth = maxRecursionDepth; // Pulled from fstack.h
@@ -184,5 +182,11 @@ void opt_Pop() {
// opt_W does not apply a whole warning state; it processes one flag string // opt_W does not apply a whole warning state; it processes one flag string
warningsAreErrors = entry.warningsAreErrors; 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

@@ -12,6 +12,7 @@
#include "error.hpp" #include "error.hpp"
#include "helpers.hpp" // assume, Defer #include "helpers.hpp" // assume, Defer
#include "platform.hpp"
#include "asm/charmap.hpp" #include "asm/charmap.hpp"
#include "asm/fstack.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) { static void putLong(uint32_t n, FILE *file) {
uint8_t bytes[] = { uint8_t bytes[] = {
(uint8_t)n, static_cast<uint8_t>(n),
(uint8_t)(n >> 8), static_cast<uint8_t>(n >> 8),
(uint8_t)(n >> 16), static_cast<uint8_t>(n >> 16),
(uint8_t)(n >> 24), static_cast<uint8_t>(n >> 24),
}; };
fwrite(bytes, 1, sizeof(bytes), file); fwrite(bytes, 1, sizeof(bytes), file);
} }
@@ -54,25 +55,25 @@ static void putString(std::string const &s, FILE *file) {
void out_RegisterNode(std::shared_ptr<FileStackNode> node) { void out_RegisterNode(std::shared_ptr<FileStackNode> node) {
// If node is not already registered, register it (and parents), and give it a unique ID // If node is not already registered, register it (and parents), and give it a unique ID
for (; node && node->ID == (uint32_t)-1; node = node->parent) { for (; node && node->ID == UINT32_MAX; node = node->parent) {
node->ID = fileStackNodes.size(); node->ID = fileStackNodes.size();
fileStackNodes.push_front(node); 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) { static uint32_t getSectIDIfAny(Section *sect) {
if (!sect) if (!sect)
return (uint32_t)-1; return UINT32_MAX;
if (auto search = sectionMap.find(sect->name); search != sectionMap.end()) if (auto search = sectionMap.find(sect->name); search != sectionMap.end())
return (uint32_t)(sectionMap.size() - search->second - 1); return static_cast<uint32_t>(sectionMap.size() - search->second - 1);
fatalerror("Unknown section '%s'\n", sect->name.c_str()); fatalerror("Unknown section '%s'\n", sect->name.c_str());
} }
static void writePatch(Patch const &patch, FILE *file) { 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.src->ID, file);
putLong(patch.lineNo, file); putLong(patch.lineNo, file);
@@ -85,7 +86,7 @@ static void writePatch(Patch const &patch, FILE *file) {
} }
static void writeSection(Section const &sect, 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); putString(sect.name, file);
@@ -118,7 +119,7 @@ static void writeSymbol(Symbol const &sym, FILE *file) {
if (!sym.isDefined()) { if (!sym.isDefined()) {
putc(SYMTYPE_IMPORT, file); putc(SYMTYPE_IMPORT, file);
} else { } else {
assume(sym.src->ID != (uint32_t)-1); assume(sym.src->ID != UINT32_MAX);
putc(sym.isExported ? SYMTYPE_EXPORT : SYMTYPE_LOCAL, file); putc(sym.isExported ? SYMTYPE_EXPORT : SYMTYPE_LOCAL, file);
putLong(sym.src->ID, file); putLong(sym.src->ID, file);
@@ -130,7 +131,7 @@ static void writeSymbol(Symbol const &sym, FILE *file) {
static void registerUnregisteredSymbol(Symbol &sym) { static void registerUnregisteredSymbol(Symbol &sym) {
// Check for `sym.src`, to skip any built-in symbol from rgbasm // 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 sym.ID = objectSymbols.size(); // Set the symbol's ID within the object file
objectSymbols.push_back(&sym); objectSymbols.push_back(&sym);
out_RegisterNode(sym.src); out_RegisterNode(sym.src);
@@ -287,7 +288,7 @@ static void writeAssert(Assertion &assert, FILE *file) {
} }
static void writeFileStackNode(FileStackNode const &node, FILE *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); putLong(node.lineNo, file);
putc(node.type, file); putc(node.type, file);
if (node.type != NODE_REPT) { if (node.type != NODE_REPT) {
@@ -311,7 +312,8 @@ void out_WriteObject() {
file = fopen(objectFileName.c_str(), "wb"); file = fopen(objectFileName.c_str(), "wb");
} else { } else {
objectFileName = "<stdout>"; 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()); err("Failed to open object file '%s'", objectFileName.c_str());
@@ -503,7 +505,8 @@ void out_WriteState(std::string name, std::vector<StateFeature> const &features)
file = fopen(name.c_str(), "wb"); file = fopen(name.c_str(), "wb");
} else { } else {
name = "<stdout>"; 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()); err("Failed to open state file '%s'", name.c_str());

View File

@@ -103,7 +103,7 @@
/******************** Tokens ********************/ /******************** Tokens ********************/
%token YYEOF 0 "end of file" %token YYEOF 0 "end of file"
%token NEWLINE "newline" %token NEWLINE "end of line"
%token EOB "end of buffer" %token EOB "end of buffer"
// General punctuation // General punctuation
@@ -140,7 +140,7 @@
%left OP_SHL OP_SHR OP_USHR %left OP_SHL OP_SHR OP_USHR
%left OP_MUL OP_DIV OP_MOD %left OP_MUL OP_DIV OP_MOD
%precedence NEG // applies to unary OP_LOGICNOT, OP_ADD, OP_SUB, OP_NOT %precedence NEG // applies to unary OP_LOGICNOT, OP_ADD, OP_SUB, OP_NOT
%left OP_EXP %right OP_EXP
// Assignment operators (only for variables) // Assignment operators (only for variables)
%token POP_EQUAL "=" %token POP_EQUAL "="
@@ -335,7 +335,7 @@
%type <Expression> reloc_16bit_no_str %type <Expression> reloc_16bit_no_str
// Constant numbers // Constant numbers
%type <int32_t> const %type <int32_t> iconst
%type <int32_t> const_no_str %type <int32_t> const_no_str
%type <int32_t> uconst %type <int32_t> uconst
// Constant numbers used only in specific contexts // Constant numbers used only in specific contexts
@@ -404,6 +404,14 @@ asm_file: lines;
lines: lines:
%empty %empty
| lines diff_mark line | lines diff_mark line
// Continue parsing the next line on a syntax error
| error {
lexer_SetMode(LEXER_NORMAL);
lexer_ToggleStringExpansion(true);
} endofline {
fstk_StopRept();
yyerrok;
}
; ;
diff_mark: diff_mark:
@@ -425,30 +433,6 @@ diff_mark:
line: line:
plain_directive endofline plain_directive endofline
| line_directive // Directives that manage newlines themselves | line_directive // Directives that manage newlines themselves
// Continue parsing the next line on a syntax error
| error {
lexer_SetMode(LEXER_NORMAL);
lexer_ToggleStringExpansion(true);
} endofline {
fstk_StopRept();
yyerrok;
}
// Hint about unindented macros parsed as labels
| LABEL error {
lexer_SetMode(LEXER_NORMAL);
lexer_ToggleStringExpansion(true);
} endofline {
Symbol *macro = sym_FindExactSymbol($1);
if (macro && macro->type == SYM_MACRO)
fprintf(
stderr,
" To invoke `%s` as a macro it must be indented\n",
$1.c_str()
);
fstk_StopRept();
yyerrok;
}
; ;
endofline: NEWLINE | EOB; endofline: NEWLINE | EOB;
@@ -470,7 +454,7 @@ line_directive:
; ;
if: if:
POP_IF const NEWLINE { POP_IF iconst NEWLINE {
lexer_IncIFDepth(); lexer_IncIFDepth();
if ($2) if ($2)
@@ -481,7 +465,7 @@ if:
; ;
elif: elif:
POP_ELIF const NEWLINE { POP_ELIF iconst NEWLINE {
if (lexer_GetIFDepth() == 0) if (lexer_GetIFDepth() == 0)
fatalerror("Found ELIF outside of an IF construct\n"); fatalerror("Found ELIF outside of an IF construct\n");
@@ -716,14 +700,14 @@ align_spec:
$$.alignOfs = 0; $$.alignOfs = 0;
} }
} }
| uconst COMMA const { | uconst COMMA iconst {
if ($1 > 16) { if ($1 > 16) {
::error("Alignment must be between 0 and 16, not %u\n", $1); ::error("Alignment must be between 0 and 16, not %u\n", $1);
$$.alignment = $$.alignOfs = 0; $$.alignment = $$.alignOfs = 0;
} else if ($3 <= -(1 << $1) || $3 >= 1 << $1) { } else if ($3 <= -(1 << $1) || $3 >= 1 << $1) {
::error( ::error(
"The absolute alignment offset (%" PRIu32 ") must be less than alignment size (%d)\n", "The absolute alignment offset (%" PRIu32 ") must be less than alignment size (%d)\n",
(uint32_t)($3 < 0 ? -$3 : $3), static_cast<uint32_t>($3 < 0 ? -$3 : $3),
1 << $1 1 << $1
); );
$$.alignment = $$.alignOfs = 0; $$.alignment = $$.alignOfs = 0;
@@ -833,11 +817,11 @@ assert:
failAssertMsg($2, $5); failAssertMsg($2, $5);
} }
} }
| POP_STATIC_ASSERT assert_type const { | POP_STATIC_ASSERT assert_type iconst {
if ($3 == 0) if ($3 == 0)
failAssert($2); failAssert($2);
} }
| POP_STATIC_ASSERT assert_type const COMMA string { | POP_STATIC_ASSERT assert_type iconst COMMA string {
if ($3 == 0) if ($3 == 0)
failAssertMsg($2, $5); failAssertMsg($2, $5);
} }
@@ -857,7 +841,7 @@ shift_const:
%empty { %empty {
$$ = 1; $$ = 1;
} }
| const | iconst
; ;
load: load:
@@ -865,7 +849,7 @@ load:
sect_SetLoadSection($3, $5, $6, $7, $2); sect_SetLoadSection($3, $5, $6, $7, $2);
} }
| POP_ENDL { | POP_ENDL {
sect_EndLoadSection(); sect_EndLoadSection(nullptr);
} }
; ;
@@ -894,17 +878,17 @@ capture_rept:
; ;
for_args: for_args:
const { iconst {
$$.start = 0; $$.start = 0;
$$.stop = $1; $$.stop = $1;
$$.step = 1; $$.step = 1;
} }
| const COMMA const { | iconst COMMA iconst {
$$.start = $1; $$.start = $1;
$$.stop = $3; $$.stop = $3;
$$.step = 1; $$.step = 1;
} }
| const COMMA const COMMA const { | iconst COMMA iconst COMMA iconst {
$$.start = $1; $$.start = $1;
$$.stop = $3; $$.stop = $3;
$$.step = $5; $$.step = $5;
@@ -1025,33 +1009,33 @@ dl:
; ;
def_equ: def_equ:
def_id POP_EQU const { def_id POP_EQU iconst {
$$ = std::move($1); $$ = std::move($1);
sym_AddEqu($$, $3); sym_AddEqu($$, $3);
} }
; ;
redef_equ: redef_equ:
redef_id POP_EQU const { redef_id POP_EQU iconst {
$$ = std::move($1); $$ = std::move($1);
sym_RedefEqu($$, $3); sym_RedefEqu($$, $3);
} }
; ;
def_set: def_set:
def_id POP_EQUAL const { def_id POP_EQUAL iconst {
$$ = std::move($1); $$ = std::move($1);
sym_AddVar($$, $3); sym_AddVar($$, $3);
} }
| redef_id POP_EQUAL const { | redef_id POP_EQUAL iconst {
$$ = std::move($1); $$ = std::move($1);
sym_AddVar($$, $3); sym_AddVar($$, $3);
} }
| def_id compound_eq const { | def_id compound_eq iconst {
$$ = std::move($1); $$ = std::move($1);
compoundAssignment($$, $2, $3); compoundAssignment($$, $2, $3);
} }
| redef_id compound_eq const { | redef_id compound_eq iconst {
$$ = std::move($1); $$ = std::move($1);
compoundAssignment($$, $2, $3); compoundAssignment($$, $2, $3);
} }
@@ -1151,12 +1135,12 @@ incbin:
if (failedOnMissingInclude) if (failedOnMissingInclude)
YYACCEPT; YYACCEPT;
} }
| POP_INCBIN string COMMA const { | POP_INCBIN string COMMA iconst {
sect_BinaryFile($2, $4); sect_BinaryFile($2, $4);
if (failedOnMissingInclude) if (failedOnMissingInclude)
YYACCEPT; YYACCEPT;
} }
| POP_INCBIN string COMMA const COMMA const { | POP_INCBIN string COMMA iconst COMMA iconst {
sect_BinaryFileSlice($2, $4, $6); sect_BinaryFileSlice($2, $4, $6);
if (failedOnMissingInclude) if (failedOnMissingInclude)
YYACCEPT; YYACCEPT;
@@ -1170,10 +1154,10 @@ charmap:
; ;
charmap_args: charmap_args:
const { iconst {
$$.push_back(std::move($1)); $$.push_back(std::move($1));
} }
| charmap_args COMMA const { | charmap_args COMMA iconst {
$$ = std::move($1); $$ = std::move($1);
$$.push_back(std::move($3)); $$.push_back(std::move($3));
} }
@@ -1242,7 +1226,7 @@ print_expr:
; ;
bit_const: bit_const:
const { iconst {
$$ = $1; $$ = $1;
if ($$ < 0 || $$ > 7) { if ($$ < 0 || $$ > 7) {
::error("Bit number must be between 0 and 7, not %" PRId32 "\n", $$); ::error("Bit number must be between 0 and 7, not %" PRId32 "\n", $$);
@@ -1464,49 +1448,49 @@ relocexpr_no_str:
$$.makeNumber(sym_FindScopedValidSymbol($4) != nullptr); $$.makeNumber(sym_FindScopedValidSymbol($4) != nullptr);
lexer_ToggleStringExpansion(true); lexer_ToggleStringExpansion(true);
} }
| OP_ROUND LPAREN const precision_arg RPAREN { | OP_ROUND LPAREN iconst precision_arg RPAREN {
$$.makeNumber(fix_Round($3, $4)); $$.makeNumber(fix_Round($3, $4));
} }
| OP_CEIL LPAREN const precision_arg RPAREN { | OP_CEIL LPAREN iconst precision_arg RPAREN {
$$.makeNumber(fix_Ceil($3, $4)); $$.makeNumber(fix_Ceil($3, $4));
} }
| OP_FLOOR LPAREN const precision_arg RPAREN { | OP_FLOOR LPAREN iconst precision_arg RPAREN {
$$.makeNumber(fix_Floor($3, $4)); $$.makeNumber(fix_Floor($3, $4));
} }
| OP_FDIV LPAREN const COMMA const precision_arg RPAREN { | OP_FDIV LPAREN iconst COMMA iconst precision_arg RPAREN {
$$.makeNumber(fix_Div($3, $5, $6)); $$.makeNumber(fix_Div($3, $5, $6));
} }
| OP_FMUL LPAREN const COMMA const precision_arg RPAREN { | OP_FMUL LPAREN iconst COMMA iconst precision_arg RPAREN {
$$.makeNumber(fix_Mul($3, $5, $6)); $$.makeNumber(fix_Mul($3, $5, $6));
} }
| OP_FMOD LPAREN const COMMA const precision_arg RPAREN { | OP_FMOD LPAREN iconst COMMA iconst precision_arg RPAREN {
$$.makeNumber(fix_Mod($3, $5, $6)); $$.makeNumber(fix_Mod($3, $5, $6));
} }
| OP_POW LPAREN const COMMA const precision_arg RPAREN { | OP_POW LPAREN iconst COMMA iconst precision_arg RPAREN {
$$.makeNumber(fix_Pow($3, $5, $6)); $$.makeNumber(fix_Pow($3, $5, $6));
} }
| OP_LOG LPAREN const COMMA const precision_arg RPAREN { | OP_LOG LPAREN iconst COMMA iconst precision_arg RPAREN {
$$.makeNumber(fix_Log($3, $5, $6)); $$.makeNumber(fix_Log($3, $5, $6));
} }
| OP_SIN LPAREN const precision_arg RPAREN { | OP_SIN LPAREN iconst precision_arg RPAREN {
$$.makeNumber(fix_Sin($3, $4)); $$.makeNumber(fix_Sin($3, $4));
} }
| OP_COS LPAREN const precision_arg RPAREN { | OP_COS LPAREN iconst precision_arg RPAREN {
$$.makeNumber(fix_Cos($3, $4)); $$.makeNumber(fix_Cos($3, $4));
} }
| OP_TAN LPAREN const precision_arg RPAREN { | OP_TAN LPAREN iconst precision_arg RPAREN {
$$.makeNumber(fix_Tan($3, $4)); $$.makeNumber(fix_Tan($3, $4));
} }
| OP_ASIN LPAREN const precision_arg RPAREN { | OP_ASIN LPAREN iconst precision_arg RPAREN {
$$.makeNumber(fix_ASin($3, $4)); $$.makeNumber(fix_ASin($3, $4));
} }
| OP_ACOS LPAREN const precision_arg RPAREN { | OP_ACOS LPAREN iconst precision_arg RPAREN {
$$.makeNumber(fix_ACos($3, $4)); $$.makeNumber(fix_ACos($3, $4));
} }
| OP_ATAN LPAREN const precision_arg RPAREN { | OP_ATAN LPAREN iconst precision_arg RPAREN {
$$.makeNumber(fix_ATan($3, $4)); $$.makeNumber(fix_ATan($3, $4));
} }
| OP_ATAN2 LPAREN const COMMA const precision_arg RPAREN { | OP_ATAN2 LPAREN iconst COMMA iconst precision_arg RPAREN {
$$.makeNumber(fix_ATan2($3, $5, $6)); $$.makeNumber(fix_ATan2($3, $5, $6));
} }
| OP_STRCMP LPAREN string COMMA string RPAREN { | OP_STRCMP LPAREN string COMMA string RPAREN {
@@ -1537,14 +1521,14 @@ relocexpr_no_str:
; ;
uconst: uconst:
const { iconst {
$$ = $1; $$ = $1;
if ($$ < 0) if ($$ < 0)
fatalerror("Constant must not be negative: %d\n", $$); fatalerror("Constant must not be negative: %d\n", $$);
} }
; ;
const: iconst:
relocexpr { relocexpr {
$$ = $1.getConstVal(); $$ = $1.getConstVal();
} }
@@ -1560,7 +1544,7 @@ precision_arg:
%empty { %empty {
$$ = fix_Precision(); $$ = fix_Precision();
} }
| COMMA const { | COMMA iconst {
$$ = $2; $$ = $2;
if ($$ < 1 || $$ > 31) { if ($$ < 1 || $$ > 31) {
::error("Fixed-point precision must be between 1 and 31, not %" PRId32 "\n", $$); ::error("Fixed-point precision must be between 1 and 31, not %" PRId32 "\n", $$);
@@ -1573,19 +1557,19 @@ string:
STRING { STRING {
$$ = std::move($1); $$ = std::move($1);
} }
| OP_STRSUB LPAREN string COMMA const COMMA uconst RPAREN { | OP_STRSUB LPAREN string COMMA iconst COMMA uconst RPAREN {
size_t len = strlenUTF8($3); size_t len = strlenUTF8($3);
uint32_t pos = adjustNegativePos($5, len, "STRSUB"); uint32_t pos = adjustNegativePos($5, len, "STRSUB");
$$ = strsubUTF8($3, pos, $7); $$ = strsubUTF8($3, pos, $7);
} }
| OP_STRSUB LPAREN string COMMA const RPAREN { | OP_STRSUB LPAREN string COMMA iconst RPAREN {
size_t len = strlenUTF8($3); size_t len = strlenUTF8($3);
uint32_t pos = adjustNegativePos($5, len, "STRSUB"); uint32_t pos = adjustNegativePos($5, len, "STRSUB");
$$ = strsubUTF8($3, pos, pos > len ? 0 : len + 1 - pos); $$ = strsubUTF8($3, pos, pos > len ? 0 : len + 1 - pos);
} }
| OP_CHARSUB LPAREN string COMMA const RPAREN { | OP_CHARSUB LPAREN string COMMA iconst RPAREN {
size_t len = charlenUTF8($3); size_t len = charlenUTF8($3);
uint32_t pos = adjustNegativePos($5, len, "CHARSUB"); uint32_t pos = adjustNegativePos($5, len, "CHARSUB");
@@ -1652,7 +1636,7 @@ strfmt_va_args:
%empty {} %empty {}
| strfmt_va_args COMMA const_no_str { | strfmt_va_args COMMA const_no_str {
$$ = std::move($1); $$ = std::move($1);
$$.args.push_back((uint32_t)$3); $$.args.push_back(static_cast<uint32_t>($3));
} }
| strfmt_va_args COMMA string { | strfmt_va_args COMMA string {
$$ = std::move($1); $$ = std::move($1);
@@ -1718,7 +1702,7 @@ sect_org:
} }
| LBRACK uconst RBRACK { | LBRACK uconst RBRACK {
$$ = $2; $$ = $2;
if ($$ < 0 || $$ >= 0x10000) { if ($$ < 0 || $$ > 0xFFFF) {
::error("Address $%x is not 16-bit\n", $$); ::error("Address $%x is not 16-bit\n", $$);
$$ = -1; $$ = -1;
} }
@@ -1768,8 +1752,8 @@ cpu_command:
| z80_jr | z80_jr
| z80_ld | z80_ld
| z80_ldd | z80_ldd
| z80_ldh
| z80_ldi | z80_ldi
| z80_ldio
| z80_nop | z80_nop
| z80_or | z80_or
| z80_pop | z80_pop
@@ -1963,15 +1947,25 @@ z80_ldd:
} }
; ;
z80_ldio: z80_ldh:
Z80_LDH MODE_A COMMA op_mem_ind { Z80_LDH MODE_A COMMA op_mem_ind {
$4.makeCheckHRAM(); if ($4.makeCheckHRAM()) {
warning(
WARNING_OBSOLETE,
"LDH is deprecated with values from $00 to $FF; use $FF00 to $FFFF\n"
);
}
sect_ConstByte(0xF0); sect_ConstByte(0xF0);
sect_RelByte($4, 1); sect_RelByte($4, 1);
} }
| Z80_LDH op_mem_ind COMMA MODE_A { | Z80_LDH op_mem_ind COMMA MODE_A {
$2.makeCheckHRAM(); if ($2.makeCheckHRAM()) {
warning(
WARNING_OBSOLETE,
"LDH is deprecated with values from $00 to $FF; use $FF00 to $FFFF\n"
);
}
sect_ConstByte(0xE0); sect_ConstByte(0xE0);
sect_RelByte($2, 1); sect_RelByte($2, 1);
@@ -1987,7 +1981,7 @@ z80_ldio:
c_ind: c_ind:
LBRACK MODE_C RBRACK LBRACK MODE_C RBRACK
| LBRACK relocexpr OP_ADD MODE_C RBRACK { | LBRACK relocexpr OP_ADD MODE_C RBRACK {
// This has to use `relocexpr`, not `const`, to avoid a shift/reduce conflict // This has to use `relocexpr`, not `iconst`, to avoid a shift/reduce conflict
if ($2.getConstVal() != 0xFF00) if ($2.getConstVal() != 0xFF00)
::error("Base value must be equal to $FF00 for $FF00+C\n"); ::error("Base value must be equal to $FF00 for $FF00+C\n");
} }
@@ -2038,6 +2032,7 @@ z80_ld_mem:
z80_ld_c_ind: z80_ld_c_ind:
Z80_LD c_ind COMMA MODE_A { Z80_LD c_ind COMMA MODE_A {
warning(WARNING_OBSOLETE, "LD [C], A is deprecated; use LDH [C], A\n");
sect_ConstByte(0xE2); sect_ConstByte(0xE2);
} }
; ;
@@ -2070,6 +2065,7 @@ z80_ld_a:
sect_ConstByte(0x40 | ($2 << 3) | $4); sect_ConstByte(0x40 | ($2 << 3) | $4);
} }
| Z80_LD reg_a COMMA c_ind { | Z80_LD reg_a COMMA c_ind {
warning(WARNING_OBSOLETE, "LD A, [C] is deprecated; use LDH A, [C]\n");
sect_ConstByte(0xF2); sect_ConstByte(0xF2);
} }
| Z80_LD reg_a COMMA reg_rr { | Z80_LD reg_a COMMA reg_rr {
@@ -2472,7 +2468,7 @@ static uint32_t strToNum(std::vector<int32_t> const &s) {
if (length == 1) { if (length == 1) {
// The string is a single character with a single value, // The string is a single character with a single value,
// which can be used directly as a number. // which can be used directly as a number.
return (uint32_t)s[0]; return static_cast<uint32_t>(s[0]);
} }
warning(WARNING_OBSOLETE, "Treating multi-unit strings as numbers is deprecated\n"); warning(WARNING_OBSOLETE, "Treating multi-unit strings as numbers is deprecated\n");
@@ -2617,7 +2613,7 @@ static uint32_t adjustNegativePos(int32_t pos, size_t len, char const *functionN
warning(WARNING_BUILTIN_ARG, "%s: Position starts at 1\n", functionName); warning(WARNING_BUILTIN_ARG, "%s: Position starts at 1\n", functionName);
pos = 1; pos = 1;
} }
return (uint32_t)pos; return static_cast<uint32_t>(pos);
} }
static std::string strrpl(std::string_view str, std::string const &old, std::string const &rep) { static std::string strrpl(std::string_view str, std::string const &old, std::string const &rep) {

View File

@@ -48,7 +48,7 @@ int32_t Expression::getConstVal() const {
Symbol const *Expression::symbolOf() const { Symbol const *Expression::symbolOf() const {
if (!isSymbol) if (!isSymbol)
return nullptr; return nullptr;
return sym_FindScopedSymbol((char const *)&rpn[1]); return sym_FindScopedSymbol(reinterpret_cast<char const *>(&rpn[1]));
} }
bool Expression::isDiffConstant(Symbol const *sym) const { bool Expression::isDiffConstant(Symbol const *sym) const {
@@ -65,7 +65,7 @@ bool Expression::isDiffConstant(Symbol const *sym) const {
void Expression::makeNumber(uint32_t value) { void Expression::makeNumber(uint32_t value) {
clear(); clear();
data = (int32_t)value; data = static_cast<int32_t>(value);
} }
void Expression::makeSymbol(std::string const &symName) { void Expression::makeSymbol(std::string const &symName) {
@@ -89,7 +89,7 @@ void Expression::makeSymbol(std::string const &symName) {
*ptr++ = RPN_SYM; *ptr++ = RPN_SYM;
memcpy(ptr, sym->name.c_str(), nameLen); memcpy(ptr, sym->name.c_str(), nameLen);
} else { } else {
data = (int32_t)sym_GetConstantValue(symName); data = static_cast<int32_t>(sym_GetConstantValue(symName));
} }
} }
@@ -100,12 +100,12 @@ void Expression::makeBankSymbol(std::string const &symName) {
if (!currentSection) { if (!currentSection) {
error("PC has no bank outside of a section\n"); error("PC has no bank outside of a section\n");
data = 1; data = 1;
} else if (currentSection->bank == (uint32_t)-1) { } else if (currentSection->bank == UINT32_MAX) {
data = "Current section's bank is not known"; data = "Current section's bank is not known";
*reserveSpace(1) = RPN_BANK_SELF; *reserveSpace(1) = RPN_BANK_SELF;
} else { } else {
data = (int32_t)currentSection->bank; data = static_cast<int32_t>(currentSection->bank);
} }
return; return;
} else if (sym && !sym->isLabel()) { } else if (sym && !sym->isLabel()) {
@@ -115,9 +115,9 @@ void Expression::makeBankSymbol(std::string const &symName) {
sym = sym_Ref(symName); sym = sym_Ref(symName);
assume(sym); // If the symbol didn't exist, it should have been created 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 // Symbol's section is known and bank is fixed
data = (int32_t)sym->getSection()->bank; data = static_cast<int32_t>(sym->getSection()->bank);
} else { } else {
data = sym_IsPurgedScoped(symName) data = sym_IsPurgedScoped(symName)
? "\""s + symName + "\"'s bank is not known; it was purged" ? "\""s + symName + "\"'s bank is not known; it was purged"
@@ -135,8 +135,8 @@ void Expression::makeBankSymbol(std::string const &symName) {
void Expression::makeBankSection(std::string const &sectName) { void Expression::makeBankSection(std::string const &sectName) {
clear(); clear();
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->bank != (uint32_t)-1) { if (Section *sect = sect_FindSectionByName(sectName); sect && sect->bank != UINT32_MAX) {
data = (int32_t)sect->bank; data = static_cast<int32_t>(sect->bank);
} else { } else {
data = "Section \""s + sectName + "\"'s bank is not known"; data = "Section \""s + sectName + "\"'s bank is not known";
@@ -151,7 +151,7 @@ void Expression::makeBankSection(std::string const &sectName) {
void Expression::makeSizeOfSection(std::string const &sectName) { void Expression::makeSizeOfSection(std::string const &sectName) {
clear(); clear();
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->isSizeKnown()) { if (Section *sect = sect_FindSectionByName(sectName); sect && sect->isSizeKnown()) {
data = (int32_t)sect->size; data = static_cast<int32_t>(sect->size);
} else { } else {
data = "Section \""s + sectName + "\"'s size is not known"; data = "Section \""s + sectName + "\"'s size is not known";
@@ -165,8 +165,8 @@ void Expression::makeSizeOfSection(std::string const &sectName) {
void Expression::makeStartOfSection(std::string const &sectName) { void Expression::makeStartOfSection(std::string const &sectName) {
clear(); clear();
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->org != (uint32_t)-1) { if (Section *sect = sect_FindSectionByName(sectName); sect && sect->org != UINT32_MAX) {
data = (int32_t)sect->org; data = static_cast<int32_t>(sect->org);
} else { } else {
data = "Section \""s + sectName + "\"'s start is not known"; data = "Section \""s + sectName + "\"'s start is not known";
@@ -216,9 +216,9 @@ static bool tryConstLogNot(Expression const &expr) {
Section const &sect = *sym->getSection(); Section const &sect = *sym->getSection();
int32_t unknownBits = (1 << 16) - (1 << sect.align); 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) // 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 symbolOfs = sym->getValue() + 1;
int32_t knownBits = (symbolOfs + sect.alignOfs) & ~unknownBits; int32_t knownBits = (symbolOfs + sect.alignOfs) & ~unknownBits;
@@ -243,9 +243,9 @@ static int32_t tryConstLow(Expression const &expr) {
if (sect.align < 8) if (sect.align < 8)
return -1; 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) // 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 symbolOfs = sym->getValue() + 1;
return (symbolOfs + sect.alignOfs) & 0xFF; return (symbolOfs + sect.alignOfs) & 0xFF;
@@ -284,9 +284,9 @@ static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) {
if (int32_t unknownBits = (1 << 16) - (1 << sect.align); (unknownBits & mask) != 0) if (int32_t unknownBits = (1 << 16) - (1 << sect.align); (unknownBits & mask) != 0)
return -1; return -1;
// `sym.getValue()` attempts to add the section's address, but that's "-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) // 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 symbolOfs = sym.getValue() + 1;
return (symbolOfs + sect.alignOfs) & mask; return (symbolOfs + sect.alignOfs) & mask;
@@ -298,10 +298,11 @@ void Expression::makeUnaryOp(RPNCommand op, Expression &&src) {
if (src.isKnown()) { if (src.isKnown()) {
// If the expressions is known, just compute the value // If the expressions is known, just compute the value
int32_t val = src.value(); int32_t val = src.value();
uint32_t uval = static_cast<uint32_t>(val);
switch (op) { switch (op) {
case RPN_NEG: case RPN_NEG:
data = (int32_t) - (uint32_t)val; data = static_cast<int32_t>(-uval);
break; break;
case RPN_NOT: case RPN_NOT:
data = ~val; data = ~val;
@@ -310,16 +311,16 @@ void Expression::makeUnaryOp(RPNCommand op, Expression &&src) {
data = !val; data = !val;
break; break;
case RPN_HIGH: case RPN_HIGH:
data = (int32_t)((uint32_t)val >> 8 & 0xFF); data = static_cast<int32_t>(uval >> 8 & 0xFF);
break; break;
case RPN_LOW: case RPN_LOW:
data = val & 0xFF; data = val & 0xFF;
break; break;
case RPN_BITWIDTH: case RPN_BITWIDTH:
data = val != 0 ? 32 - clz((uint32_t)val) : 0; data = val != 0 ? 32 - clz(uval) : 0;
break; break;
case RPN_TZCOUNT: case RPN_TZCOUNT:
data = val != 0 ? ctz((uint32_t)val) : 32; data = val != 0 ? ctz(uval) : 32;
break; break;
case RPN_LOGOR: case RPN_LOGOR:
@@ -374,6 +375,7 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
if (src1.isKnown() && src2.isKnown()) { if (src1.isKnown() && src2.isKnown()) {
// If both expressions are known, just compute the value // If both expressions are known, just compute the value
int32_t lval = src1.value(), rval = src2.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) { switch (op) {
case RPN_LOGOR: case RPN_LOGOR:
@@ -401,10 +403,10 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
data = lval != rval; data = lval != rval;
break; break;
case RPN_ADD: case RPN_ADD:
data = (int32_t)((uint32_t)lval + (uint32_t)rval); data = static_cast<int32_t>(ulval + urval);
break; break;
case RPN_SUB: case RPN_SUB:
data = (int32_t)((uint32_t)lval - (uint32_t)rval); data = static_cast<int32_t>(ulval - urval);
break; break;
case RPN_XOR: case RPN_XOR:
data = lval ^ rval; data = lval ^ rval;
@@ -452,7 +454,7 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
data = op_shift_right_unsigned(lval, rval); data = op_shift_right_unsigned(lval, rval);
break; break;
case RPN_MUL: case RPN_MUL:
data = (int32_t)((uint32_t)lval * (uint32_t)rval); data = static_cast<int32_t>(ulval * urval);
break; break;
case RPN_DIV: case RPN_DIV:
if (rval == 0) if (rval == 0)
@@ -522,10 +524,10 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
uint32_t lval = src1.value(); uint32_t lval = src1.value();
uint8_t bytes[] = { uint8_t bytes[] = {
RPN_CONST, RPN_CONST,
(uint8_t)lval, static_cast<uint8_t>(lval),
(uint8_t)(lval >> 8), static_cast<uint8_t>(lval >> 8),
(uint8_t)(lval >> 16), static_cast<uint8_t>(lval >> 16),
(uint8_t)(lval >> 24), static_cast<uint8_t>(lval >> 24),
}; };
rpn.clear(); rpn.clear();
rpnPatchSize = 0; rpnPatchSize = 0;
@@ -546,10 +548,10 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
uint32_t rval = src2.value(); uint32_t rval = src2.value();
uint8_t bytes[] = { uint8_t bytes[] = {
RPN_CONST, RPN_CONST,
(uint8_t)rval, static_cast<uint8_t>(rval),
(uint8_t)(rval >> 8), static_cast<uint8_t>(rval >> 8),
(uint8_t)(rval >> 16), static_cast<uint8_t>(rval >> 16),
(uint8_t)(rval >> 24), static_cast<uint8_t>(rval >> 24),
}; };
uint8_t *ptr = reserveSpace(sizeof(bytes) + 1, sizeof(bytes) + 1); uint8_t *ptr = reserveSpace(sizeof(bytes) + 1, sizeof(bytes) + 1);
memcpy(ptr, bytes, sizeof(bytes)); memcpy(ptr, bytes, sizeof(bytes));
@@ -566,16 +568,20 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
} }
} }
void Expression::makeCheckHRAM() { bool Expression::makeCheckHRAM() {
isSymbol = false; isSymbol = false;
if (!isKnown()) { if (!isKnown()) {
*reserveSpace(1) = RPN_HRAM; *reserveSpace(1) = RPN_HRAM;
} else if (int32_t val = value(); val >= 0xFF00 && val <= 0xFFFF) { } else if (int32_t val = value(); val >= 0xFF00 && val <= 0xFFFF) {
// That range is valid, but only keep the lower byte // That range is valid, but only keep the lower byte
data = val & 0xFF; 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); error("Source address $%" PRIx32 " not between $FF00 to $FFFF\n", val);
} }
return false;
} }
void Expression::makeCheckRST() { void Expression::makeCheckRST() {
@@ -584,9 +590,6 @@ void Expression::makeCheckRST() {
} else if (int32_t val = value(); val & ~0x38) { } else if (int32_t val = value(); val & ~0x38) {
// A valid RST address must be masked with 0x38 // A valid RST address must be masked with 0x38
error("Invalid address $%" PRIx32 " for RST\n", val); error("Invalid address $%" PRIx32 " for RST\n", val);
} else {
// The target is in the "0x38" bits, all other bits are set
data = val | 0xC7;
} }
} }

View File

@@ -109,9 +109,9 @@ static unsigned int mergeSectUnion(
if (sect_HasData(type)) if (sect_HasData(type))
sectError("Cannot declare ROM sections as UNION\n"); sectError("Cannot declare ROM sections as UNION\n");
if (org != (uint32_t)-1) { if (org != UINT32_MAX) {
// If both are fixed, they must be the same // If both are fixed, they must be the same
if (sect.org != (uint32_t)-1 && sect.org != org) if (sect.org != UINT32_MAX && sect.org != org)
sectError( sectError(
"Section already declared as fixed at different address $%04" PRIx32 "\n", sect.org "Section already declared as fixed at different address $%04" PRIx32 "\n", sect.org
); );
@@ -127,7 +127,7 @@ static unsigned int mergeSectUnion(
} else if (alignment != 0) { } else if (alignment != 0) {
// Make sure any fixed address given is compatible // Make sure any fixed address given is compatible
if (sect.org != (uint32_t)-1) { if (sect.org != UINT32_MAX) {
if ((sect.org - alignOffset) & mask(alignment)) if ((sect.org - alignOffset) & mask(alignment))
sectError( sectError(
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n", "Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
@@ -159,11 +159,11 @@ static unsigned int
// Fragments only need "compatible" constraints, and they end up with the strictest // Fragments only need "compatible" constraints, and they end up with the strictest
// combination of both. // combination of both.
// The merging is however performed at the *end* of the original section! // The merging is however performed at the *end* of the original section!
if (org != (uint32_t)-1) { if (org != UINT32_MAX) {
uint16_t curOrg = org - sect.size; uint16_t curOrg = org - sect.size;
// If both are fixed, they must be the same // If both are fixed, they must be the same
if (sect.org != (uint32_t)-1 && sect.org != curOrg) if (sect.org != UINT32_MAX && sect.org != curOrg)
sectError( sectError(
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n", "Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
sect.org sect.org
@@ -185,7 +185,7 @@ static unsigned int
curOfs += 1U << alignment; curOfs += 1U << alignment;
// Make sure any fixed address given is compatible // Make sure any fixed address given is compatible
if (sect.org != (uint32_t)-1) { if (sect.org != UINT32_MAX) {
if ((sect.org - curOfs) & mask(alignment)) if ((sect.org - curOfs) & mask(alignment))
sectError( sectError(
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n", "Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
@@ -238,10 +238,10 @@ static void mergeSections(
// Common checks // Common checks
// If the section's bank is unspecified, override it // If the section's bank is unspecified, override it
if (sect.bank == (uint32_t)-1) if (sect.bank == UINT32_MAX)
sect.bank = bank; sect.bank = bank;
// If both specify a bank, it must be the same one // If both specify a bank, it must be the same one
else if (bank != (uint32_t)-1 && sect.bank != bank) else if (bank != UINT32_MAX && sect.bank != bank)
sectError("Section already declared with different bank %" PRIu32 "\n", sect.bank); sectError("Section already declared with different bank %" PRIu32 "\n", sect.bank);
break; break;
@@ -312,7 +312,7 @@ static Section *getSection(
// First, validate parameters, and normalize them if applicable // 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 if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM && type != SECTTYPE_SRAM
&& type != SECTTYPE_WRAMX) && type != SECTTYPE_WRAMX)
error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n"); error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n");
@@ -338,7 +338,7 @@ static Section *getSection(
alignOffset = 0; alignOffset = 0;
} }
if (org != (uint32_t)-1) { if (org != UINT32_MAX) {
if (org < sectionTypeInfo[type].startAddr || org > endaddr(type)) if (org < sectionTypeInfo[type].startAddr || org > endaddr(type))
error( error(
"Section \"%s\"'s fixed address $%04" PRIx32 " is outside of range [$%04" PRIx16 "Section \"%s\"'s fixed address $%04" PRIx32 " is outside of range [$%04" PRIx16
@@ -358,7 +358,7 @@ static Section *getSection(
// It doesn't make sense to have both alignment and org set // It doesn't make sense to have both alignment and org set
uint32_t mask = mask(alignment); uint32_t mask = mask(alignment);
if (org != (uint32_t)-1) { if (org != UINT32_MAX) {
if ((org - alignOffset) & mask) if ((org - alignOffset) & mask)
error("Section \"%s\"'s fixed address doesn't match its alignment\n", name.c_str()); error("Section \"%s\"'s fixed address doesn't match its alignment\n", name.c_str());
alignment = 0; // Ignore it if it's satisfied alignment = 0; // Ignore it if it's satisfied
@@ -425,14 +425,14 @@ void sect_NewSection(
SectionSpec const &attrs, SectionSpec const &attrs,
SectionModifier mod SectionModifier mod
) { ) {
if (currentLoadSection)
fatalerror("Cannot change the section within a `LOAD` block\n");
for (SectionStackEntry &entry : sectionStack) { for (SectionStackEntry &entry : sectionStack) {
if (entry.section && entry.section->name == name) if (entry.section && entry.section->name == name)
fatalerror("Section '%s' is already on the stack\n", name.c_str()); fatalerror("Section '%s' is already on the stack\n", name.c_str());
} }
if (currentLoadSection)
sect_EndLoadSection("SECTION");
Section *sect = getSection(name, type, org, attrs, mod); Section *sect = getSection(name, type, org, attrs, mod);
changeSection(); changeSection();
@@ -457,20 +457,13 @@ void sect_SetLoadSection(
if (!requireCodeSection()) if (!requireCodeSection())
return; return;
if (currentLoadSection) {
error("`LOAD` blocks cannot be nested\n");
return;
}
if (sect_HasData(type)) { if (sect_HasData(type)) {
error("`LOAD` blocks cannot create a ROM section\n"); error("`LOAD` blocks cannot create a ROM section\n");
return; return;
} }
if (mod == SECTION_FRAGMENT) { if (currentLoadSection)
error("`LOAD FRAGMENT` is not allowed\n"); sect_EndLoadSection("LOAD");
return;
}
Section *sect = getSection(name, type, org, attrs, mod); Section *sect = getSection(name, type, org, attrs, mod);
@@ -481,7 +474,14 @@ void sect_SetLoadSection(
currentLoadSection = sect; 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) { if (!currentLoadSection) {
error("Found `ENDL` outside of a `LOAD` block\n"); error("Found `ENDL` outside of a `LOAD` block\n");
return; return;
@@ -494,6 +494,11 @@ void sect_EndLoadSection() {
sym_SetCurrentLabelScopes(currentLoadLabelScopes); sym_SetCurrentLabelScopes(currentLoadLabelScopes);
} }
void sect_CheckLoadClosed() {
if (currentLoadSection)
warning(WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by EOF\n");
}
Section *sect_GetSymbolSection() { Section *sect_GetSymbolSection() {
return currentLoadSection ? currentLoadSection : currentSection; return currentLoadSection ? currentLoadSection : currentSection;
} }
@@ -513,7 +518,7 @@ uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) {
if (!sect) if (!sect)
return 0; 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 // If the section is not aligned, no bytes are needed
// (fixed sections count as being maximally aligned for this purpose) // (fixed sections count as being maximally aligned for this purpose)
@@ -534,7 +539,7 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset) {
Section *sect = sect_GetSymbolSection(); Section *sect = sect_GetSymbolSection();
uint32_t alignSize = 1 << alignment; // Size of an aligned "block" uint32_t alignSize = 1 << alignment; // Size of an aligned "block"
if (sect->org != (uint32_t)-1) { if (sect->org != UINT32_MAX) {
if ((sect->org + curOffset - offset) % alignSize) if ((sect->org + curOffset - offset) % alignSize)
error( error(
"Section's fixed address fails required alignment (PC = $%04" PRIx32 ")\n", "Section's fixed address fails required alignment (PC = $%04" PRIx32 ")\n",
@@ -954,7 +959,7 @@ void sect_PopSection() {
fatalerror("No entries in the section stack\n"); fatalerror("No entries in the section stack\n");
if (currentLoadSection) if (currentLoadSection)
fatalerror("Cannot change the section within a `LOAD` block\n"); sect_EndLoadSection("POPS");
SectionStackEntry entry = sectionStack.front(); SectionStackEntry entry = sectionStack.front();
sectionStack.pop_front(); sectionStack.pop_front();
@@ -968,16 +973,22 @@ void sect_PopSection() {
std::swap(currentUnionStack, entry.unionStack); std::swap(currentUnionStack, entry.unionStack);
} }
void sect_CheckStack() {
if (!sectionStack.empty()) {
warning(WARNING_UNMATCHED_DIRECTIVE, "`PUSHS` without corresponding `POPS`\n");
}
}
void sect_EndSection() { void sect_EndSection() {
if (!currentSection) if (!currentSection)
fatalerror("Cannot end the section outside of a SECTION\n"); fatalerror("Cannot end the section outside of a SECTION\n");
if (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"); fatalerror("Cannot end the section within a UNION\n");
if (currentLoadSection)
sect_EndLoadSection("ENDSECTION");
// Reset the section scope // Reset the section scope
currentSection = nullptr; currentSection = nullptr;
sym_ResetCurrentLabelScopes(); sym_ResetCurrentLabelScopes();

View File

@@ -123,7 +123,7 @@ static void updateSymbolFilename(Symbol &sym) {
sym.fileLine = sym.src ? lexer_GetLineNo() : 0; sym.fileLine = sym.src ? lexer_GetLineNo() : 0;
// If the old node was registered, ensure the new one is too // If the old node was registered, ensure the new one is too
if (oldSrc && oldSrc->ID != (uint32_t)-1) if (oldSrc && oldSrc->ID != UINT32_MAX)
out_RegisterNode(sym.src); out_RegisterNode(sym.src);
} }
@@ -169,7 +169,7 @@ static Symbol &createSymbol(std::string const &symName) {
sym.section = nullptr; sym.section = nullptr;
sym.src = fstk_GetFileStack(); sym.src = fstk_GetFileStack();
sym.fileLine = sym.src ? lexer_GetLineNo() : 0; sym.fileLine = sym.src ? lexer_GetLineNo() : 0;
sym.ID = -1; sym.ID = UINT32_MAX;
sym.defIndex = nextDefIndex++; sym.defIndex = nextDefIndex++;
return sym; return sym;
@@ -258,7 +258,7 @@ void sym_Purge(std::string const &symName) {
error("'%s' not defined\n", symName.c_str()); error("'%s' not defined\n", symName.c_str());
} else if (sym->isBuiltin) { } else if (sym->isBuiltin) {
error("Built-in symbol '%s' cannot be purged\n", symName.c_str()); error("Built-in symbol '%s' cannot be purged\n", symName.c_str());
} else if (sym->ID != (uint32_t)-1) { } else if (sym->ID != UINT32_MAX) {
error("Symbol \"%s\" is referenced and thus cannot be purged\n", symName.c_str()); error("Symbol \"%s\" is referenced and thus cannot be purged\n", symName.c_str());
} else { } else {
if (sym->isExported) if (sym->isExported)
@@ -460,7 +460,7 @@ static Symbol *addLabel(std::string const &symName) {
} }
// If the symbol already exists as a ref, just "take over" it // If the symbol already exists as a ref, just "take over" it
sym->type = SYM_LABEL; sym->type = SYM_LABEL;
sym->data = (int32_t)sect_GetSymbolOffset(); sym->data = static_cast<int32_t>(sect_GetSymbolOffset());
// Don't export anonymous labels // Don't export anonymous labels
if (exportAll && !symName.starts_with('!')) if (exportAll && !symName.starts_with('!'))
sym->isExported = true; sym->isExported = true;
@@ -627,7 +627,7 @@ void sym_Init(time_t now) {
sym_AddEqu("__RGBDS_RC__"s, PACKAGE_VERSION_RC)->isBuiltin = true; sym_AddEqu("__RGBDS_RC__"s, PACKAGE_VERSION_RC)->isBuiltin = true;
#endif #endif
if (now == (time_t)-1) { if (now == static_cast<time_t>(-1)) {
warn("Failed to determine current time"); warn("Failed to determine current time");
// Fall back by pretending we are at the Epoch // Fall back by pretending we are at the Epoch
now = 0; now = 0;

View File

@@ -10,7 +10,7 @@
#include <string.h> #include <string.h>
#include "error.hpp" #include "error.hpp"
#include "helpers.hpp" // QUOTEDSTRLEN #include "helpers.hpp"
#include "itertools.hpp" #include "itertools.hpp"
#include "asm/fstack.hpp" #include "asm/fstack.hpp"
@@ -20,265 +20,165 @@
unsigned int nbErrors = 0; unsigned int nbErrors = 0;
unsigned int maxErrors = 0; unsigned int maxErrors = 0;
static WarningState const defaultWarnings[ARRAY_SIZE(warningStates)] = { Diagnostics warningStates;
WARNING_ENABLED, // WARNING_ASSERT bool warningsAreErrors;
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
WARNING_DISABLED, // WARNING_NUMERIC_STRING_1 enum WarningLevel {
WARNING_DISABLED, // WARNING_NUMERIC_STRING_2 LEVEL_DEFAULT, // Warnings that are enabled by default
WARNING_ENABLED, // WARNING_TRUNCATION_1 LEVEL_ALL, // Warnings that probably indicate an error
WARNING_DISABLED, // WARNING_TRUNCATION_2 LEVEL_EXTRA, // Warnings that are less likely to indicate an error
WARNING_ENABLED, // WARNING_PURGE_1 LEVEL_EVERYTHING, // Literally every warning
WARNING_DISABLED, // WARNING_PURGE_2
WARNING_ENABLED, // WARNING_UNMAPPED_CHAR_1
WARNING_DISABLED, // WARNING_UNMAPPED_CHAR_2
}; };
WarningState warningStates[ARRAY_SIZE(warningStates)]; struct WarningFlag {
char const *name;
WarningLevel level;
};
bool warningsAreErrors; // Set if `-Werror` was specified static const WarningFlag metaWarnings[] = {
{"all", LEVEL_ALL },
static WarningState warningState(WarningID id) { {"extra", LEVEL_EXTRA },
// Check if warnings are globally disabled {"everything", LEVEL_EVERYTHING},
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 const WarningFlag 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 // Parametric warnings
"numeric-string", {"numeric-string", LEVEL_EVERYTHING},
"numeric-string", {"numeric-string", LEVEL_EVERYTHING},
"purge", {"purge", LEVEL_DEFAULT },
"purge", {"purge", LEVEL_ALL },
"truncation", {"truncation", LEVEL_DEFAULT },
"truncation", {"truncation", LEVEL_EXTRA },
"unmapped-char", {"unmapped-char", LEVEL_DEFAULT },
"unmapped-char", {"unmapped-char", LEVEL_ALL },
// Meta warnings
"all",
"extra",
"everything", // Especially useful for testing
}; };
static const struct { static const struct {
char const *name; WarningID firstID;
uint8_t nbLevels; WarningID lastID;
uint8_t defaultLevel; uint8_t defaultLevel;
} paramWarnings[] = { } paramWarnings[] = {
{"numeric-string", 2, 1}, {WARNING_NUMERIC_STRING_1, WARNING_NUMERIC_STRING_2, 1},
{"purge", 2, 1}, {WARNING_PURGE_1, WARNING_PURGE_2, 1},
{"truncation", 2, 2}, {WARNING_TRUNCATION_1, WARNING_TRUNCATION_2, 2},
{"unmapped-char", 2, 1}, {WARNING_UNMAPPED_CHAR_1, WARNING_UNMAPPED_CHAR_2, 1},
}; };
static bool tryProcessParamWarning(char const *flag, uint8_t param, WarningState state) { enum WarningBehavior { DISABLED, ENABLED, ERROR };
WarningID baseID = PARAM_WARNINGS_START;
for (size_t i = 0; i < ARRAY_SIZE(paramWarnings); i++) { static WarningBehavior getWarningBehavior(WarningID id) {
uint8_t maxParam = paramWarnings[i].nbLevels; // Check if warnings are globally disabled
if (!warnings)
return WarningBehavior::DISABLED;
if (!strcmp(flag, paramWarnings[i].name)) { // Match! // Get the state of this warning flag
if (!strcmp(flag, "numeric-string")) WarningState const &flagState = warningStates.flagStates[id];
warning(WARNING_OBSOLETE, "Warning flag \"numeric-string\" is deprecated\n"); WarningState const &metaState = warningStates.metaStates[id];
// If making the warning an error but param is 0, set to the maximum // If subsequent checks determine that the warning flag is enabled, this checks whether it has
// This accommodates `-Werror=flag`, but also `-Werror=flag=0`, which is // -Werror without -Wno-error=<flag> or -Wno-error=<meta>, which makes it into an error
// thus filtered out by the caller. bool warningIsError = warningsAreErrors && flagState.error != WARNING_DISABLED
// A param of 0 makes sense for disabling everything, but neither for && metaState.error != WARNING_DISABLED;
// enabling nor "erroring". Use the default for those. WarningBehavior enabledBehavior =
if (param == 0 && state != WARNING_DISABLED) { warningIsError ? WarningBehavior::ERROR : WarningBehavior::ENABLED;
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 // First, check the state of the specific warning flag
for (uint8_t ofs = 0; ofs < maxParam; ofs++) { if (flagState.state == WARNING_DISABLED) // -Wno-<flag>
warningStates[baseID + ofs] = ofs < param ? state : WARNING_DISABLED; return WarningBehavior::DISABLED;
} if (flagState.error == WARNING_ENABLED) // -Werror=<flag>
return true; return WarningBehavior::ERROR;
} if (flagState.state == WARNING_ENABLED) // -W<flag>
return enabledBehavior;
baseID = (WarningID)(baseID + maxParam); // 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 false; 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 }; void WarningState::update(WarningState other) {
if (other.state != WARNING_DEFAULT)
// Warnings that probably indicate an error state = other.state;
static uint8_t const _wallCommands[] = { if (other.error != WARNING_DEFAULT)
WARNING_BACKWARDS_FOR, error = other.error;
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 processWarningFlag(char const *flag) { void processWarningFlag(char const *flag) {
static bool setError = false; std::string rootFlag = flag;
// First, try to match against a "meta" warning // Check for `-Werror` or `-Wno-error` to return early
for (WarningID id : EnumSeq(META_WARNINGS_START, NB_WARNINGS)) { if (rootFlag == "error") {
// TODO: improve the matching performance? // `-Werror` promotes warnings to errors
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;
}
}
// 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; warningsAreErrors = true;
return; return;
} else if (rootFlag == "no-error") {
case '=': // `-Wno-error` disables promotion of warnings to errors
// `-Werror=XXX` warningsAreErrors = false;
setError = true;
processWarningFlag(errorFlag + 1); // Skip the `=`
setError = false;
return; return;
// Otherwise, allow parsing as another flag
}
} }
// Well, it's either a normal warning or a mistake // 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, QUOTEDSTRLEN("error="));
} else if (rootFlag.starts_with("no-error=")) {
// `-Wno-error=<flag>` prevents the flag from being an error,
// without affecting whether it is enabled
state = {.state = WARNING_DEFAULT, .error = WARNING_DISABLED};
rootFlag.erase(0, QUOTEDSTRLEN("no-error="));
} else if (rootFlag.starts_with("no-")) {
// `-Wno-<flag>` disables the flag
state = {.state = WARNING_DISABLED, .error = WARNING_DEFAULT};
rootFlag.erase(0, QUOTEDSTRLEN("no-"));
} else {
// `-W<flag>` enables the flag
state = {.state = WARNING_ENABLED, .error = WARNING_DEFAULT};
}
WarningState state = setError ? WARNING_ERROR // Check for an `=` parameter to process as a parametric warning
// Not an error, then check if this is a negation // `-Wno-<flag>` and `-Wno-error=<flag>` negation cannot have an `=` parameter, but without a
: strncmp(flag, "no-", QUOTEDSTRLEN("no-")) ? WARNING_ENABLED // parameter, the 0 value will apply to all levels of a parametric warning
: WARNING_DISABLED; uint8_t param = 0;
char const *rootFlag = state == WARNING_DISABLED ? flag + QUOTEDSTRLEN("no-") : flag; bool hasParam = false;
if (state.state == WARNING_ENABLED) {
// Is this a "parametric" warning?
if (state != WARNING_DISABLED) { // The `no-` form cannot be parametrized
// First, check if there is an "equals" sign followed by a decimal number // 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? // Is the rest of the string a decimal number?
// We want to avoid `strtoul`'s whitespace and sign, so we parse manually // We want to avoid `strtoul`'s whitespace and sign, so we parse manually
uint8_t param = 0; char const *ptr = rootFlag.c_str() + equals + 1;
char const *ptr = equals + 1;
bool warned = false; bool warned = false;
// The `if`'s condition above ensures that this will run at least once // The `if`'s condition above ensures that this will run at least once
@@ -289,7 +189,7 @@ void processWarningFlag(char const *flag) {
// Avoid overflowing! // Avoid overflowing!
if (param > UINT8_MAX - (*ptr - '0')) { if (param > UINT8_MAX - (*ptr - '0')) {
if (!warned) if (!warned)
warnx("Invalid warning flag \"%s\": capping parameter at 255\n", flag); warnx("Invalid warning flag \"%s\": capping parameter at 255", flag);
warned = true; // Only warn once, cap always warned = true; // Only warn once, cap always
param = 255; param = 255;
continue; continue;
@@ -299,40 +199,83 @@ void processWarningFlag(char const *flag) {
ptr++; ptr++;
} while (*ptr); } while (*ptr);
// If we managed to the end of the string, check that the warning indeed // If we reached the end of the string, truncate it at the '='
// accepts a parameter
if (*ptr == '\0') { if (*ptr == '\0') {
if (setError && param == 0) { rootFlag.resize(equals);
warnx("Ignoring nonsensical warning flag \"%s\"\n", flag); // `-W<flag>=0` is equivalent to `-Wno-<flag>`
return; if (param == 0)
state.state = WARNING_DISABLED;
}
}
} }
std::string truncFlag = rootFlag; // 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);
truncFlag.resize(equals - rootFlag); // Truncate the param at the '=' if (rootFlag == warningFlags[baseID].name) { // Match!
if (tryProcessParamWarning( if (rootFlag == "numeric-string")
truncFlag.c_str(), param, param == 0 ? WARNING_DISABLED : state 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; 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;
}
} }
// Try to match the flag against a "normal" flag // Try to match the flag against a "normal" flag
for (WarningID id : EnumSeq(NB_PLAIN_WARNINGS)) { for (WarningID id : EnumSeq(NB_PLAIN_WARNINGS)) {
if (!strcmp(rootFlag, warningFlags[id])) { if (rootFlag == warningFlags[id].name) {
// We got a match! warningStates.flagStates[id].update(state);
warningStates[id] = state;
return; return;
} }
} }
}
// Lastly, this might be a "parametric" warning without an equals sign warnx("Unknown warning flag \"%s\"", flag);
// If it is, treat the param as 1 if enabling, or 0 if disabling
if (tryProcessParamWarning(rootFlag, 0, state))
return;
warnx("Unknown warning `%s`", flag);
} }
void printDiag( void printDiag(
@@ -376,26 +319,22 @@ void error(char const *fmt, ...) {
} }
void warning(WarningID id, 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_list args;
va_start(args, fmt); va_start(args, fmt);
switch (warningState(id)) { switch (getWarningBehavior(id)) {
case WARNING_DISABLED: case WarningBehavior::DISABLED:
break; break;
case WARNING_ENABLED: case WarningBehavior::ENABLED:
printDiag(fmt, args, "warning", ": [-W%s]", flag); printDiag(fmt, args, "warning", ": [-W%s]", flag);
break; break;
case WARNING_ERROR: case WarningBehavior::ERROR:
printDiag(fmt, args, "error", ": [-Werror=%s]", flag); printDiag(fmt, args, "error", ": [-Werror=%s]", flag);
break; break;
case WARNING_DEFAULT:
unreachable_();
// Not reached
} }
va_end(args); va_end(args);

View File

@@ -227,7 +227,7 @@ static MbcType parseMBC(char const *name) {
return MBC_BAD; return MBC_BAD;
if (mbc > 0xFF) if (mbc > 0xFF)
return MBC_BAD_RANGE; return MBC_BAD_RANGE;
return (MbcType)mbc; return static_cast<MbcType>(mbc);
} else { } else {
// Begin by reading the MBC type: // Begin by reading the MBC type:
@@ -568,7 +568,7 @@ static MbcType parseMBC(char const *name) {
if (*ptr) if (*ptr)
return MBC_BAD; return MBC_BAD;
return (MbcType)mbc; return static_cast<MbcType>(mbc);
} }
} }
@@ -882,9 +882,9 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
report( report(
"FATAL: \"%s\" too short, expected at least %jd ($%jx) bytes, got only %jd\n", "FATAL: \"%s\" too short, expected at least %jd ($%jx) bytes, got only %jd\n",
name, name,
(intmax_t)headerSize, static_cast<intmax_t>(headerSize),
(intmax_t)headerSize, static_cast<intmax_t>(headerSize),
(intmax_t)rom0Len static_cast<intmax_t>(rom0Len)
); );
return; return;
} }
@@ -894,17 +894,27 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
overwriteBytes(rom0, 0x0104, logo, sizeof(logo), logoFilename ? "logo" : "Nintendo logo"); overwriteBytes(rom0, 0x0104, logo, sizeof(logo), logoFilename ? "logo" : "Nintendo logo");
if (title) if (title)
overwriteBytes(rom0, 0x134, (uint8_t const *)title, titleLen, "title"); overwriteBytes(rom0, 0x134, reinterpret_cast<uint8_t const *>(title), titleLen, "title");
if (gameID) if (gameID)
overwriteBytes(rom0, 0x13F, (uint8_t const *)gameID, gameIDLen, "manufacturer code"); overwriteBytes(
rom0,
0x13F,
reinterpret_cast<uint8_t const *>(gameID),
gameIDLen,
"manufacturer code"
);
if (model != DMG) if (model != DMG)
overwriteByte(rom0, 0x143, model == BOTH ? 0x80 : 0xC0, "CGB flag"); overwriteByte(rom0, 0x143, model == BOTH ? 0x80 : 0xC0, "CGB flag");
if (newLicensee) if (newLicensee)
overwriteBytes( overwriteBytes(
rom0, 0x144, (uint8_t const *)newLicensee, newLicenseeLen, "new licensee code" rom0,
0x144,
reinterpret_cast<uint8_t const *>(newLicensee),
newLicenseeLen,
"new licensee code"
); );
if (sgb) if (sgb)
@@ -1076,7 +1086,10 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
if (fixSpec & TRASH_GLOBAL_SUM) if (fixSpec & TRASH_GLOBAL_SUM)
globalSum = ~globalSum; globalSum = ~globalSum;
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"); overwriteBytes(rom0, 0x14E, bytes, sizeof(bytes), "global checksum");
} }
@@ -1086,7 +1099,7 @@ 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 // In case the output depends on the input, reset to the beginning of the file, and only
// write the header // write the header
if (input == output) { 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)); report("FATAL: Failed to rewind \"%s\": %s\n", name, strerror(errno));
return; return;
} }
@@ -1103,9 +1116,9 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
} else if (writeLen < rom0Len) { } else if (writeLen < rom0Len) {
report( report(
"FATAL: Could only write %jd of \"%s\"'s %jd ROM0 bytes\n", "FATAL: Could only write %jd of \"%s\"'s %jd ROM0 bytes\n",
(intmax_t)writeLen, static_cast<intmax_t>(writeLen),
name, name,
(intmax_t)rom0Len static_cast<intmax_t>(rom0Len)
); );
return; return;
} }
@@ -1118,10 +1131,10 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
if (writeLen == -1) { if (writeLen == -1) {
report("FATAL: Failed to write \"%s\"'s ROMX: %s\n", name, strerror(errno)); report("FATAL: Failed to write \"%s\"'s ROMX: %s\n", name, strerror(errno));
return; return;
} else if ((size_t)writeLen < totalRomxLen) { } else if (static_cast<size_t>(writeLen) < totalRomxLen) {
report( report(
"FATAL: Could only write %jd of \"%s\"'s %zu ROMX bytes\n", "FATAL: Could only write %jd of \"%s\"'s %zu ROMX bytes\n",
(intmax_t)writeLen, static_cast<intmax_t>(writeLen),
name, name,
totalRomxLen totalRomxLen
); );
@@ -1132,7 +1145,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
// Output padding // Output padding
if (padValue != UNSPECIFIED) { if (padValue != UNSPECIFIED) {
if (input == output) { 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)); report("FATAL: Failed to seek to end of \"%s\": %s\n", name, strerror(errno));
return; return;
} }
@@ -1147,7 +1160,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
// The return value is either -1, or at most `thisLen`, // The return value is either -1, or at most `thisLen`,
// so it's fine to cast to `size_t` // 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)); report("FATAL: Failed to write \"%s\"'s padding: %s\n", name, strerror(errno));
break; break;
} }
@@ -1160,9 +1173,9 @@ static bool processFilename(char const *name) {
nbErrors = 0; nbErrors = 0;
if (!strcmp(name, "-")) { if (!strcmp(name, "-")) {
name = "<stdin>";
(void)setmode(STDIN_FILENO, O_BINARY); (void)setmode(STDIN_FILENO, O_BINARY);
(void)setmode(STDOUT_FILENO, O_BINARY); (void)setmode(STDOUT_FILENO, O_BINARY);
name = "<stdin>";
processFile(STDIN_FILENO, STDOUT_FILENO, name, 0); processFile(STDIN_FILENO, STDOUT_FILENO, name, 0);
} else { } else {
// POSIX specifies that the results of O_RDWR on a FIFO are undefined. // POSIX specifies that the results of O_RDWR on a FIFO are undefined.
@@ -1188,7 +1201,7 @@ static bool processFilename(char const *name) {
report( report(
"FATAL: \"%s\" too short, expected at least 336 ($150) bytes, got only %jd\n", "FATAL: \"%s\" too short, expected at least 336 ($150) bytes, got only %jd\n",
name, name,
(intmax_t)stat.st_size static_cast<intmax_t>(stat.st_size)
); );
} else { } else {
processFile(input, input, name, stat.st_size); processFile(input, input, name, stat.st_size);
@@ -1435,7 +1448,8 @@ int main(int argc, char *argv[]) {
logoFile = fopen(logoFilename, "rb"); logoFile = fopen(logoFilename, "rb");
} else { } else {
logoFilename = "<stdin>"; logoFilename = "<stdin>";
logoFile = fdopen(STDIN_FILENO, "rb"); (void)setmode(STDIN_FILENO, O_BINARY);
logoFile = stdin;
} }
if (!logoFile) { if (!logoFile) {
fprintf( fprintf(

View File

@@ -108,7 +108,7 @@ void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
} }
// Short options // 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:i:L:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvXx:YZ";
/* /*
* Equivalent long options * Equivalent long options
@@ -139,6 +139,7 @@ static option const longopts[] = {
{"auto-palette-map", no_argument, nullptr, 'Q'}, {"auto-palette-map", no_argument, nullptr, 'Q'},
{"palette-map", required_argument, nullptr, 'q'}, {"palette-map", required_argument, nullptr, 'q'},
{"reverse", required_argument, nullptr, 'r'}, {"reverse", required_argument, nullptr, 'r'},
{"palette-size", required_argument, nullptr, 's'},
{"auto-tilemap", no_argument, nullptr, 'T'}, {"auto-tilemap", no_argument, nullptr, 'T'},
{"tilemap", required_argument, nullptr, 't'}, {"tilemap", required_argument, nullptr, 't'},
{"unit-size", required_argument, nullptr, 'U'}, {"unit-size", required_argument, nullptr, 'U'},

View File

@@ -5,20 +5,19 @@
#include <algorithm> #include <algorithm>
#include <deque> #include <deque>
#include <inttypes.h> #include <inttypes.h>
#include <numeric>
#include <optional> #include <optional>
#include <queue> #include <queue>
#include <stdint.h>
#include <type_traits> #include <type_traits>
#include <unordered_set> #include <unordered_set>
#include <utility>
#include "helpers.hpp" #include "helpers.hpp"
#include "gfx/main.hpp" #include "gfx/main.hpp"
#include "gfx/proto_palette.hpp" #include "gfx/proto_palette.hpp"
using std::swap;
namespace packing {
// The solvers here are picked from the paper at https://arxiv.org/abs/1605.00558: // The solvers here are picked from the paper at https://arxiv.org/abs/1605.00558:
// "Algorithms for the Pagination Problem, a Bin Packing with Overlapping Items" // "Algorithms for the Pagination Problem, a Bin Packing with Overlapping Items"
// Their formulation of the problem consists in packing "tiles" into "pages"; here is a // Their formulation of the problem consists in packing "tiles" into "pages"; here is a
@@ -114,8 +113,8 @@ private:
} }
friend void swap(Iter &lhs, Iter &rhs) { friend void swap(Iter &lhs, Iter &rhs) {
swap(lhs._array, rhs._array); std::swap(lhs._array, rhs._array);
swap(lhs._iter, rhs._iter); std::swap(lhs._iter, rhs._iter);
} }
}; };
public: public:
@@ -203,21 +202,44 @@ public:
return colors.size() <= options.maxOpaqueColors(); return colors.size() <= options.maxOpaqueColors();
} }
// The `relSizeOf` method below should compute the sum, for each color in `protoPal`, of
// the reciprocal of the "multiplicity" of the color across "our" proto-palettes.
// However, literally computing the reciprocals would involve floating-point division, which
// leads to imprecision and even platform-specific differences.
// We avoid this by multiplying the reciprocals by a factor such that division always produces
// an integer; the LCM of all values the denominator can take is the smallest suitable factor.
static constexpr uint32_t scaleFactor = [] {
// Fold over 1..=17 with the associative LCM function
// (17 is the largest the denominator in `relSizeOf` below can be)
uint32_t factor = 1;
for (uint32_t n = 2; n <= 17; ++n) {
factor = std::lcm(factor, n);
}
return factor;
}();
/* /*
* Computes the "relative size" of a proto-palette on this palette * Computes the "relative size" of a proto-palette on this palette;
* it's a measure of how much this proto-palette would "cost" to introduce.
*/ */
double relSizeOf(ProtoPalette const &protoPal) const { uint32_t relSizeOf(ProtoPalette const &protoPal) const {
// NOTE: this function must not call `uniqueColors`, or one of its callers will break! // NOTE: this function must not call `uniqueColors`, or one of its callers will break!
double relSize = 0.;
uint32_t relSize = 0;
for (uint16_t color : protoPal) { for (uint16_t color : protoPal) {
auto n = std::count_if(RANGE(*this), [this, &color](ProtoPalAttrs const &attrs) { auto multiplicity = // How many of our proto-palettes does this color also belong to?
std::count_if(RANGE(*this), [this, &color](ProtoPalAttrs const &attrs) {
ProtoPalette const &pal = (*_protoPals)[attrs.protoPalIndex]; ProtoPalette const &pal = (*_protoPals)[attrs.protoPalIndex];
return std::find(RANGE(pal), color) != pal.end(); return std::find(RANGE(pal), color) != pal.end();
}); });
// NOTE: The paper and the associated code disagree on this: the code has // We increase the denominator by 1 here; the reference code does this,
// this `1 +`, whereas the paper does not; its lack causes a division by 0 // but the paper does not. Not adding 1 makes a multiplicity of 0 cause a division by 0
// if the symbol is not found anywhere, so I'm assuming the paper is wrong. // (that is, if the color is not found in any proto-palette), and adding 1 still seems
relSize += 1. / (1 + n); // to preserve the paper's reasoning.
//
// The scale factor should ensure integer divisions only.
assume(scaleFactor % (multiplicity + 1) == 0);
relSize += scaleFactor / (multiplicity + 1);
} }
return relSize; return relSize;
} }
@@ -379,13 +401,15 @@ std::tuple<DefaultInitVec<size_t>, size_t>
for (; !queue.empty(); queue.pop()) { for (; !queue.empty(); queue.pop()) {
ProtoPalAttrs const &attrs = queue.front(); // Valid until the `queue.pop()` ProtoPalAttrs const &attrs = queue.front(); // Valid until the `queue.pop()`
options.verbosePrint(Options::VERB_DEBUG, "Handling proto-pal %zu\n", attrs.protoPalIndex); options.verbosePrint(
Options::VERB_TRACE, "Handling proto-palette %zu\n", attrs.protoPalIndex
);
ProtoPalette const &protoPal = protoPalettes[attrs.protoPalIndex]; ProtoPalette const &protoPal = protoPalettes[attrs.protoPalIndex];
size_t bestPalIndex = assignments.size(); size_t bestPalIndex = assignments.size();
// We're looking for a palette where the proto-palette's relative size is less than // We're looking for a palette where the proto-palette's relative size is less than
// its actual size; so only overwrite the "not found" index on meeting that criterion // its actual size; so only overwrite the "not found" index on meeting that criterion
double bestRelSize = protoPal.size(); uint32_t bestRelSize = protoPal.size() * AssignedProtos::scaleFactor;
for (size_t i = 0; i < assignments.size(); ++i) { for (size_t i = 0; i < assignments.size(); ++i) {
// Skip the page if this one is banned from it // Skip the page if this one is banned from it
@@ -393,11 +417,11 @@ std::tuple<DefaultInitVec<size_t>, size_t>
continue; continue;
} }
double relSize = assignments[i].relSizeOf(protoPal); uint32_t relSize = assignments[i].relSizeOf(protoPal);
options.verbosePrint( options.verbosePrint(
Options::VERB_DEBUG, Options::VERB_TRACE,
"%zu/%zu: Rel size: %f (size = %zu)\n", " Relative size to palette %zu (of %zu): %" PRIu32 " (size = %zu)\n",
i + 1, i,
assignments.size(), assignments.size(),
relSize, relSize,
protoPal.size() protoPal.size()
@@ -410,8 +434,20 @@ std::tuple<DefaultInitVec<size_t>, size_t>
if (bestPalIndex == assignments.size()) { if (bestPalIndex == assignments.size()) {
// Found nowhere to put it, create a new page containing just that one // Found nowhere to put it, create a new page containing just that one
options.verbosePrint(
Options::VERB_TRACE,
"Assigning proto-palette %zu to new palette %zu\n",
attrs.protoPalIndex,
bestPalIndex
);
assignments.emplace_back(protoPalettes, std::move(attrs)); assignments.emplace_back(protoPalettes, std::move(attrs));
} else { } else {
options.verbosePrint(
Options::VERB_TRACE,
"Assigning proto-palette %zu to palette %zu\n",
attrs.protoPalIndex,
bestPalIndex
);
auto &bestPal = assignments[bestPalIndex]; auto &bestPal = assignments[bestPalIndex];
// Add the color to that palette // Add the color to that palette
bestPal.assign(std::move(attrs)); bestPal.assign(std::move(attrs));
@@ -419,7 +455,7 @@ std::tuple<DefaultInitVec<size_t>, size_t>
// If this overloads the palette, get it back to normal (if possible) // If this overloads the palette, get it back to normal (if possible)
while (bestPal.volume() > options.maxOpaqueColors()) { while (bestPal.volume() > options.maxOpaqueColors()) {
options.verbosePrint( options.verbosePrint(
Options::VERB_DEBUG, Options::VERB_TRACE,
"Palette %zu is overloaded! (%zu > %" PRIu8 ")\n", "Palette %zu is overloaded! (%zu > %" PRIu8 ")\n",
bestPalIndex, bestPalIndex,
bestPal.volume(), bestPal.volume(),
@@ -427,28 +463,67 @@ std::tuple<DefaultInitVec<size_t>, size_t>
); );
// Look for a proto-pal minimizing "efficiency" (size / rel_size) // Look for a proto-pal minimizing "efficiency" (size / rel_size)
auto efficiency = [&bestPal](ProtoPalette const &pal) {
return pal.size() / bestPal.relSizeOf(pal);
};
auto [minEfficiencyIter, maxEfficiencyIter] = std::minmax_element( auto [minEfficiencyIter, maxEfficiencyIter] = std::minmax_element(
RANGE(bestPal), RANGE(bestPal),
[&efficiency, [&bestPal, &protoPalettes](ProtoPalAttrs const &lhs, ProtoPalAttrs const &rhs) {
&protoPalettes](ProtoPalAttrs const &lhs, ProtoPalAttrs const &rhs) { ProtoPalette const &lhsProtoPal = protoPalettes[lhs.protoPalIndex];
return efficiency(protoPalettes[lhs.protoPalIndex]) ProtoPalette const &rhsProtoPal = protoPalettes[rhs.protoPalIndex];
< efficiency(protoPalettes[rhs.protoPalIndex]); size_t lhsSize = lhsProtoPal.size();
size_t rhsSize = rhsProtoPal.size();
uint32_t lhsRelSize = bestPal.relSizeOf(lhsProtoPal);
uint32_t rhsRelSize = bestPal.relSizeOf(rhsProtoPal);
options.verbosePrint(
Options::VERB_TRACE,
" Proto-palettes %zu <=> %zu: Efficiency: %zu / %" PRIu32 " <=> %zu / "
"%" PRIu32 "\n",
lhs.protoPalIndex,
rhs.protoPalIndex,
lhsSize,
lhsRelSize,
rhsSize,
rhsRelSize
);
// This comparison is algebraically equivalent to
// `lhsSize / lhsRelSize < rhsSize / rhsRelSize`,
// but without potential precision loss from floating-point division.
return lhsSize * rhsRelSize < rhsSize * lhsRelSize;
} }
); );
// All efficiencies are identical iff min equals max // All efficiencies are identical iff min equals max
// TODO: maybe not ideal to re-compute these two? // TODO: maybe not ideal to re-compute these two?
// TODO: yikes for float comparison! I *think* this threshold is OK? ProtoPalette const &minProtoPal = protoPalettes[minEfficiencyIter->protoPalIndex];
if (efficiency(protoPalettes[maxEfficiencyIter->protoPalIndex]) ProtoPalette const &maxProtoPal = protoPalettes[maxEfficiencyIter->protoPalIndex];
- efficiency(protoPalettes[minEfficiencyIter->protoPalIndex]) size_t minSize = minProtoPal.size();
< .001) { size_t maxSize = maxProtoPal.size();
uint32_t minRelSize = bestPal.relSizeOf(minProtoPal);
uint32_t maxRelSize = bestPal.relSizeOf(maxProtoPal);
options.verbosePrint(
Options::VERB_TRACE,
" Proto-palettes %zu <= %zu: Efficiency: %zu / %" PRIu32 " <= %zu / %" PRIu32
"\n",
minEfficiencyIter->protoPalIndex,
maxEfficiencyIter->protoPalIndex,
minSize,
minRelSize,
maxSize,
maxRelSize
);
// This comparison is algebraically equivalent to
// `maxSize / maxRelSize == minSize / minRelSize`,
// but without potential precision loss from floating-point division.
if (maxSize * minRelSize == minSize * maxRelSize) {
options.verbosePrint(Options::VERB_TRACE, " All efficiencies are identical\n");
break; break;
} }
// Remove the proto-pal with minimal efficiency // Remove the proto-pal with minimal efficiency
options.verbosePrint(
Options::VERB_TRACE,
" Removing proto-palette %zu\n",
minEfficiencyIter->protoPalIndex
);
queue.emplace(std::move(*minEfficiencyIter)); queue.emplace(std::move(*minEfficiencyIter));
queue.back().banFrom(bestPalIndex); // Ban it from this palette queue.back().banFrom(bestPalIndex); // Ban it from this palette
bestPal.remove(minEfficiencyIter); bestPal.remove(minEfficiencyIter);
@@ -483,7 +558,7 @@ std::tuple<DefaultInitVec<size_t>, size_t>
if (iter == assignments.end()) { // No such page, create a new one if (iter == assignments.end()) { // No such page, create a new one
options.verbosePrint( options.verbosePrint(
Options::VERB_DEBUG, Options::VERB_DEBUG,
"Adding new palette (%zu) for overflowing proto-pal %zu\n", "Adding new palette (%zu) for overflowing proto-palette %zu\n",
assignments.size(), assignments.size(),
attrs.protoPalIndex attrs.protoPalIndex
); );
@@ -491,7 +566,7 @@ std::tuple<DefaultInitVec<size_t>, size_t>
} else { } else {
options.verbosePrint( options.verbosePrint(
Options::VERB_DEBUG, Options::VERB_DEBUG,
"Assigning overflowing proto-pal %zu to palette %zu\n", "Assigning overflowing proto-palette %zu to palette %zu\n",
attrs.protoPalIndex, attrs.protoPalIndex,
iter - assignments.begin() iter - assignments.begin()
); );
@@ -537,5 +612,3 @@ std::tuple<DefaultInitVec<size_t>, size_t>
} }
return {mappings, assignments.size()}; return {mappings, assignments.size()};
} }
} // namespace packing

View File

@@ -8,9 +8,7 @@
#include "gfx/main.hpp" #include "gfx/main.hpp"
namespace sorting { void sortIndexed(
void indexed(
std::vector<Palette> &palettes, std::vector<Palette> &palettes,
int palSize, int palSize,
png_color const *palRGB, png_color const *palRGB,
@@ -47,10 +45,10 @@ void indexed(
} }
} }
void grayscale( void sortGrayscale(
std::vector<Palette> &palettes, std::array<std::optional<Rgba>, 0x8001> const &colors std::vector<Palette> &palettes, std::array<std::optional<Rgba>, 0x8001> const &colors
) { ) {
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting grayscale-only palette...\n"); options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palette by grayscale bins...\n");
// This method is only applicable if there are at most as many colors as colors per palette, so // This method is only applicable if there are at most as many colors as colors per palette, so
// we should only have a single palette. // we should only have a single palette.
@@ -58,7 +56,7 @@ void grayscale(
Palette &palette = palettes[0]; Palette &palette = palettes[0];
std::fill(RANGE(palette.colors), Rgba::transparent); std::fill(RANGE(palette.colors), Rgba::transparent);
for (auto const &slot : colors) { for (std::optional<Rgba> const &slot : colors) {
if (!slot.has_value() || slot->isTransparent()) { if (!slot.has_value() || slot->isTransparent()) {
continue; continue;
} }
@@ -66,21 +64,19 @@ void grayscale(
} }
} }
static unsigned int legacyLuminance(uint16_t color) { static unsigned int luminance(uint16_t color) {
uint8_t red = color & 0b11111; uint8_t red = color & 0b11111;
uint8_t green = color >> 5 & 0b11111; uint8_t green = color >> 5 & 0b11111;
uint8_t blue = color >> 10; uint8_t blue = color >> 10;
return 2126 * red + 7152 * green + 722 * blue; return 2126 * red + 7152 * green + 722 * blue;
} }
void rgb(std::vector<Palette> &palettes) { void sortRgb(std::vector<Palette> &palettes) {
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palettes by \"\"\"luminance\"\"\"...\n"); options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palettes by luminance...\n");
for (Palette &pal : palettes) { for (Palette &pal : palettes) {
std::sort(RANGE(pal), [](uint16_t lhs, uint16_t rhs) { std::sort(RANGE(pal), [](uint16_t lhs, uint16_t rhs) {
return legacyLuminance(lhs) > legacyLuminance(rhs); return luminance(lhs) > luminance(rhs);
}); });
} }
} }
} // namespace sorting

View File

@@ -211,11 +211,12 @@ static T readLE(U const *bytes) {
[[gnu::warn_unused_result]] // Ignoring EOF is a bad idea. [[gnu::warn_unused_result]] // Ignoring EOF is a bad idea.
static bool static bool
readLine(std::filebuf &file, std::string &buffer) { readLine(std::filebuf &file, std::string &buffer) {
assume(buffer.empty());
// TODO: maybe this can be optimized to bulk reads? // TODO: maybe this can be optimized to bulk reads?
for (;;) { for (;;) {
auto c = file.sbumpc(); auto c = file.sbumpc();
if (c == std::filebuf::traits_type::eof()) { if (c == std::filebuf::traits_type::eof()) {
return false; return !buffer.empty();
} }
if (c == '\n') { if (c == '\n') {
// Discard a trailing CRLF // Discard a trailing CRLF

View File

@@ -15,7 +15,7 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "defaultinitalloc.hpp" #include "defaultinitvec.hpp"
#include "file.hpp" #include "file.hpp"
#include "helpers.hpp" #include "helpers.hpp"
#include "itertools.hpp" #include "itertools.hpp"
@@ -104,7 +104,7 @@ class Png {
"bytes after reading %zu)", "bytes after reading %zu)",
self->c_str(), self->c_str(),
length - nbBytesRead, length - nbBytesRead,
(size_t)self->file->pubseekoff(0, std::ios_base::cur) static_cast<size_t>(self->file->pubseekoff(0, std::ios_base::cur))
); );
} }
} }
@@ -193,7 +193,7 @@ public:
options.verbosePrint(Options::VERB_INTERM, "PNG header signature is OK\n"); options.verbosePrint(Options::VERB_INTERM, "PNG header signature is OK\n");
png = png_create_read_struct( png = png_create_read_struct(
PNG_LIBPNG_VER_STRING, (png_voidp)this, handleError, handleWarning PNG_LIBPNG_VER_STRING, static_cast<png_voidp>(this), handleError, handleWarning
); );
if (!png) { if (!png) {
fatal("Failed to allocate PNG structure: %s", strerror(errno)); fatal("Failed to allocate PNG structure: %s", strerror(errno));
@@ -446,7 +446,7 @@ public:
}; };
private: private:
struct iterator { struct Iterator {
TilesVisitor const &parent; TilesVisitor const &parent;
uint32_t const limit; uint32_t const limit;
uint32_t x, y; uint32_t x, y;
@@ -458,7 +458,7 @@ public:
return {parent._png, x + options.inputSlice.left, y + options.inputSlice.top}; return {parent._png, x + options.inputSlice.left, y + options.inputSlice.top};
} }
iterator &operator++() { Iterator &operator++() {
auto [major, minor] = parent._columnMajor ? std::tie(y, x) : std::tie(x, y); auto [major, minor] = parent._columnMajor ? std::tie(y, x) : std::tie(x, y);
major += 8; major += 8;
if (major == limit) { if (major == limit) {
@@ -468,19 +468,14 @@ public:
return *this; return *this;
} }
friend bool operator==(iterator const &lhs, iterator const &rhs) { bool operator==(Iterator const &rhs) const { return coords() == rhs.coords(); }
return lhs.coords() == rhs.coords(); // Compare the returned coord pairs bool operator!=(Iterator const &rhs) const { return coords() != rhs.coords(); }
}
friend bool operator!=(iterator const &lhs, iterator const &rhs) {
return lhs.coords() != rhs.coords(); // Compare the returned coord pairs
}
}; };
public: public:
iterator begin() const { return {*this, _limit, 0, 0}; } Iterator begin() const { return {*this, _limit, 0, 0}; }
iterator end() const { Iterator end() const {
iterator it{*this, _limit, _width - 8, _height - 8}; // Last valid one... Iterator it{*this, _limit, _width - 8, _height - 8}; // Last valid one...
return ++it; // ...now one-past-last! return ++it; // ...now one-past-last!
} }
}; };
@@ -567,7 +562,7 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
generatePalettes(std::vector<ProtoPalette> const &protoPalettes, Png const &png) { generatePalettes(std::vector<ProtoPalette> const &protoPalettes, Png const &png) {
// Run a "pagination" problem solver // Run a "pagination" problem solver
// TODO: allow picking one of several solvers? // TODO: allow picking one of several solvers?
auto [mappings, nbPalettes] = packing::overloadAndRemove(protoPalettes); auto [mappings, nbPalettes] = overloadAndRemove(protoPalettes);
assume(mappings.size() == protoPalettes.size()); assume(mappings.size() == protoPalettes.size());
if (options.verbosity >= Options::VERB_INTERM) { if (options.verbosity >= Options::VERB_INTERM) {
@@ -601,11 +596,11 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
// "Sort" colors in the generated palettes, see the man page for the flowchart // "Sort" colors in the generated palettes, see the man page for the flowchart
auto [embPalSize, embPalRGB, embPalAlphaSize, embPalAlpha] = png.getEmbeddedPal(); auto [embPalSize, embPalRGB, embPalAlphaSize, embPalAlpha] = png.getEmbeddedPal();
if (embPalRGB != nullptr) { if (embPalRGB != nullptr) {
sorting::indexed(palettes, embPalSize, embPalRGB, embPalAlphaSize, embPalAlpha); sortIndexed(palettes, embPalSize, embPalRGB, embPalAlphaSize, embPalAlpha);
} else if (png.isSuitableForGrayscale()) { } else if (png.isSuitableForGrayscale()) {
sorting::grayscale(palettes, png.getColors().raw()); sortGrayscale(palettes, png.getColors().raw());
} else { } else {
sorting::rgb(palettes); sortRgb(palettes);
} }
return {mappings, palettes}; return {mappings, palettes};
} }
@@ -717,6 +712,9 @@ static void hashBitplanes(uint16_t bitplanes, uint16_t &hash) {
} }
class TileData { class TileData {
// Importantly, `TileData` is **always** 2bpp.
// If the active bit depth is 1bpp, all tiles are processed as 2bpp nonetheless, but emitted as 1bpp.
// This massively simplifies internal processing, since bit depth is always identical outside of I/O / serialization boundaries.
std::array<uint8_t, 16> _data; std::array<uint8_t, 16> _data;
// The hash is a bit lax: it's the XOR of all lines, and every other nibble is identical // The hash is a bit lax: it's the XOR of all lines, and every other nibble is identical
// if horizontal mirroring is in effect. It should still be a reasonable tie-breaker in // if horizontal mirroring is in effect. It should still be a reasonable tie-breaker in
@@ -760,11 +758,9 @@ public:
hashBitplanes(bitplanes, _hash); hashBitplanes(bitplanes, _hash);
_data[writeIndex++] = bitplanes & 0xFF; _data[writeIndex++] = bitplanes & 0xFF;
if (options.bitDepth == 2) {
_data[writeIndex++] = bitplanes >> 8; _data[writeIndex++] = bitplanes >> 8;
} }
} }
}
auto const &data() const { return _data; } auto const &data() const { return _data; }
uint16_t hash() const { return _hash; } uint16_t hash() const { return _hash; }
@@ -826,9 +822,7 @@ public:
return MatchType::NOPE; return MatchType::NOPE;
} }
friend bool operator==(TileData const &lhs, TileData const &rhs) { bool operator==(TileData const &rhs) const { return tryMatching(rhs) != MatchType::NOPE; }
return lhs.tryMatching(rhs) != MatchType::NOPE;
}
}; };
template<> template<>
@@ -836,9 +830,7 @@ struct std::hash<TileData> {
std::size_t operator()(TileData const &tile) const { return tile.hash(); } std::size_t operator()(TileData const &tile) const { return tile.hash(); }
}; };
namespace unoptimized { static void outputUnoptimizedTileData(
static void outputTileData(
Png const &png, Png const &png,
DefaultInitVec<AttrmapEntry> const &attrmap, DefaultInitVec<AttrmapEntry> const &attrmap,
std::vector<Palette> const &palettes, std::vector<Palette> const &palettes,
@@ -877,7 +869,7 @@ static void outputTileData(
assume(remainingTiles == 0); assume(remainingTiles == 0);
} }
static void outputMaps( static void outputUnoptimizedMaps(
DefaultInitVec<AttrmapEntry> const &attrmap, DefaultInitVec<size_t> const &mappings DefaultInitVec<AttrmapEntry> const &attrmap, DefaultInitVec<size_t> const &mappings
) { ) {
std::optional<File> tilemapOutput, attrmapOutput, palmapOutput; std::optional<File> tilemapOutput, attrmapOutput, palmapOutput;
@@ -916,10 +908,6 @@ static void outputMaps(
} }
} }
} // namespace unoptimized
namespace optimized {
struct UniqueTiles { struct UniqueTiles {
std::unordered_set<TileData> tileset; std::unordered_set<TileData> tileset;
std::vector<TileData const *> tiles; std::vector<TileData const *> tiles;
@@ -1045,7 +1033,14 @@ static void outputTileData(UniqueTiles const &tiles) {
TileData const *tile = *iter; TileData const *tile = *iter;
assume(tile->tileID == tileID); assume(tile->tileID == tileID);
++tileID; ++tileID;
output->sputn(reinterpret_cast<char const *>(tile->data().data()), options.bitDepth * 8); if (options.bitDepth == 2) {
output->sputn(reinterpret_cast<char const *>(tile->data().data()), 16);
} else {
assume(options.bitDepth == 1);
for (size_t y = 0; y < 8; ++y) {
output->sputc(tile->data()[y * 2]);
}
}
} }
} }
@@ -1089,8 +1084,6 @@ static void outputPalmap(
} }
} }
} // namespace optimized
void processPalettes() { void processPalettes() {
options.verbosePrint(Options::VERB_CFG, "Using libpng %s\n", png_get_libpng_ver(nullptr)); options.verbosePrint(Options::VERB_CFG, "Using libpng %s\n", png_get_libpng_ver(nullptr));
@@ -1130,20 +1123,26 @@ void process() {
DefaultInitVec<AttrmapEntry> attrmap{}; DefaultInitVec<AttrmapEntry> attrmap{};
for (auto tile : png.visitAsTiles()) { for (auto tile : png.visitAsTiles()) {
ProtoPalette tileColors;
AttrmapEntry &attrs = attrmap.emplace_back(); AttrmapEntry &attrs = attrmap.emplace_back();
uint8_t nbColorsInTile = 0;
// Count the unique non-transparent colors for packing
std::unordered_set<uint16_t> tileColors;
for (uint32_t y = 0; y < 8; ++y) { for (uint32_t y = 0; y < 8; ++y) {
for (uint32_t x = 0; x < 8; ++x) { for (uint32_t x = 0; x < 8; ++x) {
Rgba color = tile.pixel(x, y); if (Rgba color = tile.pixel(x, y); !color.isTransparent()) {
if (!color.isTransparent()) { // Do not count transparency in for packing tileColors.insert(color.cgbColor());
// Add the color to the proto-pal (if not full), and count it if it was unique.
if (tileColors.add(color.cgbColor())) {
++nbColorsInTile;
} }
} }
} }
if (tileColors.size() > options.maxOpaqueColors()) {
fatal(
"Tile at (%" PRIu32 ", %" PRIu32 ") has %zu colors, more than %" PRIu8 "!",
tile.x,
tile.y,
tileColors.size(),
options.maxOpaqueColors()
);
} }
if (tileColors.empty()) { if (tileColors.empty()) {
@@ -1152,11 +1151,16 @@ void process() {
continue; continue;
} }
ProtoPalette protoPalette;
for (uint16_t cgbColor : tileColors) {
protoPalette.add(cgbColor);
}
// Insert the proto-palette, making sure to avoid overlaps // Insert the proto-palette, making sure to avoid overlaps
for (size_t n = 0; n < protoPalettes.size(); ++n) { for (size_t n = 0; n < protoPalettes.size(); ++n) {
switch (tileColors.compare(protoPalettes[n])) { switch (protoPalette.compare(protoPalettes[n])) {
case ProtoPalette::WE_BIGGER: case ProtoPalette::WE_BIGGER:
protoPalettes[n] = tileColors; // Override them protoPalettes[n] = protoPalette; // Override them
// Remove any other proto-palettes that we encompass // Remove any other proto-palettes that we encompass
// (Example [(0, 1), (0, 2)], inserting (0, 1, 2)) // (Example [(0, 1), (0, 2)], inserting (0, 1, 2))
/* /*
@@ -1166,7 +1170,7 @@ void process() {
* Investigation is necessary, especially if pathological cases are found. * Investigation is necessary, especially if pathological cases are found.
* *
* for (size_t i = protoPalettes.size(); --i != n;) { * for (size_t i = protoPalettes.size(); --i != n;) {
* if (tileColors.compare(protoPalettes[i]) == ProtoPalette::WE_BIGGER) { * if (protoPalette.compare(protoPalettes[i]) == ProtoPalette::WE_BIGGER) {
* protoPalettes.erase(protoPalettes.begin() + i); * protoPalettes.erase(protoPalettes.begin() + i);
* } * }
* } * }
@@ -1183,17 +1187,6 @@ void process() {
} }
} }
if (nbColorsInTile > options.maxOpaqueColors()) {
fatal(
"Tile at (%" PRIu32 ", %" PRIu32 ") has %" PRIu8 " opaque colors, more than %" PRIu8
"!",
tile.x,
tile.y,
nbColorsInTile,
options.maxOpaqueColors()
);
}
attrs.protoPaletteID = protoPalettes.size(); attrs.protoPaletteID = protoPalettes.size();
if (protoPalettes.size() == AttrmapEntry::transparent) { // Check for overflow if (protoPalettes.size() == AttrmapEntry::transparent) { // Check for overflow
fatal( fatal(
@@ -1201,7 +1194,7 @@ void process() {
AttrmapEntry::transparent AttrmapEntry::transparent
); );
} }
protoPalettes.push_back(tileColors); protoPalettes.push_back(protoPalette);
continue_visiting_tiles:; continue_visiting_tiles:;
} }
@@ -1251,7 +1244,7 @@ continue_visiting_tiles:;
if (!options.output.empty()) { if (!options.output.empty()) {
options.verbosePrint(Options::VERB_LOG_ACT, "Generating unoptimized tile data...\n"); options.verbosePrint(Options::VERB_LOG_ACT, "Generating unoptimized tile data...\n");
unoptimized::outputTileData(png, attrmap, palettes, mappings); outputUnoptimizedTileData(png, attrmap, palettes, mappings);
} }
if (!options.tilemap.empty() || !options.attrmap.empty() || !options.palmap.empty()) { if (!options.tilemap.empty() || !options.attrmap.empty() || !options.palmap.empty()) {
@@ -1259,12 +1252,12 @@ continue_visiting_tiles:;
Options::VERB_LOG_ACT, Options::VERB_LOG_ACT,
"Generating unoptimized tilemap and/or attrmap and/or palmap...\n" "Generating unoptimized tilemap and/or attrmap and/or palmap...\n"
); );
unoptimized::outputMaps(attrmap, mappings); outputUnoptimizedMaps(attrmap, mappings);
} }
} else { } else {
// All of these require the deduplication process to be performed to be output // All of these require the deduplication process to be performed to be output
options.verbosePrint(Options::VERB_LOG_ACT, "Deduplicating tiles...\n"); options.verbosePrint(Options::VERB_LOG_ACT, "Deduplicating tiles...\n");
optimized::UniqueTiles tiles = optimized::dedupTiles(png, attrmap, palettes, mappings); UniqueTiles tiles = dedupTiles(png, attrmap, palettes, mappings);
if (tiles.size() > options.maxNbTiles[0] + options.maxNbTiles[1]) { if (tiles.size() > options.maxNbTiles[0] + options.maxNbTiles[1]) {
fatal( fatal(
@@ -1277,22 +1270,22 @@ continue_visiting_tiles:;
if (!options.output.empty()) { if (!options.output.empty()) {
options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized tile data...\n"); options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized tile data...\n");
optimized::outputTileData(tiles); outputTileData(tiles);
} }
if (!options.tilemap.empty()) { if (!options.tilemap.empty()) {
options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized tilemap...\n"); options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized tilemap...\n");
optimized::outputTilemap(attrmap); outputTilemap(attrmap);
} }
if (!options.attrmap.empty()) { if (!options.attrmap.empty()) {
options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized attrmap...\n"); options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized attrmap...\n");
optimized::outputAttrmap(attrmap, mappings); outputAttrmap(attrmap, mappings);
} }
if (!options.palmap.empty()) { if (!options.palmap.empty()) {
options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized palmap...\n"); options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized palmap...\n");
optimized::outputPalmap(attrmap, mappings); outputPalmap(attrmap, mappings);
} }
} }
} }

View File

@@ -6,7 +6,7 @@
#include "helpers.hpp" #include "helpers.hpp"
bool ProtoPalette::add(uint16_t color) { void ProtoPalette::add(uint16_t color) {
size_t i = 0; size_t i = 0;
// Seek the first slot greater than the new color // Seek the first slot greater than the new color
@@ -16,12 +16,12 @@ bool ProtoPalette::add(uint16_t color) {
++i; ++i;
if (i == _colorIndices.size()) { if (i == _colorIndices.size()) {
// We reached the end of the array without finding the color, so it's a new one. // We reached the end of the array without finding the color, so it's a new one.
return true; return;
} }
} }
// If we found it, great! Nothing else to do. // If we found it, great! Nothing else to do.
if (_colorIndices[i] == color) { if (_colorIndices[i] == color) {
return false; return;
} }
// Swap entries until the end // Swap entries until the end
@@ -30,12 +30,11 @@ bool ProtoPalette::add(uint16_t color) {
++i; ++i;
if (i == _colorIndices.size()) { if (i == _colorIndices.size()) {
// The set is full, but doesn't include the new color. // The set is full, but doesn't include the new color.
return true; return;
} }
} }
// Write that last one into the new slot // Write that last one into the new slot
_colorIndices[i] = color; _colorIndices[i] = color;
return true;
} }
ProtoPalette::ComparisonResult ProtoPalette::compare(ProtoPalette const &other) const { ProtoPalette::ComparisonResult ProtoPalette::compare(ProtoPalette const &other) const {

View File

@@ -12,7 +12,7 @@
#include <string.h> #include <string.h>
#include <vector> #include <vector>
#include "defaultinitalloc.hpp" #include "defaultinitvec.hpp"
#include "file.hpp" #include "file.hpp"
#include "helpers.hpp" // assume #include "helpers.hpp" // assume
@@ -164,7 +164,7 @@ void reverse() {
// Pick the smallest width that will result in a landscape-aspect rectangular image. // Pick the smallest width that will result in a landscape-aspect rectangular image.
// Thus a prime number of tiles will result in a horizontal row. // Thus a prime number of tiles will result in a horizontal row.
// This avoids redundancy with `-r 1` which results in a vertical column. // This avoids redundancy with `-r 1` which results in a vertical column.
width = (size_t)ceil(sqrt(mapSize)); width = static_cast<size_t>(ceil(sqrt(mapSize)));
for (; width < mapSize; ++width) { for (; width < mapSize; ++width) {
if (mapSize % width == 0) if (mapSize % width == 0)
break; break;
@@ -389,7 +389,9 @@ void reverse() {
palmap = readInto(options.palmap); palmap = readInto(options.palmap);
if (palmap->size() != mapSize) { if (palmap->size() != mapSize) {
fatal( fatal(
"Palette map size (%zu tiles) doesn't match image size (%zu)", palmap->size(), mapSize "Palette map size (%zu tiles) doesn't match image size (%zu)",
palmap->size(),
mapSize
); );
} }
} }

View File

@@ -248,7 +248,7 @@ static void placeSection(Section &section) {
bankMem.insert( bankMem.insert(
bankMem.begin() + spaceIdx + 1, bankMem.begin() + spaceIdx + 1,
{.address = sectionEnd, {.address = sectionEnd,
.size = (uint16_t)(freeSpace.address + freeSpace.size - sectionEnd)} .size = static_cast<uint16_t>(freeSpace.address + freeSpace.size - sectionEnd)}
); );
// **`freeSpace` cannot be reused from this point on, because `bankMem.insert` // **`freeSpace` cannot be reused from this point on, because `bankMem.insert`
// invalidates all references to itself!** // invalidates all references to itself!**
@@ -279,7 +279,7 @@ static void placeSection(Section &section) {
sizeof(where), sizeof(where),
"in bank $%02" PRIx32 " with align mask $%" PRIx16, "in bank $%02" PRIx32 " with align mask $%" PRIx16,
section.bank, section.bank,
(uint16_t)~section.alignMask static_cast<uint16_t>(~section.alignMask)
); );
else else
snprintf(where, sizeof(where), "in bank $%02" PRIx32, section.bank); snprintf(where, sizeof(where), "in bank $%02" PRIx32, section.bank);
@@ -291,7 +291,7 @@ static void placeSection(Section &section) {
where, where,
sizeof(where), sizeof(where),
"with align mask $%" PRIx16 " and offset $%" PRIx16, "with align mask $%" PRIx16 " and offset $%" PRIx16,
(uint16_t)~section.alignMask, static_cast<uint16_t>(~section.alignMask),
section.alignOfs section.alignOfs
); );
else else

View File

@@ -209,8 +209,8 @@ static void parseScrambleSpec(char const *spec) {
// Remember where the region's name begins and ends // Remember where the region's name begins and ends
char const *regionName = spec; char const *regionName = spec;
size_t regionNameLen = strcspn(spec, "=, \t"); size_t regionNameLen = strcspn(spec, "=, \t");
// Length of region name string slice for printing, truncated if too long // Length of region name string slice for print formatting, truncated if too long
int regionNamePrintLen = regionNameLen > INT_MAX ? INT_MAX : (int)regionNameLen; int regionNameFmtLen = regionNameLen > INT_MAX ? INT_MAX : static_cast<int>(regionNameLen);
ScrambledRegion region = SCRAMBLE_UNK; ScrambledRegion region = SCRAMBLE_UNK;
// If this trips, `spec` must be pointing at a ',' or '=' (or NUL) due to the assumption // If this trips, `spec` must be pointing at a ',' or '=' (or NUL) due to the assumption
@@ -228,7 +228,7 @@ static void parseScrambleSpec(char const *spec) {
spec += regionNameLen + strspn(&spec[regionNameLen], " \t"); spec += regionNameLen + strspn(&spec[regionNameLen], " \t");
if (*spec != '\0' && *spec != ',' && *spec != '=') { if (*spec != '\0' && *spec != ',' && *spec != '=') {
argErr( argErr(
'S', "Unexpected '%c' after region name \"%.*s\"", regionNamePrintLen, regionName 'S', "Unexpected '%c' after region name \"%.*s\"", regionNameFmtLen, regionName
); );
// Skip to next ',' or '=' (or NUL) and keep parsing // Skip to next ',' or '=' (or NUL) and keep parsing
spec += 1 + strcspn(&spec[1], ",="); spec += 1 + strcspn(&spec[1], ",=");
@@ -246,7 +246,7 @@ static void parseScrambleSpec(char const *spec) {
} }
if (region == SCRAMBLE_UNK) if (region == SCRAMBLE_UNK)
argErr('S', "Unknown region \"%.*s\"", regionNamePrintLen, regionName); argErr('S', "Unknown region \"%.*s\"", regionNameFmtLen, regionName);
if (*spec == '=') { if (*spec == '=') {
spec++; // `strtoul` will skip the whitespace on its own spec++; // `strtoul` will skip the whitespace on its own
@@ -254,7 +254,7 @@ static void parseScrambleSpec(char const *spec) {
char *endptr; char *endptr;
if (*spec == '\0' || *spec == ',') { if (*spec == '\0' || *spec == ',') {
argErr('S', "Empty limit for region \"%.*s\"", regionNamePrintLen, regionName); argErr('S', "Empty limit for region \"%.*s\"", regionNameFmtLen, regionName);
goto next; goto next;
} }
limit = strtoul(spec, &endptr, 10); limit = strtoul(spec, &endptr, 10);
@@ -263,7 +263,7 @@ static void parseScrambleSpec(char const *spec) {
argErr( argErr(
'S', 'S',
"Invalid non-numeric limit for region \"%.*s\"", "Invalid non-numeric limit for region \"%.*s\"",
regionNamePrintLen, regionNameFmtLen,
regionName regionName
); );
endptr = strchr(endptr, ','); endptr = strchr(endptr, ',');
@@ -274,7 +274,7 @@ static void parseScrambleSpec(char const *spec) {
argErr( argErr(
'S', 'S',
"Limit for region \"%.*s\" may not exceed %" PRIu16, "Limit for region \"%.*s\" may not exceed %" PRIu16,
regionNamePrintLen, regionNameFmtLen,
regionName, regionName,
scrambleSpecs[region].max scrambleSpecs[region].max
); );
@@ -298,7 +298,7 @@ static void parseScrambleSpec(char const *spec) {
// Only WRAMX can be implied, since ROMX and SRAM size may vary // Only WRAMX can be implied, since ROMX and SRAM size may vary
scrambleWRAMX = 7; scrambleWRAMX = 7;
} else { } else {
argErr('S', "Cannot imply limit for region \"%.*s\"", regionNamePrintLen, regionName); argErr('S', "Cannot imply limit for region \"%.*s\"", regionNameFmtLen, regionName);
} }
next: // Can't `continue` a `for` loop with this nontrivial iteration logic next: // Can't `continue` a `for` loop with this nontrivial iteration logic

View File

@@ -30,23 +30,21 @@ static std::vector<std::vector<FileStackNode>> nodes;
// Helper functions for reading object files // Helper functions for reading object files
// Internal, DO NOT USE. // For internal use only by `tryReadLong` and `tryGetc`!
// For helper wrapper macros defined below, such as `tryReadLong`
#define tryRead(func, type, errval, vartype, var, file, ...) \ #define tryRead(func, type, errval, vartype, var, file, ...) \
do { \ do { \
FILE *tmpFile = file; \ FILE *tmpFile = file; \
type tmpVal = func(tmpFile); \ type tmpVal = func(tmpFile); \
/* TODO: maybe mark the condition as `unlikely`; how to do that portably? */ \
if (tmpVal == (errval)) { \ if (tmpVal == (errval)) { \
errx(__VA_ARGS__, feof(tmpFile) ? "Unexpected end of file" : strerror(errno)); \ errx(__VA_ARGS__, feof(tmpFile) ? "Unexpected end of file" : strerror(errno)); \
} \ } \
var = (vartype)tmpVal; \ var = static_cast<vartype>(tmpVal); \
} while (0) } while (0)
/* /*
* Reads an unsigned long (32-bit) value from a file. * Reads an unsigned long (32-bit) value from a file.
* @param file The file to read from. This will read 4 bytes from the file. * @param file The file to read from. This will read 4 bytes from the file.
* @return The value read, cast to a int64_t, or -1 on failure. * @return The value read, cast to a int64_t, or `INT64_MAX` on failure.
*/ */
static int64_t readLong(FILE *file) { static int64_t readLong(FILE *file) {
uint32_t value = 0; uint32_t value = 0;
@@ -63,7 +61,7 @@ static int64_t readLong(FILE *file) {
// `uint8_t`, because int is large enough to hold a byte. This // `uint8_t`, because int is large enough to hold a byte. This
// however causes values larger than 127 to be too large when // however causes values larger than 127 to be too large when
// shifted, potentially triggering undefined behavior. // shifted, potentially triggering undefined behavior.
value |= (unsigned int)byte << shift; value |= static_cast<unsigned int>(byte) << shift;
} }
return value; return value;
} }
@@ -128,7 +126,7 @@ static void readFileStackNode(
uint32_t parentID; uint32_t parentID;
tryReadLong(parentID, file, "%s: Cannot read node #%" PRIu32 "'s parent ID: %s", fileName, i); tryReadLong(parentID, file, "%s: Cannot read node #%" PRIu32 "'s parent ID: %s", fileName, i);
node.parent = parentID != (uint32_t)-1 ? &fileNodes[parentID] : nullptr; node.parent = parentID != UINT32_MAX ? &fileNodes[parentID] : nullptr;
tryReadLong( tryReadLong(
node.lineNo, file, "%s: Cannot read node #%" PRIu32 "'s line number: %s", fileName, i node.lineNo, file, "%s: Cannot read node #%" PRIu32 "'s line number: %s", fileName, i
); );
@@ -329,7 +327,7 @@ static void readPatch(
static void static void
linkPatchToPCSect(Patch &patch, std::vector<std::unique_ptr<Section>> const &fileSections) { linkPatchToPCSect(Patch &patch, std::vector<std::unique_ptr<Section>> const &fileSections) {
patch.pcSection = patch.pcSection =
patch.pcSectionID != (uint32_t)-1 ? fileSections[patch.pcSectionID].get() : nullptr; patch.pcSectionID != UINT32_MAX ? fileSections[patch.pcSectionID].get() : nullptr;
} }
/* /*
@@ -359,7 +357,7 @@ static void readSection(
); );
tryReadLong(tmp, file, "%s: Cannot read \"%s\"'s' size: %s", fileName, section.name.c_str()); tryReadLong(tmp, file, "%s: Cannot read \"%s\"'s' size: %s", fileName, section.name.c_str());
if (tmp < 0 || tmp > UINT16_MAX) if (tmp < 0 || tmp > UINT16_MAX)
errx("\"%s\"'s section size (%" PRId32 ") is invalid", section.name.c_str(), tmp); errx("\"%s\"'s section size ($%" PRIx32 ") is invalid", section.name.c_str(), tmp);
section.size = tmp; section.size = tmp;
section.offset = 0; section.offset = 0;
tryGetc( tryGetc(
@@ -379,7 +377,7 @@ static void readSection(
tryReadLong(tmp, file, "%s: Cannot read \"%s\"'s org: %s", fileName, section.name.c_str()); tryReadLong(tmp, file, "%s: Cannot read \"%s\"'s org: %s", fileName, section.name.c_str());
section.isAddressFixed = tmp >= 0; section.isAddressFixed = tmp >= 0;
if (tmp > UINT16_MAX) { if (tmp > UINT16_MAX) {
error(nullptr, 0, "\"%s\"'s org is too large (%" PRId32 ")", section.name.c_str(), tmp); error(nullptr, 0, "\"%s\"'s org is too large ($%" PRIx32 ")", section.name.c_str(), tmp);
tmp = UINT16_MAX; tmp = UINT16_MAX;
} }
section.org = tmp; section.org = tmp;
@@ -405,7 +403,7 @@ static void readSection(
error( error(
nullptr, nullptr,
0, 0,
"\"%s\"'s alignment offset is too large (%" PRId32 ")", "\"%s\"'s alignment offset is too large ($%" PRIx32 ")",
section.name.c_str(), section.name.c_str(),
tmp tmp
); );
@@ -490,7 +488,8 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
file = fopen(fileName, "rb"); file = fopen(fileName, "rb");
} else { } else {
fileName = "<stdin>"; fileName = "<stdin>";
file = fdopen(STDIN_FILENO, "rb"); // `stdin` is in text mode by default (void)setmode(STDIN_FILENO, O_BINARY);
file = stdin;
} }
if (!file) if (!file)
err("Failed to open file \"%s\"", fileName); err("Failed to open file \"%s\"", fileName);

View File

@@ -207,7 +207,8 @@ static void writeROM() {
outputFile = fopen(outputFileName, "wb"); outputFile = fopen(outputFileName, "wb");
} else { } else {
outputFileName = "<stdout>"; outputFileName = "<stdout>";
outputFile = fdopen(STDOUT_FILENO, "wb"); (void)setmode(STDOUT_FILENO, O_BINARY);
outputFile = stdout;
} }
if (!outputFile) if (!outputFile)
err("Failed to open output file \"%s\"", outputFileName); err("Failed to open output file \"%s\"", outputFileName);
@@ -222,7 +223,8 @@ static void writeROM() {
overlayFile = fopen(overlayFileName, "rb"); overlayFile = fopen(overlayFileName, "rb");
} else { } else {
overlayFileName = "<stdin>"; overlayFileName = "<stdin>";
overlayFile = fdopen(STDIN_FILENO, "rb"); (void)setmode(STDIN_FILENO, O_BINARY);
overlayFile = stdin;
} }
if (!overlayFile) if (!overlayFile)
err("Failed to open overlay file \"%s\"", overlayFileName); err("Failed to open overlay file \"%s\"", overlayFileName);
@@ -264,15 +266,15 @@ static bool isLegalForSymName(char c) {
|| c == '@' || c == '#' || c == '$' || c == '.'; || c == '@' || c == '#' || c == '$' || c == '.';
} }
// Prints a symbol's name to `symFile`, assuming that the first character is legal. // Prints a symbol's name to a file, assuming that the first character is legal.
// Illegal characters are UTF-8-decoded (errors are replaced by U+FFFD) and emitted as `\u`/`\U`. // Illegal characters are UTF-8-decoded (errors are replaced by U+FFFD) and emitted as `\u`/`\U`.
static void printSymName(char const *name) { static void printSymName(std::string const &name, FILE *file) {
for (char const *ptr = name; *ptr != '\0';) { for (char const *ptr = name.c_str(); *ptr != '\0';) {
char c = *ptr; char c = *ptr;
if (isLegalForSymName(c)) { if (isLegalForSymName(c)) {
// Output legal ASCII characters as-is // Output legal ASCII characters as-is
putc(c, symFile); putc(c, file);
++ptr; ++ptr;
} else { } else {
// Output illegal characters using Unicode escapes // Output illegal characters using Unicode escapes
@@ -293,7 +295,7 @@ static void printSymName(char const *name) {
++ptr; ++ptr;
} while (state != 0); } while (state != 0);
fprintf(symFile, codepoint <= 0xFFFF ? "\\u%04" PRIx32 : "\\U%08" PRIx32, codepoint); fprintf(file, codepoint <= 0xFFFF ? "\\u%04" PRIx32 : "\\U%08" PRIx32, codepoint);
} }
} }
} }
@@ -361,7 +363,7 @@ static void writeSymBank(SortedSections const &bankSections, SectionType type, u
if (!sym->name.empty() && canStartSymName(sym->name[0])) if (!sym->name.empty() && canStartSymName(sym->name[0]))
symList.push_back({ symList.push_back({
.sym = sym, .sym = sym,
.addr = (uint16_t)(sym->label().offset + sect->org), .addr = static_cast<uint16_t>(sym->label().offset + sect->org),
}); });
} }
}); });
@@ -374,7 +376,7 @@ static void writeSymBank(SortedSections const &bankSections, SectionType type, u
for (SortedSymbol &sym : symList) { for (SortedSymbol &sym : symList) {
fprintf(symFile, "%02" PRIx32 ":%04" PRIx16 " ", symBank, sym.addr); fprintf(symFile, "%02" PRIx32 ":%04" PRIx16 " ", symBank, sym.addr);
printSymName(sym.sym->name.c_str()); printSymName(sym.sym->name, symFile);
putc('\n', symFile); putc('\n', symFile);
} }
} }
@@ -394,6 +396,31 @@ static void writeEmptySpace(uint16_t begin, uint16_t end) {
} }
} }
// Prints a section's name to a file.
static void printSectionName(std::string const &name, FILE *file) {
for (char c : name) {
// Escape characters that need escaping
switch (c) {
case '\n':
fputs("\\n", file);
break;
case '\r':
fputs("\\r", file);
break;
case '\t':
fputs("\\t", file);
break;
case '\\':
case '"':
putc('\\', file);
[[fallthrough]];
default:
putc(c, file);
break;
}
}
}
/* /*
* Write a bank's contents to the map file * Write a bank's contents to the map file
*/ */
@@ -425,35 +452,22 @@ static void writeMapBank(SortedSections const &sectList, SectionType type, uint3
prevEndAddr = sect->org + sect->size; prevEndAddr = sect->org + sect->size;
fprintf(mapFile, "\tSECTION: $%04" PRIx16, sect->org);
if (sect->size != 0) if (sect->size != 0)
fprintf( fprintf(mapFile, "-$%04x", prevEndAddr - 1);
mapFile, fprintf(mapFile, " ($%04" PRIx16 " byte%s) [\"", sect->size, sect->size == 1 ? "" : "s");
"\tSECTION: $%04" PRIx16 "-$%04x ($%04" PRIx16 " byte%s) [\"%s\"]\n", printSectionName(sect->name, mapFile);
sect->org, fputs("\"]\n", mapFile);
prevEndAddr - 1,
sect->size,
sect->size == 1 ? "" : "s",
sect->name.c_str()
);
else
fprintf(
mapFile,
"\tSECTION: $%04" PRIx16 " (0 bytes) [\"%s\"]\n",
sect->org,
sect->name.c_str()
);
if (!noSymInMap) { if (!noSymInMap) {
// Also print symbols in the following "pieces" // Also print symbols in the following "pieces"
for (uint16_t org = sect->org; sect; sect = sect->nextu.get()) { for (uint16_t org = sect->org; sect; sect = sect->nextu.get()) {
for (Symbol *sym : sect->symbols) for (Symbol *sym : sect->symbols) {
// Space matches "\tSECTION: $xxxx ..." // Space matches "\tSECTION: $xxxx ..."
fprintf( fprintf(mapFile, "\t $%04" PRIx32 " = ", sym->label().offset + org);
mapFile, printSymName(sym->name, mapFile);
"\t $%04" PRIx32 " = %s\n", putc('\n', mapFile);
sym->label().offset + org, }
sym->name.c_str()
);
if (sect->nextu) { if (sect->nextu) {
// Announce the following "piece" // Announce the following "piece"
@@ -545,7 +559,8 @@ static void writeSym() {
symFile = fopen(symFileName, "w"); symFile = fopen(symFileName, "w");
} else { } else {
symFileName = "<stdout>"; symFileName = "<stdout>";
symFile = fdopen(STDOUT_FILENO, "w"); (void)setmode(STDOUT_FILENO, O_TEXT); // May have been set to O_BINARY previously
symFile = stdout;
} }
if (!symFile) if (!symFile)
err("Failed to open sym file \"%s\"", symFileName); err("Failed to open sym file \"%s\"", symFileName);
@@ -576,7 +591,9 @@ static void writeSym() {
for (Symbol *sym : constants) { for (Symbol *sym : constants) {
int32_t val = sym->data.get<int32_t>(); int32_t val = sym->data.get<int32_t>();
int width = val < 0x100 ? 2 : val < 0x10000 ? 4 : 8; int width = val < 0x100 ? 2 : val < 0x10000 ? 4 : 8;
fprintf(symFile, "%0*" PRIx32 " %s\n", width, val, sym->name.c_str()); fprintf(symFile, "%0*" PRIx32 " ", width, val);
printSymName(sym->name, symFile);
putc('\n', symFile);
} }
} }
@@ -589,7 +606,8 @@ static void writeMap() {
mapFile = fopen(mapFileName, "w"); mapFile = fopen(mapFileName, "w");
} else { } else {
mapFileName = "<stdout>"; mapFileName = "<stdout>";
mapFile = fdopen(STDOUT_FILENO, "w"); (void)setmode(STDOUT_FILENO, O_TEXT); // May have been set to O_BINARY previously
mapFile = stdout;
} }
if (!mapFile) if (!mapFile)
err("Failed to open map file \"%s\"", mapFileName); err("Failed to open map file \"%s\"", mapFileName);

View File

@@ -53,7 +53,7 @@ static uint32_t getRPNByte(uint8_t const *&expression, int32_t &size, Patch cons
} }
static Symbol const *getSymbol(std::vector<Symbol> const &symbolList, uint32_t index) { static Symbol const *getSymbol(std::vector<Symbol> const &symbolList, uint32_t index) {
assume(index != (uint32_t)-1); // PC needs to be handled specially, not here assume(index != UINT32_MAX); // PC needs to be handled specially, not here
Symbol const &symbol = symbolList[index]; Symbol const &symbol = symbolList[index];
// If the symbol is defined elsewhere... // If the symbol is defined elsewhere...
@@ -73,12 +73,12 @@ static Symbol const *getSymbol(std::vector<Symbol> const &symbolList, uint32_t i
*/ */
static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fileSymbols) { static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fileSymbols) {
uint8_t const *expression = patch.rpnExpression.data(); uint8_t const *expression = patch.rpnExpression.data();
int32_t size = (int32_t)patch.rpnExpression.size(); int32_t size = static_cast<int32_t>(patch.rpnExpression.size());
rpnStack.clear(); rpnStack.clear();
while (size > 0) { while (size > 0) {
RPNCommand command = (RPNCommand)getRPNByte(expression, size, patch); RPNCommand command = static_cast<RPNCommand>(getRPNByte(expression, size, patch));
int32_t value; int32_t value;
isError = false; isError = false;
@@ -101,9 +101,10 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
case RPN_DIV: case RPN_DIV:
value = popRPN(patch); value = popRPN(patch);
if (value == 0) { if (value == 0) {
if (!isError) if (!isError) {
error(patch.src, patch.lineNo, "Division by 0"); error(patch.src, patch.lineNo, "Division by 0");
isError = true; isError = true;
}
popRPN(patch); popRPN(patch);
value = INT32_MAX; value = INT32_MAX;
} else { } else {
@@ -113,9 +114,10 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
case RPN_MOD: case RPN_MOD:
value = popRPN(patch); value = popRPN(patch);
if (value == 0) { if (value == 0) {
if (!isError) if (!isError) {
error(patch.src, patch.lineNo, "Modulo by 0"); error(patch.src, patch.lineNo, "Modulo by 0");
isError = true; isError = true;
}
popRPN(patch); popRPN(patch);
value = 0; value = 0;
} else { } else {
@@ -128,9 +130,10 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
case RPN_EXP: case RPN_EXP:
value = popRPN(patch); value = popRPN(patch);
if (value < 0) { if (value < 0) {
if (!isError) if (!isError) {
error(patch.src, patch.lineNo, "Exponent by negative"); error(patch.src, patch.lineNo, "Exponent by negative value %" PRId32, value);
isError = true; isError = true;
}
popRPN(patch); popRPN(patch);
value = 0; value = 0;
} else { } else {
@@ -147,11 +150,11 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
case RPN_BITWIDTH: case RPN_BITWIDTH:
value = popRPN(patch); value = popRPN(patch);
value = value != 0 ? 32 - clz((uint32_t)value) : 0; value = value != 0 ? 32 - clz(static_cast<uint32_t>(value)) : 0;
break; break;
case RPN_TZCOUNT: case RPN_TZCOUNT:
value = popRPN(patch); value = popRPN(patch);
value = value != 0 ? ctz((uint32_t)value) : 32; value = value != 0 ? ctz(static_cast<uint32_t>(value)) : 32;
break; break;
case RPN_OR: case RPN_OR:
@@ -246,7 +249,7 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
case RPN_BANK_SECT: { case RPN_BANK_SECT: {
// `expression` is not guaranteed to be '\0'-terminated. If it is not, // `expression` is not guaranteed to be '\0'-terminated. If it is not,
// `getRPNByte` will have a fatal internal error. // `getRPNByte` will have a fatal internal error.
char const *name = (char const *)expression; char const *name = reinterpret_cast<char const *>(expression);
while (getRPNByte(expression, size, patch)) while (getRPNByte(expression, size, patch))
; ;
@@ -277,7 +280,7 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
case RPN_SIZEOF_SECT: { case RPN_SIZEOF_SECT: {
// This has assumptions commented in the `RPN_BANK_SECT` case above. // This has assumptions commented in the `RPN_BANK_SECT` case above.
char const *name = (char const *)expression; char const *name = reinterpret_cast<char const *>(expression);
while (getRPNByte(expression, size, patch)) while (getRPNByte(expression, size, patch))
; ;
@@ -298,7 +301,7 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
case RPN_STARTOF_SECT: { case RPN_STARTOF_SECT: {
// This has assumptions commented in the `RPN_BANK_SECT` case above. // This has assumptions commented in the `RPN_BANK_SECT` case above.
char const *name = (char const *)expression; char const *name = reinterpret_cast<char const *>(expression);
while (getRPNByte(expression, size, patch)) while (getRPNByte(expression, size, patch))
; ;
@@ -342,10 +345,24 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
case RPN_HRAM: case RPN_HRAM:
value = popRPN(patch); value = popRPN(patch);
if (!isError && (value < 0 || (value > 0xFF && value < 0xFF00) || value > 0xFFFF)) { if (value < 0 || (value > 0xFF && value < 0xFF00) || value > 0xFFFF) {
error(patch.src, patch.lineNo, "Value %" PRId32 " is not in HRAM range", value); if (!isError) {
error(
patch.src,
patch.lineNo,
"Address $%" PRIx32 " for LDH is not in HRAM range",
value
);
isError = true; isError = true;
} }
value = 0;
} else if (value >= 0 && value <= 0xFF) {
warning(
patch.src,
patch.lineNo,
"LDH is deprecated with values from $00 to $FF; use $FF00 to $FFFF"
);
}
value &= 0xFF; value &= 0xFF;
break; break;
@@ -354,10 +371,12 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
// Acceptable values are 0x00, 0x08, 0x10, ..., 0x38 // Acceptable values are 0x00, 0x08, 0x10, ..., 0x38
// They can be easily checked with a bitmask // They can be easily checked with a bitmask
if (value & ~0x38) { if (value & ~0x38) {
if (!isError) if (!isError) {
error(patch.src, patch.lineNo, "Value %" PRId32 " is not a RST vector", value); error(patch.src, patch.lineNo, "Value $%" PRIx32 " is not a RST vector", value);
isError = true; isError = true;
} }
value = 0;
}
value |= 0xC7; value |= 0xC7;
break; break;
@@ -415,7 +434,7 @@ void patch_CheckAssertions() {
for (Assertion &assert : assertions) { for (Assertion &assert : assertions) {
int32_t value = computeRPNExpr(assert.patch, *assert.fileSymbols); int32_t value = computeRPNExpr(assert.patch, *assert.fileSymbols);
AssertionType type = (AssertionType)assert.patch.type; AssertionType type = static_cast<AssertionType>(assert.patch.type);
if (!isError && !value) { if (!isError && !value) {
switch (type) { switch (type) {

View File

@@ -55,7 +55,7 @@
/******************** Tokens and data types ********************/ /******************** Tokens and data types ********************/
%token YYEOF 0 "end of file" %token YYEOF 0 "end of file"
%token newline %token newline "end of line"
%token COMMA "," %token COMMA ","
%token ORG "ORG" %token ORG "ORG"
FLOATING "FLOATING" FLOATING "FLOATING"
@@ -557,8 +557,8 @@ static void alignTo(uint32_t alignment, uint32_t alignOfs) {
"Cannot align: the next suitable address after $%04" PRIx16 " is $%04" PRIx16 "Cannot align: the next suitable address after $%04" PRIx16 " is $%04" PRIx16
", past $%04" PRIx16, ", past $%04" PRIx16,
pc, pc,
(uint16_t)(pc + length), static_cast<uint16_t>(pc + length),
(uint16_t)(endaddr(activeType) + 1) static_cast<uint16_t>(endaddr(activeType) + 1)
); );
return; return;
} }
@@ -588,7 +588,7 @@ static void pad(uint32_t length) {
"Cannot increase the current address by %u bytes: only %u bytes to $%04" PRIx16, "Cannot increase the current address by %u bytes: only %u bytes to $%04" PRIx16,
length, length,
typeInfo.size - offset, typeInfo.size - offset,
(uint16_t)(endaddr(activeType) + 1) static_cast<uint16_t>(endaddr(activeType) + 1)
); );
} else { } else {
pc += length; pc += length;
@@ -689,7 +689,7 @@ static void placeSection(std::string const &name, bool isOptional) {
name.c_str(), name.c_str(),
org, org,
alignment, alignment,
(uint16_t)(org & section->alignMask), static_cast<uint16_t>(org & section->alignMask),
alignment, alignment,
section->alignOfs section->alignOfs
); );

View File

@@ -468,7 +468,7 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
getToken(nullptr, "'R' line is too short"); getToken(nullptr, "'R' line is too short");
areaIdx = parseByte(where, lineNo, token, numberType); areaIdx = parseByte(where, lineNo, token, numberType);
getToken(nullptr, "'R' line is too short"); getToken(nullptr, "'R' line is too short");
areaIdx |= (uint16_t)parseByte(where, lineNo, token, numberType) << 8; areaIdx |= static_cast<uint16_t>(parseByte(where, lineNo, token, numberType)) << 8;
if (areaIdx >= fileSections.size()) if (areaIdx >= fileSections.size())
fatal( fatal(
&where, &where,
@@ -532,8 +532,9 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
if ((flags & 0xF0) == 0xF0) { if ((flags & 0xF0) == 0xF0) {
getToken(nullptr, "Incomplete relocation"); getToken(nullptr, "Incomplete relocation");
flags = flags = (flags & 0x0F)
(flags & 0x0F) | (uint16_t)parseByte(where, lineNo, token, numberType) << 4; | static_cast<uint16_t>(parseByte(where, lineNo, token, numberType))
<< 4;
} }
getToken(nullptr, "Incomplete relocation"); getToken(nullptr, "Incomplete relocation");
@@ -560,7 +561,7 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
uint16_t idx = parseByte(where, lineNo, token, numberType); uint16_t idx = parseByte(where, lineNo, token, numberType);
getToken(nullptr, "Incomplete relocation"); getToken(nullptr, "Incomplete relocation");
idx |= (uint16_t)parseByte(where, lineNo, token, numberType); idx |= static_cast<uint16_t>(parseByte(where, lineNo, token, numberType));
// Loudly fail on unknown flags // Loudly fail on unknown flags
if (flags & (1 << RELOC_ZPAGE | 1 << RELOC_NPAGE)) if (flags & (1 << RELOC_ZPAGE | 1 << RELOC_NPAGE))
@@ -646,7 +647,7 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
patch.rpnExpression.resize(1 + sym.name.length() - 2 + 1); patch.rpnExpression.resize(1 + sym.name.length() - 2 + 1);
patch.rpnExpression[0] = RPN_SIZEOF_SECT; patch.rpnExpression[0] = RPN_SIZEOF_SECT;
memcpy( memcpy(
(char *)&patch.rpnExpression[1], reinterpret_cast<char *>(&patch.rpnExpression[1]),
&sym.name.c_str()[2], &sym.name.c_str()[2],
sym.name.length() - 2 + 1 sym.name.length() - 2 + 1
); );
@@ -654,7 +655,7 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
patch.rpnExpression.resize(1 + sym.name.length() - 2 + 1); patch.rpnExpression.resize(1 + sym.name.length() - 2 + 1);
patch.rpnExpression[0] = RPN_STARTOF_SECT; patch.rpnExpression[0] = RPN_STARTOF_SECT;
memcpy( memcpy(
(char *)&patch.rpnExpression[1], reinterpret_cast<char *>(&patch.rpnExpression[1]),
&sym.name.c_str()[2], &sym.name.c_str()[2],
sym.name.length() - 2 + 1 sym.name.length() - 2 + 1
); );
@@ -700,7 +701,11 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
patch.rpnExpression.resize(1 + name.length() + 1); patch.rpnExpression.resize(1 + name.length() + 1);
patch.rpnExpression[0] = RPN_STARTOF_SECT; patch.rpnExpression[0] = RPN_STARTOF_SECT;
// The cast is fine, it's just different signedness // The cast is fine, it's just different signedness
memcpy((char *)&patch.rpnExpression[1], name.c_str(), name.length() + 1); memcpy(
reinterpret_cast<char *>(&patch.rpnExpression[1]),
name.c_str(),
name.length() + 1
);
} }
patch.rpnExpression.push_back(RPN_CONST); patch.rpnExpression.push_back(RPN_CONST);

View File

@@ -48,7 +48,7 @@ int32_t op_shift_left(int32_t value, int32_t amount) {
// Use unsigned to force a bitwise shift // Use unsigned to force a bitwise shift
// Casting back is OK because the types implement two's complement behavior // Casting back is OK because the types implement two's complement behavior
return (uint32_t)value << amount; return static_cast<uint32_t>(value) << amount;
} }
int32_t op_shift_right(int32_t value, int32_t amount) { int32_t op_shift_right(int32_t value, int32_t amount) {
@@ -63,7 +63,7 @@ int32_t op_shift_right(int32_t value, int32_t amount) {
return op_shift_left(value, -amount); return op_shift_left(value, -amount);
if (value > 0) if (value > 0)
return (uint32_t)value >> amount; return static_cast<uint32_t>(value) >> amount;
// Calculate an OR mask for sign extension // Calculate an OR mask for sign extension
// 1->0x80000000, 2->0xC0000000, ..., 31->0xFFFFFFFE // 1->0x80000000, 2->0xC0000000, ..., 31->0xFFFFFFFE
@@ -71,7 +71,7 @@ int32_t op_shift_right(int32_t value, int32_t amount) {
// The C++ standard leaves shifting right negative values // The C++ standard leaves shifting right negative values
// undefined, so use a left shift manually sign-extended // undefined, so use a left shift manually sign-extended
return ((uint32_t)value >> amount) | amount_high_bits; return (static_cast<uint32_t>(value) >> amount) | amount_high_bits;
} }
int32_t op_shift_right_unsigned(int32_t value, int32_t amount) { int32_t op_shift_right_unsigned(int32_t value, int32_t amount) {
@@ -83,5 +83,5 @@ int32_t op_shift_right_unsigned(int32_t value, int32_t amount) {
if (amount < 0) if (amount < 0)
return op_shift_left(value, -amount); return op_shift_left(value, -amount);
return (uint32_t)value >> amount; return static_cast<uint32_t>(value) >> amount;
} }

View File

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

View File

@@ -1,13 +1,18 @@
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
OPTION(USE_NONFREE_TESTS "run tests that build nonfree codebases" ON)
if(NOT USE_NONFREE_TESTS)
set(ONLY_FREE "--only-free")
endif()
add_executable(randtilegen gfx/randtilegen.cpp) add_executable(randtilegen gfx/randtilegen.cpp)
add_executable(rgbgfx_test gfx/rgbgfx_test.cpp) add_executable(rgbgfx_test gfx/rgbgfx_test.cpp)
set_target_properties(randtilegen rgbgfx_test PROPERTIES
# hack for MSVC: no-op generator expression to stop generation of "per-configuration subdirectory"
RUNTIME_OUTPUT_DIRECTORY $<1:${CMAKE_CURRENT_SOURCE_DIR}/gfx>)
install(TARGETS randtilegen rgbgfx_test configure_file(CTestCustom.cmake.in ${CMAKE_BINARY_DIR}/CTestCustom.cmake)
DESTINATION ${rgbds_SOURCE_DIR}/test/gfx
COMPONENT "Test support programs"
EXCLUDE_FROM_ALL
)
foreach(TARGET randtilegen rgbgfx_test) foreach(TARGET randtilegen rgbgfx_test)
if(LIBPNG_FOUND) # pkg-config if(LIBPNG_FOUND) # pkg-config
@@ -20,3 +25,8 @@ foreach(TARGET randtilegen rgbgfx_test)
target_link_libraries(${TARGET} PRIVATE ${PNG_LIBRARIES}) target_link_libraries(${TARGET} PRIVATE ${PNG_LIBRARIES})
endif() endif()
endforeach() endforeach()
add_test(NAME all
COMMAND ./run-tests.sh ${ONLY_FREE}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)

View File

@@ -0,0 +1 @@
set(CTEST_CUSTOM_PRE_TEST "bash -c 'cd @CMAKE_CURRENT_SOURCE_DIR@\; ./fetch-test-deps.sh @ONLY_FREE@'")

View File

@@ -1,5 +1,5 @@
error: charmap-empty.asm(1): error: charmap-empty.asm(1):
Cannot map an empty string Cannot map an empty string
error: charmap-empty.asm(2): error: charmap-empty.asm(2):
syntax error, unexpected newline syntax error, unexpected end of line
error: Assembly aborted (2 errors)! error: Assembly aborted (2 errors)!

View File

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

View File

@@ -0,0 +1,31 @@
SECTION "LDIO", ROM0
ldh [c], a
ldh a, [c]
ldh [$11], a
ldh a, [$11]
ld [$ff00+c], a
ld a, [$ff00+c]
ld [$ff11], a
ld a, [$ff11]
ldio [c], a
ldio a, [c]
ldio [$ff11], a
ldio a, [$ff11]
LDH [C], A
LDH A, [C]
LDH [$11], A
LDH A, [$11]
LD [$FF00+C], A
LD A, [$FF00+C]
LD [$FF11], A
LD A, [$FF11]
LDIO [C], A
LDIO A, [C]
LDIO [$FF11], A
LDIO A, [$FF11]

View File

@@ -0,0 +1,32 @@
warning: deprecated-ldio.asm(5): [-Wobsolete]
LDH is deprecated with values from $00 to $FF; use $FF00 to $FFFF
warning: deprecated-ldio.asm(6): [-Wobsolete]
LDH is deprecated with values from $00 to $FF; use $FF00 to $FFFF
warning: deprecated-ldio.asm(8): [-Wobsolete]
LD [C], A is deprecated; use LDH [C], A
warning: deprecated-ldio.asm(9): [-Wobsolete]
LD A, [C] is deprecated; use LDH A, [C]
warning: deprecated-ldio.asm(13): [-Wobsolete]
LDIO is deprecated; use LDH
warning: deprecated-ldio.asm(14): [-Wobsolete]
LDIO is deprecated; use LDH
warning: deprecated-ldio.asm(15): [-Wobsolete]
LDIO is deprecated; use LDH
warning: deprecated-ldio.asm(16): [-Wobsolete]
LDIO is deprecated; use LDH
warning: deprecated-ldio.asm(20): [-Wobsolete]
LDH is deprecated with values from $00 to $FF; use $FF00 to $FFFF
warning: deprecated-ldio.asm(21): [-Wobsolete]
LDH is deprecated with values from $00 to $FF; use $FF00 to $FFFF
warning: deprecated-ldio.asm(23): [-Wobsolete]
LD [C], A is deprecated; use LDH [C], A
warning: deprecated-ldio.asm(24): [-Wobsolete]
LD A, [C] is deprecated; use LDH A, [C]
warning: deprecated-ldio.asm(28): [-Wobsolete]
LDIO is deprecated; use LDH
warning: deprecated-ldio.asm(29): [-Wobsolete]
LDIO is deprecated; use LDH
warning: deprecated-ldio.asm(30): [-Wobsolete]
LDIO is deprecated; use LDH
warning: deprecated-ldio.asm(31): [-Wobsolete]
LDIO is deprecated; use LDH

View File

@@ -0,0 +1 @@
<EFBFBD><EFBFBD><EFBFBD><11><11><><EFBFBD><11><><11><><EFBFBD><EFBFBD><11><11><><EFBFBD><11><11><><EFBFBD><11><><11><><EFBFBD><EFBFBD><11>

View File

@@ -1,2 +1,5 @@
FATAL: endsection-in-load.asm(3): warning: endsection-in-load.asm(3): [-Wunterminated-load]
Cannot end the section within a `LOAD` block `LOAD` block without `ENDL` terminated by `ENDSECTION`
error: endsection-in-load.asm(4):
Found `ENDL` outside of a `LOAD` block
error: Assembly aborted (1 error)!

View File

@@ -1,9 +1,9 @@
SECTION "ff00+c or not to ff00+c", ROMX SECTION "ff00+c or not to ff00+c", ROMX
ld a, [$ff00 + c] ldh a, [$ff00 + c]
ld [65280 + c], a ldh [65280 + c], a
; Not ok ; Not ok
ld a, [$ff01 + c] ldh a, [$ff01 + c]
ld [xyz + c], a ldh [xyz + c], a

View File

@@ -1,5 +1,5 @@
SECTION "test", ROM0[0] SECTION "test", ROM0[0]
ld [ $ff00 + c ], a ldh [ $ff00 + c ], a
; 257 spaces exceeds both LEXER_BUF_SIZE (42) and uint8_t limit (255) ; 257 spaces exceeds both LEXER_BUF_SIZE (42) and uint8_t limit (255)
ld [ $ff00 + c ], a ldh [ $ff00 + c ], a
ld [ $ff00 + c ], a ldh [ $ff00 + c ], a

View File

@@ -1,6 +1,6 @@
SECTION "invalid", ROM0[$10000] SECTION "invalid", ROM0[$10000]
ld [hl], [hl] ld [hl], [hl]
ld a, [$00ff+c] ldh a, [$00ff+c]
ld b, [c] ld b, [c]
ld b, [bc] ld b, [bc]
ld b, [$4000] ld b, [$4000]

View File

@@ -17,7 +17,7 @@ error: invalid-opt.asm(8):
error: invalid-opt.asm(9): error: invalid-opt.asm(9):
Must specify an argument for option 'W' Must specify an argument for option 'W'
error: invalid-opt.asm(10): error: invalid-opt.asm(10):
syntax error, unexpected newline, expecting string syntax error, unexpected end of line, expecting string
error: invalid-opt.asm(11): error: invalid-opt.asm(11):
No entries in the option stack No entries in the option stack
error: Assembly aborted (11 errors)! error: Assembly aborted (11 errors)!

28
test/asm/lexer-hack.asm Normal file
View File

@@ -0,0 +1,28 @@
MACRO mac
println "got {d:_NARG} args: \#"
ENDM
; indented, these were always macro invocations
mac
mac ro
mac : ld a, 1
; in column 1, we historically treated these as labels
mac
mac ro
mac : ld b, 2
SECTION "test", ROM0
; a colon makes these into labels
Label1: ld c, 3
Label2: ld d, 4
; a macro invocation when already defined as a label
Label1 args
; and a label definition when already defined as a macro
mac: ld e, 5
; the space before the colon matters!
undef :
undef :

9
test/asm/lexer-hack.err Normal file
View File

@@ -0,0 +1,9 @@
error: lexer-hack.asm(22):
"Label1" is not a macro
error: lexer-hack.asm(24):
'mac' already defined at lexer-hack.asm(1)
error: lexer-hack.asm(27):
Macro "undef" not defined
error: lexer-hack.asm(28):
Macro "undef" not defined
error: Assembly aborted (4 errors)!

6
test/asm/lexer-hack.out Normal file
View File

@@ -0,0 +1,6 @@
got 0 args:
got 1 args: ro
got 2 args: : ld a,1
got 0 args:
got 1 args: ro
got 2 args: : ld b,2

51
test/asm/load-endings.asm Normal file
View File

@@ -0,0 +1,51 @@
MACRO data
db SECTION(@), \#
ENDM
MACRO now_in
if strcmp("\1", "nothing")
assert !strcmp(SECTION(@), \1)
else
assert !def(@)
endc
ENDM
now_in nothing
SECTION "A", ROM0
now_in "A"
data 1
LOAD "P", WRAM0
now_in "P"
data 2
; LOAD after LOAD
LOAD "Q", WRAM0
now_in "Q"
data 3
; SECTION after LOAD
SECTION "B", ROM0
now_in "B"
data 4
LOAD "R", WRAM0
now_in "R"
data 5
; PUSHS after LOAD
PUSHS
SECTION "C", ROM0
now_in "C"
data 6
LOAD "S", WRAM0
now_in "S"
data 7
; POPS after LOAD
POPS
now_in "R"
data 8
; ENDSECTION after LOAD
ENDSECTION
now_in nothing

View File

@@ -0,0 +1,8 @@
warning: load-endings.asm(23): [-Wunterminated-load]
`LOAD` block without `ENDL` terminated by `LOAD`
warning: load-endings.asm(28): [-Wunterminated-load]
`LOAD` block without `ENDL` terminated by `SECTION`
warning: load-endings.asm(45): [-Wunterminated-load]
`LOAD` block without `ENDL` terminated by `POPS`
warning: load-endings.asm(50): [-Wunterminated-load]
`LOAD` block without `ENDL` terminated by `ENDSECTION`

View File

@@ -0,0 +1 @@
BRRAPQCS

View File

@@ -1,2 +1,29 @@
SECTION "A", ROM0 SECTION "A", ROM0
AData::
LOAD FRAGMENT "RAM", WRAM0 LOAD FRAGMENT "RAM", WRAM0
AMem::
db 0, 1, 2
AMemEnd::
ENDL
ADataEnd::
dw AMem
SECTION "B", ROM0
BData::
LOAD FRAGMENT "RAM", WRAM0
BMem::
db 3, 4, 5, 6, 7
BMemEnd::
ENDL
BDataEnd::
dw BMem
SECTION "C", ROM0
CData::
LOAD FRAGMENT "RAM", WRAM0
CMem::
db 8, 9
CMemEnd::
ENDL
CDataEnd::
dw CMem

View File

@@ -1,3 +0,0 @@
error: load-fragment.asm(2):
`LOAD FRAGMENT` is not allowed
error: Assembly aborted (1 error)!

Binary file not shown.

View File

@@ -1,5 +1,5 @@
SECTION "outer", ROM0 SECTION "outer", ROM0
LOAD "inner", WRAM0 LOAD "inner1", WRAM0 ; starts "inner1"
LOAD "matryoshka", HRAM LOAD "inner2", HRAM ; ends "inner1", starts "inner2"
ENDL ENDL ; ends "inner2"
ENDL ENDL ; error

View File

@@ -1,5 +1,5 @@
error: load-in-load.asm(3): warning: load-in-load.asm(3): [-Wunterminated-load]
`LOAD` blocks cannot be nested `LOAD` block without `ENDL` terminated by `LOAD`
error: load-in-load.asm(5): error: load-in-load.asm(5):
Found `ENDL` outside of a `LOAD` block Found `ENDL` outside of a `LOAD` block
error: Assembly aborted (2 errors)! error: Assembly aborted (1 error)!

View File

@@ -6,4 +6,6 @@ error: macro-syntax.asm(8):
'\1' cannot be used outside of a macro '\1' cannot be used outside of a macro
error: macro-syntax.asm(9): error: macro-syntax.asm(9):
syntax error, unexpected ENDM syntax error, unexpected ENDM
error: Assembly aborted (4 errors)! error: macro-syntax.asm(11):
"old" is not a macro
error: Assembly aborted (5 errors)!

View File

@@ -2,6 +2,8 @@ warning: multiple-charmaps.asm(46) -> multiple-charmaps.asm::print_mapped(34): [
Treating multi-unit strings as numbers is deprecated Treating multi-unit strings as numbers is deprecated
warning: multiple-charmaps.asm(54) -> multiple-charmaps.asm::print_mapped(34): [-Wobsolete] warning: multiple-charmaps.asm(54) -> multiple-charmaps.asm::print_mapped(34): [-Wobsolete]
Treating multi-unit strings as numbers is deprecated Treating multi-unit strings as numbers is deprecated
warning: multiple-charmaps.asm(64): [-Wcharmap-redef]
Overriding charmap mapping
warning: multiple-charmaps.asm(73) -> multiple-charmaps.asm::print_mapped(34): [-Wobsolete] warning: multiple-charmaps.asm(73) -> multiple-charmaps.asm::print_mapped(34): [-Wobsolete]
Treating multi-unit strings as numbers is deprecated Treating multi-unit strings as numbers is deprecated
warning: multiple-charmaps.asm(95) -> multiple-charmaps.asm::print_mapped(34): [-Wobsolete] warning: multiple-charmaps.asm(95) -> multiple-charmaps.asm::print_mapped(34): [-Wobsolete]

View File

@@ -0,0 +1,3 @@
def p = {{a}}
def q = "{b}"
def r = "{{c}}"

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