mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 18:22:07 +00:00
Compare commits
72 Commits
v0.9.0-rc1
...
v0.9.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d63955eccd | ||
|
|
2c4fc4cbe8 | ||
|
|
7d3c31b6d8 | ||
|
|
151f83db6d | ||
|
|
22838ce2d8 | ||
|
|
b058bb6e15 | ||
|
|
36b04b5dea | ||
|
|
a7296ecb31 | ||
|
|
92917ceb2f | ||
|
|
c1c5b10082 | ||
|
|
f44de0c7ae | ||
|
|
b18cfe6bdb | ||
|
|
a8ec9228d4 | ||
|
|
c1b85554a8 | ||
|
|
b877c81c32 | ||
|
|
e66da4c8c7 | ||
|
|
573e044b30 | ||
|
|
ceb43c7aa4 | ||
|
|
eae1ecb77e | ||
|
|
c4de6c402b | ||
|
|
0b147c9386 | ||
|
|
6982c8a116 | ||
|
|
d5f39c8dce | ||
|
|
a5d18d62df | ||
|
|
a27f704c25 | ||
|
|
9216485bca | ||
|
|
c33acb905b | ||
|
|
81c3521610 | ||
|
|
e0ee9dc3ad | ||
|
|
cb546f0cd8 | ||
|
|
a60186db2f | ||
|
|
d9f87a5721 | ||
|
|
a7fdb2c3d3 | ||
|
|
5efd303b7f | ||
|
|
0d3980d039 | ||
|
|
ab6244d81c | ||
|
|
7fcf4ba60f | ||
|
|
f048cbbb11 | ||
|
|
4c495c31d9 | ||
|
|
90286ccbbc | ||
|
|
b33aa31944 | ||
|
|
dd6c741143 | ||
|
|
3b3263273c | ||
|
|
bc5a71ff88 | ||
|
|
e623aeb85d | ||
|
|
a2ff653a83 | ||
|
|
a13723c523 | ||
|
|
cf85146353 | ||
|
|
a9e49a09fd | ||
|
|
cbe44fed9b | ||
|
|
c439b8e27f | ||
|
|
86bf289452 | ||
|
|
e1ac7f389d | ||
|
|
d5159f66be | ||
|
|
c7a029a051 | ||
|
|
d5ded84501 | ||
|
|
4cd0dd5314 | ||
|
|
9783671399 | ||
|
|
8037b9e10a | ||
|
|
7c74653aa1 | ||
|
|
22767e36e2 | ||
|
|
6b89938da7 | ||
|
|
15919e550f | ||
|
|
f93587c805 | ||
|
|
a870f7de10 | ||
|
|
6b72067387 | ||
|
|
84c01f064f | ||
|
|
5d3e96662e | ||
|
|
91580043e0 | ||
|
|
3e28e92622 | ||
|
|
d494f73825 | ||
|
|
b03a5b13b7 |
2
.github/scripts/install.sh
vendored
2
.github/scripts/install.sh
vendored
@@ -3,5 +3,5 @@
|
||||
install -d /usr/local/bin/ /usr/local/share/man/man1/ /usr/local/share/man/man5/ /usr/local/share/man/man7/
|
||||
install -s -m 755 rgbasm rgblink rgbfix rgbgfx /usr/local/bin/
|
||||
install -m 644 rgbasm.1 rgblink.1 rgbfix.1 rgbgfx.1 /usr/local/share/man/man1/
|
||||
install -m 644 rgbds.5 rgbasm.5 rgblink.5 /usr/local/share/man/man5/
|
||||
install -m 644 rgbds.5 rgbasm.5 rgblink.5 rgbasm-old.5 /usr/local/share/man/man5/
|
||||
install -m 644 rgbds.7 gbz80.7 /usr/local/share/man/man7/
|
||||
|
||||
51
.github/workflows/build-container.yml
vendored
Normal file
51
.github/workflows/build-container.yml
vendored
Normal 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'
|
||||
2
.github/workflows/checkdiff.yml
vendored
2
.github/workflows/checkdiff.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: "Code coverage checking"
|
||||
name: Code coverage checking
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
|
||||
26
.github/workflows/create-release-artifacts.yml
vendored
26
.github/workflows/create-release-artifacts.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: "Create release artifacts"
|
||||
name: Create release artifacts
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
@@ -24,30 +24,32 @@ jobs:
|
||||
run: | # Turn "vX.Y.Z" into "X.Y.Z"
|
||||
VERSION="${{ github.ref_name }}"
|
||||
echo "version=${VERSION#v}" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
run: .github/scripts/get_win_deps.ps1
|
||||
- uses: actions/cache@v4
|
||||
- name: Check libraries cache
|
||||
id: cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
zbuild
|
||||
pngbuild
|
||||
key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }}
|
||||
- name: Build zlib
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
|
||||
cmake -S zlib -B zbuild -A ${{ matrix.platform }} -Wno-dev -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
|
||||
cmake --build zbuild --config Release -j
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
- name: Install zlib
|
||||
run: |
|
||||
cmake --install zbuild
|
||||
- name: Build libpng
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
cmake -S libpng -B pngbuild -A ${{ matrix.platform }} -Wno-dev -DCMAKE_INSTALL_PREFIX=install_dir -DPNG_SHARED=ON -DPNG_STATIC=OFF -DPNG_TESTS=OFF
|
||||
cmake --build pngbuild --config Release -j
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
- name: Install libpng
|
||||
run: |
|
||||
cmake --install pngbuild
|
||||
@@ -74,7 +76,8 @@ jobs:
|
||||
run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z"
|
||||
VERSION="${{ github.ref_name }}"
|
||||
echo "version=${VERSION#v}" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -104,7 +107,8 @@ jobs:
|
||||
run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z"
|
||||
VERSION="${{ github.ref_name }}"
|
||||
echo "version=${VERSION#v}" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -133,17 +137,19 @@ jobs:
|
||||
run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z"
|
||||
VERSION="${{ github.ref_name }}"
|
||||
echo "version=${VERSION#v}" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Package sources
|
||||
run: |
|
||||
make dist Q=
|
||||
ls
|
||||
- uses: actions/download-artifact@v4
|
||||
- name: Download Linux binaries
|
||||
uses: actions/download-artifact@v4
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
body: |
|
||||
Please ensure that the four packages below work properly.
|
||||
Please ensure that the packages below work properly.
|
||||
Once that's done, replace this text with the changelog, un-draft the release, and update the `release` branch.
|
||||
By the way, if you forgot to update `include/version.hpp`, RGBASM's version test is gonna fail in the tag's regression testing! (Use `git push --delete origin <tag>` to delete it)
|
||||
draft: true # Don't publish the release quite yet...
|
||||
|
||||
4
.github/workflows/create-release-docs.yml
vendored
4
.github/workflows/create-release-docs.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: "Create release docs"
|
||||
name: Create release docs
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository_owner == 'gbdev'
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout rgbds@release
|
||||
uses: actions/checkout@v4
|
||||
|
||||
43
.github/workflows/testing.yml
vendored
43
.github/workflows/testing.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: "Regression testing"
|
||||
name: Regression testing
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
@@ -17,7 +17,8 @@ jobs:
|
||||
fail-fast: false
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -32,9 +33,7 @@ jobs:
|
||||
run: |
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} -DSANITIZERS=ON -DMORE_WARNINGS=ON
|
||||
cmake --build build -j --verbose
|
||||
cp build/src/rgb{asm,link,fix,gfx} .
|
||||
sudo cmake --install build --verbose
|
||||
cmake --install build --verbose --component "Test support programs"
|
||||
- name: Package binaries
|
||||
run: |
|
||||
mkdir bins
|
||||
@@ -58,8 +57,8 @@ jobs:
|
||||
with:
|
||||
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
|
||||
key: ${{ matrix.os }}-${{ steps.test-deps-cache-params.outputs.hash }}
|
||||
- if: steps.test-deps-cache.outputs.cache-hit != 'true'
|
||||
name: Fetch test dependency repositories
|
||||
- name: Fetch test dependency repositories
|
||||
if: steps.test-deps-cache.outputs.cache-hit != 'true'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
test/fetch-test-deps.sh
|
||||
@@ -75,7 +74,8 @@ jobs:
|
||||
macos-static:
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -109,8 +109,8 @@ jobs:
|
||||
with:
|
||||
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
|
||||
key: ${{ matrix.os }}-${{ steps.test-deps-cache-params.outputs.hash }}
|
||||
- if: steps.test-deps-cache.outputs.cache-hit != 'true'
|
||||
name: Fetch test dependency repositories
|
||||
- name: Fetch test dependency repositories
|
||||
if: steps.test-deps-cache.outputs.cache-hit != 'true'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
test/fetch-test-deps.sh
|
||||
@@ -138,30 +138,32 @@ jobs:
|
||||
fail-fast: false
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
run: .github/scripts/get_win_deps.ps1
|
||||
- uses: actions/cache@v4
|
||||
- name: Check libraries cache
|
||||
id: cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
zbuild
|
||||
pngbuild
|
||||
key: ${{ matrix.arch }}-${{ hashFiles('zlib/**', 'libpng/**') }}
|
||||
- name: Build zlib
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
|
||||
cmake -S zlib -B zbuild -A ${{ matrix.platform }} -Wno-dev -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
|
||||
cmake --build zbuild --config Release -j
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
- name: Install zlib
|
||||
run: |
|
||||
cmake --install zbuild
|
||||
- name: Build libpng
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
cmake -S libpng -B pngbuild -A ${{ matrix.platform }} -Wno-dev -DCMAKE_INSTALL_PREFIX=install_dir -DPNG_SHARED=ON -DPNG_STATIC=OFF -DPNG_TESTS=OFF
|
||||
cmake --build pngbuild --config Release -j
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
- name: Install libpng
|
||||
run: |
|
||||
cmake --install pngbuild
|
||||
@@ -171,7 +173,6 @@ jobs:
|
||||
cmake -S . -B build -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build --config Release -j --verbose
|
||||
cmake --install build --verbose --prefix install_dir
|
||||
cmake --install build --verbose --component "Test support programs"
|
||||
- name: Package binaries
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -196,8 +197,8 @@ jobs:
|
||||
with:
|
||||
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
|
||||
key: ${{ matrix.os }}-${{ matrix.bits }}-${{ steps.test-deps-cache-params.outputs.hash }}
|
||||
- if: steps.test-deps-cache.outputs.cache-hit != 'true'
|
||||
name: Fetch test dependency repositories
|
||||
- name: Fetch test dependency repositories
|
||||
if: steps.test-deps-cache.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
continue-on-error: true
|
||||
run: |
|
||||
@@ -229,7 +230,8 @@ jobs:
|
||||
env:
|
||||
DIST_DIR: win${{ matrix.bits }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -273,7 +275,8 @@ jobs:
|
||||
fail-fast: false
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Retrieve binaries
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
@@ -303,8 +306,8 @@ jobs:
|
||||
with:
|
||||
path: ${{ fromJSON(steps.test-deps-cache-params.outputs.paths) }}
|
||||
key: mingw-${{ matrix.bits }}-${{ steps.test-deps-cache-params.outputs.hash }}
|
||||
- if: steps.test-deps-cache.outputs.cache-hit != 'true'
|
||||
name: Fetch test dependency repositories
|
||||
- name: Fetch test dependency repositories
|
||||
if: steps.test-deps-cache.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
continue-on-error: true
|
||||
run: |
|
||||
|
||||
5
.github/workflows/update-master-docs.yml
vendored
5
.github/workflows/update-master-docs.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: "Update master docs"
|
||||
name: Update master docs
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -9,6 +9,7 @@ on:
|
||||
- man/rgbds.7
|
||||
- man/rgbasm.1
|
||||
- man/rgbasm.5
|
||||
- man/rgbasm-old.5
|
||||
- man/rgblink.1
|
||||
- man/rgblink.5
|
||||
- man/rgbfix.1
|
||||
@@ -17,7 +18,7 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository_owner == 'gbdev'
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout rgbds@master
|
||||
uses: actions/checkout@v4
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,4 +13,5 @@
|
||||
CMakeCache.txt
|
||||
CMakeFiles/
|
||||
cmake_install.cmake
|
||||
build/
|
||||
callgrind.out.*
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# 3.9 required for LTO checks
|
||||
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
|
||||
# 3.17 optional for CMAKE_CTEST_ARGUMENTS
|
||||
cmake_minimum_required(VERSION 3.9..3.17 FATAL_ERROR)
|
||||
|
||||
project(rgbds
|
||||
LANGUAGES CXX)
|
||||
|
||||
include(CTest)
|
||||
|
||||
# get real path of source and binary directories
|
||||
get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH)
|
||||
get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH)
|
||||
@@ -41,7 +44,6 @@ else()
|
||||
# does not recognize this yet.
|
||||
add_compile_options(-Wno-gnu-zero-variadic-macro-arguments)
|
||||
endif()
|
||||
add_definitions(-D_POSIX_C_SOURCE=200809L)
|
||||
if(SANITIZERS)
|
||||
set(SAN_FLAGS -fsanitize=shift -fsanitize=integer-divide-by-zero
|
||||
-fsanitize=unreachable -fsanitize=vla-bound
|
||||
@@ -59,7 +61,7 @@ else()
|
||||
if(MORE_WARNINGS)
|
||||
add_compile_options(-Werror -Wextra
|
||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond
|
||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wshift-overflow=2
|
||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2
|
||||
-Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused
|
||||
-Wshadow # TODO: -Wshadow=compatible-local?
|
||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1
|
||||
@@ -88,6 +90,8 @@ endif(GIT)
|
||||
find_package(PkgConfig)
|
||||
if(MSVC OR NOT PKG_CONFIG_FOUND)
|
||||
# fallback to find_package
|
||||
# cmake's FindPNG is very fragile; it breaks when multiple versions are installed
|
||||
# this is most evident on macOS but can occur on Linux too
|
||||
find_package(PNG REQUIRED)
|
||||
else()
|
||||
pkg_check_modules(LIBPNG REQUIRED libpng)
|
||||
@@ -99,6 +103,7 @@ set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
|
||||
add_subdirectory(src)
|
||||
set(CMAKE_CTEST_ARGUMENTS "--verbose")
|
||||
add_subdirectory(test)
|
||||
|
||||
# By default, build in Release mode; Debug mode must be explicitly requested
|
||||
@@ -121,6 +126,7 @@ set(man1 "man/rgbasm.1"
|
||||
"man/rgbgfx.1"
|
||||
"man/rgblink.1")
|
||||
set(man5 "man/rgbasm.5"
|
||||
"man/rgbasm-old.5"
|
||||
"man/rgblink.5"
|
||||
"man/rgbds.5")
|
||||
set(man7 "man/gbz80.7"
|
||||
|
||||
@@ -174,3 +174,17 @@ Each one is a binary RNG file which is passed to the `rgbgfx_test` program.
|
||||
```sh
|
||||
test_downstream <owner> <repo> <makefile target> <build file> <sha1 hash of build file>
|
||||
```
|
||||
|
||||
## Container images
|
||||
|
||||
The CI will [take care](https://github.com/gbdev/rgbds/blob/master/.github/workflows/build-container.yml) of updating the [rgbds container](https://github.com/gbdev/rgbds/pkgs/container/rgbds) image tagged `master`.
|
||||
|
||||
When a git tag is pushed, the image is also tagged with that tag.
|
||||
|
||||
The image can be built locally and pushed to the GitHub container registry by manually running:
|
||||
|
||||
```bash
|
||||
# e.g. to build and tag as 'master'
|
||||
docker build . --tag ghcr.io/gbdev/rgbds:master
|
||||
docker push ghcr.io/gbdev/rgbds:master
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
FROM debian:11-slim
|
||||
LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds
|
||||
ARG version=0.9.0-rc1
|
||||
ARG version=0.9.0
|
||||
WORKDIR /rgbds
|
||||
|
||||
COPY . .
|
||||
|
||||
13
Makefile
13
Makefile
@@ -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
|
||||
VERSION_STRING := `git --git-dir=.git describe --tags --dirty --always 2>/dev/null`
|
||||
|
||||
WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option \
|
||||
-Wno-gnu-zero-variadic-macro-arguments
|
||||
WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option -Wno-gnu-zero-variadic-macro-arguments
|
||||
|
||||
# Overridable CXXFLAGS
|
||||
CXXFLAGS ?= -O3 -flto -DNDEBUG
|
||||
# Non-overridable CXXFLAGS
|
||||
REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++2a -I include \
|
||||
-D_POSIX_C_SOURCE=200809L -fno-exceptions -fno-rtti
|
||||
REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++2a -I include -fno-exceptions -fno-rtti
|
||||
# Overridable LDFLAGS
|
||||
LDFLAGS ?=
|
||||
# Non-overridable LDFLAGS
|
||||
REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} \
|
||||
-DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
|
||||
REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} -DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
|
||||
|
||||
# Wrapper around bison that passes flags depending on what the version supports
|
||||
BISON := src/bison.sh
|
||||
@@ -188,7 +185,7 @@ install: all
|
||||
$Qinstall ${STRIP} -m ${BINMODE} rgbfix ${DESTDIR}${bindir}/rgbfix${SUFFIX}
|
||||
$Qinstall ${STRIP} -m ${BINMODE} rgbgfx ${DESTDIR}${bindir}/rgbgfx${SUFFIX}
|
||||
$Qinstall -m ${MANMODE} man/rgbasm.1 man/rgblink.1 man/rgbfix.1 man/rgbgfx.1 ${DESTDIR}${mandir}/man1/
|
||||
$Qinstall -m ${MANMODE} man/rgbds.5 man/rgbasm.5 man/rgblink.5 ${DESTDIR}${mandir}/man5/
|
||||
$Qinstall -m ${MANMODE} man/rgbds.5 man/rgbasm.5 man/rgbasm-old.5 man/rgblink.5 ${DESTDIR}${mandir}/man5/
|
||||
$Qinstall -m ${MANMODE} man/rgbds.7 man/gbz80.7 ${DESTDIR}${mandir}/man7/
|
||||
|
||||
# Target used to check for suspiciously missing changed files.
|
||||
@@ -204,7 +201,7 @@ checkdiff:
|
||||
develop:
|
||||
$Q${MAKE} WARNFLAGS="${WARNFLAGS} -Werror -Wextra \
|
||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond \
|
||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wshift-overflow=2 \
|
||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2 \
|
||||
-Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused -Wshadow \
|
||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
|
||||
-Wno-format-nonliteral -Wno-strict-overflow -Wno-unused-but-set-variable \
|
||||
|
||||
34
RELEASE.md
34
RELEASE.md
@@ -3,13 +3,16 @@
|
||||
This describes for the maintainers of RGBDS how to publish a new release on
|
||||
GitHub.
|
||||
|
||||
1. Update, commit, and push [include/version.hpp](include/version.hpp) with
|
||||
values for `PACKAGE_VERSION_MAJOR`, `PACKAGE_VERSION_MINOR`,
|
||||
`PACKAGE_VERSION_PATCH`, and `PACKAGE_VERSION_RC`, as well as
|
||||
[Dockerfile](Dockerfile) with a value for `ARG version`. Only define
|
||||
`PACKAGE_VERSION_RC` if you are publishing a release candidate! You can
|
||||
use <code>git commit -m "Release <i><version></i>"</code> and
|
||||
`git push origin master`.
|
||||
1. Update the following files, then commit and push.
|
||||
You can use <code>git commit -m "Release <i><version></i>"</code> and `git push origin master`.
|
||||
|
||||
- [include/version.hpp](include/version.hpp): set appropriate values for `PACKAGE_VERSION_MAJOR`,
|
||||
`PACKAGE_VERSION_MINOR`, `PACKAGE_VERSION_PATCH`, and `PACKAGE_VERSION_RC`.
|
||||
**Only** define `PACKAGE_VERSION_RC` if you are publishing a release candidate!
|
||||
- [Dockerfile](Dockerfile): update `ARG version`.
|
||||
- [test/fetch-test-deps.sh](test/fetch-test-deps.sh): update test dependency commits
|
||||
(preferably, use the latest available).
|
||||
- [man/\*](man/): update dates and authors.
|
||||
|
||||
2. Create a Git tag formatted as <code>v<i><MAJOR></i>.<i><MINOR></i>.<i><PATCH></i></code>,
|
||||
or <code>v<i><MAJOR></i>.<i><MINOR></i>.<i><PATCH></i>-rc<i><RC></i></code>
|
||||
@@ -38,7 +41,9 @@ GitHub.
|
||||
4. GitHub Actions will run the [create-release-docs.yml](.github/workflows/create-release-docs.yml)
|
||||
workflow to add the release documentation to [rgbds-www](https://github.com/gbdev/rgbds-www).
|
||||
|
||||
This is not done automatically for prereleases; if you want to do this manually,
|
||||
This is not done automatically for prereleases, since we do not normally publish documentation
|
||||
for them. If you want to manually publish prerelease documentation, such as for an April Fools
|
||||
joke prerelease,
|
||||
|
||||
1. Clone [rgbds-www](https://github.com/gbdev/rgbds-www). You can use
|
||||
`git clone https://github.com/gbdev/rgbds-www.git`.
|
||||
@@ -52,8 +57,8 @@ GitHub.
|
||||
|
||||
If you do not have `groff` installed, you can change
|
||||
`groff -Tpdf -mdoc -wall` to `mandoc -Tpdf -I os=Linux` in
|
||||
[.github/actions/get-pages.sh](.github/actions/get-pages.sh) and it
|
||||
will suffice.
|
||||
[maintainer/man_to_html.sh](https://github.com/gbdev/rgbds-www/blob/master/maintainer/man_to_html.sh)
|
||||
and it will suffice.
|
||||
|
||||
4. Commit and push the documentation. You can use <code>git commit -m
|
||||
"Create RGBDS <i><tag></i> documentation"</code> and `git push origin master`
|
||||
@@ -64,3 +69,12 @@ GitHub.
|
||||
6. Click the "Publish release" button to publish it!
|
||||
|
||||
7. Update the `release` branch. You can use `git push origin release`.
|
||||
|
||||
8. Update the following related projects.
|
||||
|
||||
- [rgbobj](https://github.com/gbdev/rgbobj) and [rgbds-obj](https://github.com/gbdev/rgbds-obj):
|
||||
make sure that object files created by the latest RGBASM can be parsed and displayed.
|
||||
If the object file revision has been updated, rgbobj will need a corresponding release.
|
||||
- [rgbds-www](https://github.com/gbdev/rgbds-www): update
|
||||
[src/pages/versions.mdx](https://github.com/gbdev/rgbds-www/blob/master/src/pages/versions.mdx)
|
||||
to list the new release.
|
||||
|
||||
@@ -192,6 +192,8 @@ _rgbasm_completions() {
|
||||
shift-amount
|
||||
truncation
|
||||
unmapped-char
|
||||
unmatched-directive
|
||||
unterminated-load
|
||||
user
|
||||
all
|
||||
extra
|
||||
|
||||
@@ -26,6 +26,8 @@ _rgbasm_warnings() {
|
||||
'shift-amount:Warn when a shift'\''s operand it negative or \> 32'
|
||||
'truncation:Warn when implicit truncation loses bits'
|
||||
'unmapped-char:Warn on unmapped character'
|
||||
'unmatched-directive:Warn on unmatched directive pair'
|
||||
'unterminated-load:Warn on LOAD without ENDL'
|
||||
'user:Warn when executing the WARN built-in'
|
||||
)
|
||||
# TODO: handle `no-` and `error=` somehow?
|
||||
|
||||
@@ -18,6 +18,7 @@ void charmap_New(std::string const &name, std::string const *baseName);
|
||||
void charmap_Set(std::string const &name);
|
||||
void charmap_Push();
|
||||
void charmap_Pop();
|
||||
void charmap_CheckStack();
|
||||
void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value);
|
||||
bool charmap_HasChar(std::string const &input);
|
||||
std::vector<int32_t> charmap_Convert(std::string const &input);
|
||||
|
||||
@@ -30,8 +30,8 @@ struct FileStackNode {
|
||||
// Meaningless at the root level, but gets written to the object file anyway, so init it
|
||||
uint32_t lineNo = 0;
|
||||
|
||||
// Set only if referenced: ID within the object file, -1 if not output yet
|
||||
uint32_t ID = -1;
|
||||
// Set only if referenced: ID within the object file, `UINT32_MAX` if not output yet
|
||||
uint32_t ID = UINT32_MAX;
|
||||
|
||||
// REPT iteration counts since last named node, in reverse depth order
|
||||
std::vector<uint32_t> &iters() { return data.get<std::vector<uint32_t>>(); }
|
||||
|
||||
@@ -14,5 +14,6 @@ void opt_Parse(char const *option);
|
||||
|
||||
void opt_Push();
|
||||
void opt_Pop();
|
||||
void opt_CheckStack();
|
||||
|
||||
#endif // RGBDS_ASM_OPT_HPP
|
||||
|
||||
@@ -53,7 +53,7 @@ struct Expression {
|
||||
void makeUnaryOp(RPNCommand op, Expression &&src);
|
||||
void makeBinaryOp(RPNCommand op, Expression &&src1, Expression const &src2);
|
||||
|
||||
void makeCheckHRAM();
|
||||
bool makeCheckHRAM();
|
||||
void makeCheckRST();
|
||||
|
||||
void checkNBit(uint8_t n) const;
|
||||
|
||||
@@ -70,7 +70,8 @@ void sect_SetLoadSection(
|
||||
SectionSpec const &attrs,
|
||||
SectionModifier mod
|
||||
);
|
||||
void sect_EndLoadSection();
|
||||
void sect_EndLoadSection(char const *cause);
|
||||
void sect_CheckLoadClosed();
|
||||
|
||||
Section *sect_GetSymbolSection();
|
||||
uint32_t sect_GetSymbolOffset();
|
||||
@@ -101,5 +102,6 @@ void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t len
|
||||
void sect_EndSection();
|
||||
void sect_PushSection();
|
||||
void sect_PopSection();
|
||||
void sect_CheckStack();
|
||||
|
||||
#endif // RGBDS_ASM_SECTION_HPP
|
||||
|
||||
@@ -45,7 +45,7 @@ struct Symbol {
|
||||
>
|
||||
data;
|
||||
|
||||
uint32_t ID; // ID of the symbol in the object file (-1 if none)
|
||||
uint32_t ID; // ID of the symbol in the object file (`UINT32_MAX` if none)
|
||||
uint32_t defIndex; // Ordering of the symbol in the state file
|
||||
|
||||
bool isDefined() const { return type != SYM_REF; }
|
||||
@@ -55,7 +55,7 @@ struct Symbol {
|
||||
bool isConstant() const {
|
||||
if (type == SYM_LABEL) {
|
||||
Section const *sect = getSection();
|
||||
return sect && sect->org != (uint32_t)-1;
|
||||
return sect && sect->org != UINT32_MAX;
|
||||
}
|
||||
return type == SYM_EQU || type == SYM_VAR;
|
||||
}
|
||||
|
||||
@@ -5,31 +5,30 @@
|
||||
|
||||
extern unsigned int nbErrors, maxErrors;
|
||||
|
||||
enum WarningState { WARNING_DEFAULT, WARNING_DISABLED, WARNING_ENABLED, WARNING_ERROR };
|
||||
|
||||
enum WarningID {
|
||||
WARNING_ASSERT, // Assertions
|
||||
WARNING_BACKWARDS_FOR, // `for` loop with backwards range
|
||||
WARNING_BACKWARDS_FOR, // `FOR` loop with backwards range
|
||||
WARNING_BUILTIN_ARG, // Invalid args to builtins
|
||||
WARNING_CHARMAP_REDEF, // Charmap entry re-definition
|
||||
WARNING_DIV, // Division undefined behavior
|
||||
WARNING_DIV, // Undefined division behavior
|
||||
WARNING_EMPTY_DATA_DIRECTIVE, // `db`, `dw` or `dl` directive without data in ROM
|
||||
WARNING_EMPTY_MACRO_ARG, // Empty macro argument
|
||||
WARNING_EMPTY_STRRPL, // Empty second argument in `STRRPL`
|
||||
WARNING_LARGE_CONSTANT, // Constants too large
|
||||
WARNING_MACRO_SHIFT, // Shift past available arguments in macro
|
||||
WARNING_MACRO_SHIFT, // `SHIFT` past available arguments in macro
|
||||
WARNING_NESTED_COMMENT, // Comment-start delimiter in a block comment
|
||||
WARNING_OBSOLETE, // Obsolete things
|
||||
WARNING_SHIFT, // Shifting undefined behavior
|
||||
WARNING_SHIFT_AMOUNT, // Strange shift amount
|
||||
WARNING_USER, // User warnings
|
||||
WARNING_OBSOLETE, // Obsolete/deprecated things
|
||||
WARNING_SHIFT, // Undefined `SHIFT` behavior
|
||||
WARNING_SHIFT_AMOUNT, // Strange `SHIFT` amount
|
||||
WARNING_UNMATCHED_DIRECTIVE, // `PUSH[C|O|S]` without `POP[C|O|S]`
|
||||
WARNING_UNTERMINATED_LOAD, // `LOAD` without `ENDL`
|
||||
WARNING_USER, // User-defined `WARN`ings
|
||||
|
||||
NB_PLAIN_WARNINGS,
|
||||
|
||||
// Warnings past this point are "parametric" warnings, only mapping to a single flag
|
||||
#define PARAM_WARNINGS_START NB_PLAIN_WARNINGS
|
||||
// Warnings past this point are "parametric" warnings, only mapping to a single flag
|
||||
// Treating string as number may lose some bits
|
||||
WARNING_NUMERIC_STRING_1 = PARAM_WARNINGS_START,
|
||||
WARNING_NUMERIC_STRING_1 = NB_PLAIN_WARNINGS,
|
||||
WARNING_NUMERIC_STRING_2,
|
||||
// Purging an exported symbol or label
|
||||
WARNING_PURGE_1,
|
||||
@@ -41,20 +40,24 @@ enum WarningID {
|
||||
WARNING_UNMAPPED_CHAR_1,
|
||||
WARNING_UNMAPPED_CHAR_2,
|
||||
|
||||
NB_PLAIN_AND_PARAM_WARNINGS,
|
||||
#define NB_PARAM_WARNINGS (NB_PLAIN_AND_PARAM_WARNINGS - PARAM_WARNINGS_START)
|
||||
|
||||
// Warnings past this point are "meta" warnings
|
||||
#define META_WARNINGS_START NB_PLAIN_AND_PARAM_WARNINGS
|
||||
WARNING_ALL = META_WARNINGS_START,
|
||||
WARNING_EXTRA,
|
||||
WARNING_EVERYTHING,
|
||||
|
||||
NB_WARNINGS,
|
||||
#define NB_META_WARNINGS (NB_WARNINGS - META_WARNINGS_START)
|
||||
};
|
||||
|
||||
extern WarningState warningStates[NB_PLAIN_AND_PARAM_WARNINGS];
|
||||
enum WarningAbled { WARNING_DEFAULT, WARNING_ENABLED, WARNING_DISABLED };
|
||||
|
||||
struct WarningState {
|
||||
WarningAbled state;
|
||||
WarningAbled error;
|
||||
|
||||
void update(WarningState other);
|
||||
};
|
||||
|
||||
struct Diagnostics {
|
||||
WarningState flagStates[NB_WARNINGS];
|
||||
WarningState metaStates[NB_WARNINGS];
|
||||
};
|
||||
|
||||
extern Diagnostics warningStates;
|
||||
extern bool warningsAreErrors;
|
||||
|
||||
void processWarningFlag(char const *flag);
|
||||
|
||||
@@ -43,11 +43,11 @@ private:
|
||||
// Generic field accessors; for internal use only.
|
||||
template<typename T>
|
||||
auto &field() {
|
||||
return pick((T *)nullptr);
|
||||
return pick(static_cast<T *>(nullptr));
|
||||
}
|
||||
template<typename T>
|
||||
auto const &field() const {
|
||||
return pick((T *)nullptr);
|
||||
return pick(static_cast<T *>(nullptr));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -48,13 +48,13 @@ struct Options {
|
||||
|
||||
std::string input{}; // positional arg
|
||||
|
||||
static constexpr uint8_t VERB_NONE = 0; // Normal, no extra output
|
||||
static constexpr uint8_t VERB_CFG = 1; // Print configuration after parsing options
|
||||
static constexpr uint8_t VERB_LOG_ACT = 2; // Log actions before doing them
|
||||
static constexpr uint8_t VERB_INTERM = 3; // Print some intermediate results
|
||||
static constexpr uint8_t VERB_DEBUG = 4; // Internals are logged
|
||||
static constexpr uint8_t VERB_UNMAPPED = 5; // Unused so far
|
||||
static constexpr uint8_t VERB_VVVVVV = 6; // What, can't I have a little fun?
|
||||
static constexpr uint8_t VERB_NONE = 0; // Normal, no extra output
|
||||
static constexpr uint8_t VERB_CFG = 1; // Print configuration after parsing options
|
||||
static constexpr uint8_t VERB_LOG_ACT = 2; // Log actions before doing them
|
||||
static constexpr uint8_t VERB_INTERM = 3; // Print some intermediate results
|
||||
static constexpr uint8_t VERB_DEBUG = 4; // Internals are logged
|
||||
static constexpr uint8_t VERB_TRACE = 5; // Step-by-step algorithm details
|
||||
static constexpr uint8_t VERB_VVVVVV = 6; // What, can't I have a little fun?
|
||||
[[gnu::format(printf, 3, 4)]] void verbosePrint(uint8_t level, char const *fmt, ...) const;
|
||||
|
||||
mutable bool hasTransparentPixels = false;
|
||||
@@ -107,20 +107,18 @@ struct Palette {
|
||||
uint8_t size() const;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
template<typename T, T... i>
|
||||
static constexpr auto flipTable(std::integer_sequence<T, i...>) {
|
||||
return std::array{[](uint8_t byte) {
|
||||
// Flipping tends to happen fairly often, so take a bite out of dcache to speed it up
|
||||
static constexpr auto flipTable = ([]() constexpr {
|
||||
std::array<uint16_t, 256> table{};
|
||||
for (uint16_t i = 0; i < table.size(); i++) {
|
||||
// To flip all the bits, we'll flip both nibbles, then each nibble half, etc.
|
||||
uint16_t byte = i;
|
||||
byte = (byte & 0b0000'1111) << 4 | (byte & 0b1111'0000) >> 4;
|
||||
byte = (byte & 0b0011'0011) << 2 | (byte & 0b1100'1100) >> 2;
|
||||
byte = (byte & 0b0101'0101) << 1 | (byte & 0b1010'1010) >> 1;
|
||||
return byte;
|
||||
}(i)...};
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
// Flipping tends to happen fairly often, so take a bite out of dcache to speed it up
|
||||
static constexpr auto flipTable = detail::flipTable(std::make_integer_sequence<uint16_t, 256>());
|
||||
table[i] = byte;
|
||||
}
|
||||
return table;
|
||||
})();
|
||||
|
||||
#endif // RGBDS_GFX_MAIN_HPP
|
||||
|
||||
@@ -6,19 +6,15 @@
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include "defaultinitalloc.hpp"
|
||||
#include "defaultinitvec.hpp"
|
||||
|
||||
struct Palette;
|
||||
class ProtoPalette;
|
||||
|
||||
namespace packing {
|
||||
|
||||
/*
|
||||
* Returns which palette each proto-palette maps to, and how many palettes are necessary
|
||||
*/
|
||||
std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes);
|
||||
|
||||
} // namespace packing
|
||||
|
||||
#endif // RGBDS_GFX_PAL_PACKING_HPP
|
||||
|
||||
@@ -12,20 +12,16 @@
|
||||
|
||||
struct Palette;
|
||||
|
||||
namespace sorting {
|
||||
|
||||
void indexed(
|
||||
void sortIndexed(
|
||||
std::vector<Palette> &palettes,
|
||||
int palSize,
|
||||
png_color const *palRGB,
|
||||
int palAlphaSize,
|
||||
png_byte *palAlpha
|
||||
);
|
||||
void grayscale(
|
||||
void sortGrayscale(
|
||||
std::vector<Palette> &palettes, std::array<std::optional<Rgba>, 0x8001> const &colors
|
||||
);
|
||||
void rgb(std::vector<Palette> &palettes);
|
||||
|
||||
} // namespace sorting
|
||||
void sortRgb(std::vector<Palette> &palettes);
|
||||
|
||||
#endif // RGBDS_GFX_PAL_SORTING_HPP
|
||||
|
||||
@@ -18,12 +18,8 @@ private:
|
||||
std::array<uint16_t, capacity> _colorIndices{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
|
||||
|
||||
public:
|
||||
/*
|
||||
* Adds the specified color to the set, or **silently drops it** if the set is full.
|
||||
*
|
||||
* Returns whether the color was unique.
|
||||
*/
|
||||
bool add(uint16_t color);
|
||||
// Adds the specified color to the set, or **silently drops it** if the set is full.
|
||||
void add(uint16_t color);
|
||||
|
||||
enum ComparisonResult {
|
||||
NEITHER,
|
||||
|
||||
@@ -28,7 +28,7 @@ struct Rgba {
|
||||
_5to8(cgbColor),
|
||||
_5to8(cgbColor >> 5),
|
||||
_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; };
|
||||
return shl(red, 24) | shl(green, 16) | shl(blue, 8) | shl(alpha, 0);
|
||||
}
|
||||
friend bool operator==(Rgba const &lhs, Rgba const &rhs) { return lhs.toCSS() == rhs.toCSS(); }
|
||||
friend bool operator!=(Rgba const &lhs, Rgba const &rhs) { return lhs.toCSS() != rhs.toCSS(); }
|
||||
bool operator==(Rgba const &rhs) const { return toCSS() == rhs.toCSS(); }
|
||||
bool operator!=(Rgba const &rhs) const { return toCSS() != rhs.toCSS(); }
|
||||
|
||||
/*
|
||||
* CGB colors are RGB555, so we use bit 15 to signify that the color is transparent instead
|
||||
|
||||
@@ -6,52 +6,46 @@
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
template<typename T>
|
||||
class EnumSeqIterator {
|
||||
T _value;
|
||||
|
||||
public:
|
||||
explicit EnumSeqIterator(T value) : _value(value) {}
|
||||
|
||||
EnumSeqIterator &operator++() {
|
||||
_value = (T)(_value + 1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator*() const { return _value; }
|
||||
|
||||
friend auto operator==(EnumSeqIterator const &lhs, EnumSeqIterator const &rhs) {
|
||||
return lhs._value == rhs._value;
|
||||
}
|
||||
|
||||
friend auto operator!=(EnumSeqIterator const &lhs, EnumSeqIterator const &rhs) {
|
||||
return lhs._value != rhs._value;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class EnumSeq {
|
||||
T _start;
|
||||
T _stop;
|
||||
|
||||
class Iterator {
|
||||
T _value;
|
||||
|
||||
public:
|
||||
explicit Iterator(T value) : _value(value) {}
|
||||
|
||||
Iterator &operator++() {
|
||||
_value = static_cast<T>(_value + 1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator*() const { return _value; }
|
||||
|
||||
bool operator==(Iterator const &rhs) const { return _value == rhs._value; }
|
||||
bool operator!=(Iterator const &rhs) const { return _value != rhs._value; }
|
||||
};
|
||||
|
||||
public:
|
||||
explicit EnumSeq(T stop) : _start((T)0), _stop(stop) {}
|
||||
explicit EnumSeq(T stop) : _start(static_cast<T>(0)), _stop(stop) {}
|
||||
explicit EnumSeq(T start, T stop) : _start(start), _stop(stop) {}
|
||||
|
||||
EnumSeqIterator<T> begin() { return EnumSeqIterator(_start); }
|
||||
EnumSeqIterator<T> end() { return EnumSeqIterator(_stop); }
|
||||
Iterator begin() { return Iterator(_start); }
|
||||
Iterator end() { return Iterator(_stop); }
|
||||
};
|
||||
|
||||
// This is not a fully generic implementation; its current use cases only require for-loop behavior.
|
||||
// We also assume that all iterators have the same length.
|
||||
template<typename... Iters>
|
||||
class Zip {
|
||||
std::tuple<Iters...> _iters;
|
||||
template<typename... Ts>
|
||||
class ZipIterator {
|
||||
std::tuple<Ts...> _iters;
|
||||
|
||||
public:
|
||||
explicit Zip(std::tuple<Iters...> &&iters) : _iters(iters) {}
|
||||
explicit ZipIterator(std::tuple<Ts...> &&iters) : _iters(iters) {}
|
||||
|
||||
Zip &operator++() {
|
||||
ZipIterator &operator++() {
|
||||
std::apply([](auto &&...it) { (++it, ...); }, _iters);
|
||||
return *this;
|
||||
}
|
||||
@@ -62,26 +56,24 @@ public:
|
||||
);
|
||||
}
|
||||
|
||||
friend auto operator==(Zip const &lhs, Zip const &rhs) {
|
||||
return std::get<0>(lhs._iters) == std::get<0>(rhs._iters);
|
||||
bool operator==(ZipIterator const &rhs) const {
|
||||
return std::get<0>(_iters) == std::get<0>(rhs._iters);
|
||||
}
|
||||
|
||||
friend auto operator!=(Zip const &lhs, Zip const &rhs) {
|
||||
return std::get<0>(lhs._iters) != std::get<0>(rhs._iters);
|
||||
bool operator!=(ZipIterator const &rhs) const {
|
||||
return std::get<0>(_iters) != std::get<0>(rhs._iters);
|
||||
}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
template<typename... Containers>
|
||||
template<typename... Ts>
|
||||
class ZipContainer {
|
||||
std::tuple<Containers...> _containers;
|
||||
std::tuple<Ts...> _containers;
|
||||
|
||||
public:
|
||||
explicit ZipContainer(Containers &&...containers)
|
||||
: _containers(std::forward<Containers>(containers)...) {}
|
||||
explicit ZipContainer(Ts &&...containers) : _containers(std::forward<Ts>(containers)...) {}
|
||||
|
||||
auto begin() {
|
||||
return Zip(std::apply(
|
||||
return ZipIterator(std::apply(
|
||||
[](auto &&...containers) {
|
||||
using std::begin;
|
||||
return std::make_tuple(begin(containers)...);
|
||||
@@ -91,7 +83,7 @@ public:
|
||||
}
|
||||
|
||||
auto end() {
|
||||
return Zip(std::apply(
|
||||
return ZipIterator(std::apply(
|
||||
[](auto &&...containers) {
|
||||
using std::end;
|
||||
return std::make_tuple(end(containers)...);
|
||||
@@ -105,12 +97,11 @@ public:
|
||||
template<typename T>
|
||||
using Holder = std::
|
||||
conditional_t<std::is_lvalue_reference_v<T>, T, std::remove_cv_t<std::remove_reference_t<T>>>;
|
||||
} // namespace detail
|
||||
|
||||
// Does the same number of iterations as the first container's iterator!
|
||||
template<typename... Containers>
|
||||
static constexpr auto zip(Containers &&...cs) {
|
||||
return detail::ZipContainer<detail::Holder<Containers>...>(std::forward<Containers>(cs)...);
|
||||
template<typename... Ts>
|
||||
static constexpr auto zip(Ts &&...cs) {
|
||||
return ZipContainer<Holder<Ts>...>(std::forward<Ts>(cs)...);
|
||||
}
|
||||
|
||||
#endif // RGBDS_ITERTOOLS_HPP
|
||||
|
||||
@@ -56,4 +56,9 @@
|
||||
#define setmode(fd, mode) (0)
|
||||
#endif
|
||||
|
||||
// MingGW and Cygwin need POSIX functions which are not standard C explicitly enabled
|
||||
#if defined(__MINGW32__) || defined(__CYGWIN__)
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#endif
|
||||
|
||||
#endif // RGBDS_PLATFORM_HPP
|
||||
|
||||
@@ -8,7 +8,6 @@ extern "C" {
|
||||
#define PACKAGE_VERSION_MAJOR 0
|
||||
#define PACKAGE_VERSION_MINOR 9
|
||||
#define PACKAGE_VERSION_PATCH 0
|
||||
#define PACKAGE_VERSION_RC 1
|
||||
|
||||
char const *get_package_version_string();
|
||||
}
|
||||
|
||||
614
man/gbz80.7
614
man/gbz80.7
File diff suppressed because it is too large
Load Diff
375
man/rgbasm-old.5
Normal file
375
man/rgbasm-old.5
Normal 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 .
|
||||
48
man/rgbasm.1
48
man/rgbasm.1
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd September 18, 2024
|
||||
.Dd December 25, 2024
|
||||
.Dt RGBASM 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -200,7 +200,7 @@ section for a list of warnings.
|
||||
Disable all warning output, even when turned into errors.
|
||||
.It Fl X Ar max_errors , Fl \-max-errors Ar max_errors
|
||||
If more than this number of errors (not warnings) occur, then abort the assembly process;
|
||||
.Fl X 0
|
||||
.Fl X Ar 0
|
||||
disables this behavior.
|
||||
The default is 100 if
|
||||
.Nm
|
||||
@@ -212,13 +212,19 @@ The following options alter the way warnings are processed.
|
||||
.Bl -tag -width Ds
|
||||
.It Fl Werror
|
||||
Make all warnings into errors.
|
||||
This can be negated as
|
||||
.Fl Wno-error
|
||||
to prevent turning all warnings into errors.
|
||||
.It Fl Werror=
|
||||
Make the specified warning into an error.
|
||||
Make the specified warning or meta warning into an error.
|
||||
A warning's name is appended
|
||||
.Pq example: Fl Werror=obsolete ,
|
||||
and this warning is implicitly enabled and turned into an error.
|
||||
This is an error if used with a meta warning, such as
|
||||
.Fl Werror=all .
|
||||
This can be negated as
|
||||
.Fl Wno-error=
|
||||
to prevent turning a specified warning into an error, even if
|
||||
.Fl Werror
|
||||
is in effect.
|
||||
.El
|
||||
.Pp
|
||||
The following warnings are
|
||||
@@ -240,6 +246,10 @@ Note that each of these flag also has a negation (for example,
|
||||
.Fl Wcharmap-redef
|
||||
enables the warning that
|
||||
.Fl Wno-charmap-redef
|
||||
disables; and
|
||||
.Fl Wall
|
||||
enables every warning that
|
||||
.Fl Wno-all
|
||||
disables).
|
||||
Only the non-default flag is listed here.
|
||||
Ignoring the
|
||||
@@ -291,6 +301,13 @@ This warning is enabled by
|
||||
Warn when shifting macro arguments past their limits.
|
||||
This warning is enabled by
|
||||
.Fl Wextra .
|
||||
.It Fl Wno-nested-comment
|
||||
Warn when the block comment start sequence
|
||||
.Ql /*
|
||||
is found inside of a block comment.
|
||||
Block comments cannot be nested, so the first
|
||||
.Ql */
|
||||
will end the whole comment.
|
||||
.It Fl Wno-obsolete
|
||||
Warn when obsolete constructs such as the
|
||||
.Ic _PI
|
||||
@@ -344,7 +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.
|
||||
.Fl Wunmapped-char=0
|
||||
or
|
||||
.Fl Wunmapped-char
|
||||
.Fl Wno-unmapped-char
|
||||
disables this warning.
|
||||
.Fl Wunmapped-char=1
|
||||
or just
|
||||
@@ -353,6 +370,24 @@ only warns if the active charmap is not empty.
|
||||
.Fl Wunmapped-char=2
|
||||
warns if the active charmap is empty, and/or is not the default charmap
|
||||
.Sq main .
|
||||
.It Fl Wunmatched-directive
|
||||
Warn when a
|
||||
.Ic PUSHC , PUSHO ,
|
||||
or
|
||||
.Ic PUSHS
|
||||
directive does not have a corresponding
|
||||
.Ic POPC , POPO ,
|
||||
or
|
||||
.Ic POPS .
|
||||
This warning is enabled by
|
||||
.Fl Wextra .
|
||||
.It Fl Wunterminated-load
|
||||
Warn when a
|
||||
.Ic LOAD
|
||||
block is not terminated by an
|
||||
.Ic ENDL .
|
||||
This warning is enabled by
|
||||
.Fl Wextra .
|
||||
.It Fl Wno-user
|
||||
Warn when the
|
||||
.Ic WARN
|
||||
@@ -392,6 +427,7 @@ Please report bugs on
|
||||
.Xr rgbfix 1 ,
|
||||
.Xr rgbgfx 1 ,
|
||||
.Xr gbz80 7 ,
|
||||
.Xr rgbasm-old 5 ,
|
||||
.Xr rgbds 5 ,
|
||||
.Xr rgbds 7
|
||||
.Sh HISTORY
|
||||
|
||||
244
man/rgbasm.5
244
man/rgbasm.5
@@ -2,7 +2,7 @@
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd September 18, 2024
|
||||
.Dd December 25, 2024
|
||||
.Dt RGBASM 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -41,7 +41,6 @@ or
|
||||
Labels tie a name to a specific location within a section (see
|
||||
.Sx Labels
|
||||
below).
|
||||
They must come first in the line.
|
||||
.Pp
|
||||
Instructions are assembled into Game Boy opcodes.
|
||||
Multiple instructions on one line can be separated by double colons
|
||||
@@ -254,7 +253,7 @@ Although, for these examples,
|
||||
.Ic STRFMT
|
||||
would be more appropriate; see
|
||||
.Sx String expressions
|
||||
further below.
|
||||
below.
|
||||
.Sh EXPRESSIONS
|
||||
An expression can be composed of many things.
|
||||
Numeric expressions are always evaluated using signed 32-bit math.
|
||||
@@ -268,21 +267,21 @@ This is generally always the case, unless a label is involved, as explained in t
|
||||
section.
|
||||
However, some operators can be constant even with non-constant operands, as explained in
|
||||
.Sx Operators
|
||||
further below.
|
||||
below.
|
||||
.Pp
|
||||
The instructions in the macro-language generally require constant expressions.
|
||||
.Ss Numeric formats
|
||||
There are a number of numeric formats.
|
||||
.Bl -column -offset indent "Precise fixed-point" "Prefix"
|
||||
.It Sy Format type Ta Sy Prefix Ta Sy Accepted characters
|
||||
.It Hexadecimal Ta $ Ta 0123456789ABCDEF
|
||||
.Bl -column -offset indent "Precise fixed-point" "Possible prefixes"
|
||||
.It Sy Format type Ta Sy Possible prefixes Ta Sy Accepted characters
|
||||
.It Decimal Ta none Ta 0123456789
|
||||
.It Octal Ta & Ta 01234567
|
||||
.It Binary Ta % Ta 01
|
||||
.It Hexadecimal Ta Li $ , 0x , 0X Ta 0123456789ABCDEF
|
||||
.It Octal Ta Li & , 0o , 0O Ta 01234567
|
||||
.It Binary Ta Li % , 0b , 0B Ta 01
|
||||
.It Fixed-point Ta none Ta 01234.56789
|
||||
.It Precise fixed-point Ta none Ta 12.34q8
|
||||
.It Character constant Ta none Ta \(dqABYZ\(dq
|
||||
.It Game Boy graphics Ta \` Ta 0123
|
||||
.It Game Boy graphics Ta Li \` Ta 0123
|
||||
.El
|
||||
.Pp
|
||||
Underscores are also accepted in numbers, except at the beginning of one.
|
||||
@@ -310,26 +309,35 @@ is equivalent to
|
||||
.Pp
|
||||
You can also use symbols, which are implicitly replaced with their value.
|
||||
.Ss Operators
|
||||
A great number of operators you can use in expressions are available (listed from highest to lowest precedence):
|
||||
You can use these operators in numeric expressions (listed from highest to lowest precedence):
|
||||
.Bl -column -offset indent "!= == <= >= < >"
|
||||
.It Sy Operator Ta Sy Meaning
|
||||
.It Li \&( \&) Ta Precedence override
|
||||
.It Li \&( \&) Ta Grouping
|
||||
.It Li FUNC() Ta Built-in function call
|
||||
.It Li ** Ta Exponent
|
||||
.It Li ~ + - Ta Unary complement/plus/minus
|
||||
.It Li * / % Ta Multiply/divide/modulo
|
||||
.It Li << Ta Shift left
|
||||
.It Li >> Ta Signed shift right (sign-extension)
|
||||
.It Li >>> Ta Unsigned shift right (zero-extension)
|
||||
.It Li & \&| ^ Ta Binary and/or/xor
|
||||
.It Li + - Ta Add/subtract
|
||||
.It Li != == <= >= < > Ta Comparison
|
||||
.It Li && || Ta Boolean and/or
|
||||
.It Li \&! Ta Unary not
|
||||
.It Li ** Ta Exponentiation
|
||||
.It Li + - ~ \&! Ta Unary plus, minus (negation), complement (bitwise negation), and Boolean negation
|
||||
.It Li * / % Ta Multiplication, division, and modulo (remainder)
|
||||
.It Li << >> >>> Ta Bit shifts (left, sign-extended right, zero-extended right)
|
||||
.It Li & \&| ^ Ta Bitwise AND/OR/XOR
|
||||
.It Li + - Ta Addition and subtraction
|
||||
.It Li == != < > <= >= Ta Comparisons
|
||||
.It Li && Ta Boolean AND
|
||||
.It Li || Ta Boolean OR
|
||||
.El
|
||||
.Pp
|
||||
.Sq **
|
||||
raises a number to a non-negative power. It is the only
|
||||
.Em right-associative
|
||||
operator, meaning that
|
||||
.Ql p ** q ** r
|
||||
is equal to
|
||||
.Ql p ** (q ** r) ,
|
||||
not
|
||||
.Ql (p ** q) ** r .
|
||||
All other binary operators are left-associative.
|
||||
.Pp
|
||||
.Sq ~
|
||||
complements a value by inverting all its bits.
|
||||
complements a value by inverting all 32 of its bits.
|
||||
.Pp
|
||||
.Sq %
|
||||
is used to get the remainder of the corresponding division, so that
|
||||
@@ -379,8 +387,8 @@ Even a non-constant operand with any non-zero bits will return 0.
|
||||
Besides operators, there are also some functions which have more specialized uses.
|
||||
.Bl -column "BITWIDTH(n)"
|
||||
.It Sy Name Ta Sy Operation
|
||||
.It Fn HIGH n Ta Equivalent to Ql Ar n No & $FF .
|
||||
.It Fn LOW n Ta Equivalent to Ql Po Ns Ar n No & $FF00 Pc >> 8 .
|
||||
.It Fn HIGH n Ta Equivalent to Ql Po Ns Ar n No & $FF00 Pc >> 8 .
|
||||
.It Fn LOW n Ta Equivalent to Ql Ar n No & $FF .
|
||||
.EQ
|
||||
delim $$
|
||||
.EN
|
||||
@@ -904,7 +912,7 @@ SECTION "LOAD example", ROMX
|
||||
CopyCode:
|
||||
ld de, RAMCode
|
||||
ld hl, RAMLocation
|
||||
ld c, RAMLocation.end - RAMLocation
|
||||
ld c, RAMCode.end - RAMCode
|
||||
\&.loop
|
||||
ld a, [de]
|
||||
inc de
|
||||
@@ -928,8 +936,8 @@ RAMLocation:
|
||||
|
||||
\&.string
|
||||
db "Hello World!\e0"
|
||||
\&.end
|
||||
ENDL
|
||||
\&.end
|
||||
.Ed
|
||||
.Pp
|
||||
A
|
||||
@@ -939,7 +947,9 @@ block feels similar to a
|
||||
declaration because it creates a new one.
|
||||
All data and code generated within such a block is placed in the current section like usual, but all labels are created as if they were placed in this newly-created section.
|
||||
.Pp
|
||||
In the example above, all of the code and data will end up in the "LOAD example" section.
|
||||
In the example above, all of the code and data will end up in the
|
||||
.Dq LOAD example
|
||||
section.
|
||||
You will notice the
|
||||
.Sq RAMCode
|
||||
and
|
||||
@@ -951,15 +961,30 @@ You cannot nest
|
||||
.Ic LOAD
|
||||
blocks, nor can you change or stop the current section within them.
|
||||
.Pp
|
||||
The current
|
||||
.Ic LOAD
|
||||
block can be ended by using
|
||||
.Ic ENDL .
|
||||
This directive is only necessary if you want to resume writing code in its containing ROM section.
|
||||
Any of
|
||||
.Ic LOAD , SECTION , ENDSECTION ,
|
||||
or
|
||||
.Ic POPS
|
||||
will end the current
|
||||
.Ic LOAD
|
||||
block before performing its own function.
|
||||
.Pp
|
||||
.Ic LOAD
|
||||
blocks can use the
|
||||
.Ic UNION
|
||||
or
|
||||
.Ic FRAGMENT
|
||||
modifiers, as described below.
|
||||
modifiers as described in
|
||||
.Sx Unionized sections
|
||||
below.
|
||||
.Ss Unionized sections
|
||||
When you're tight on RAM, you may want to define overlapping static memory allocations, as explained in the
|
||||
.Sx Unions
|
||||
.Sx Allocating overlapping spaces in RAM
|
||||
section.
|
||||
However, a
|
||||
.Ic UNION
|
||||
@@ -1000,7 +1025,7 @@ or
|
||||
.El
|
||||
.Pp
|
||||
Different declarations of the same unionized section are not appended, but instead overlaid on top of each other, just like
|
||||
.Sx Unions .
|
||||
.Sx Allocating overlapping spaces in RAM .
|
||||
Similarly, the size of an unionized section is the largest of all its declarations.
|
||||
.Ss Section fragments
|
||||
Section fragments are sections with a small twist: when several of the same name are encountered, they are concatenated instead of producing an error.
|
||||
@@ -1123,7 +1148,7 @@ otherwise, it is said to be
|
||||
.Dq exported ,
|
||||
explained in
|
||||
.Sx Exporting and importing symbols
|
||||
further below).
|
||||
below).
|
||||
More than one dot in label names is not allowed.
|
||||
.Pp
|
||||
For convenience, local labels can use a shorthand syntax: when a symbol name starting with a dot is found (for example, inside an expression, or when declaring a label), then the current
|
||||
@@ -1553,47 +1578,6 @@ environment variable if that is defined as a UNIX timestamp.
|
||||
Refer to the spec at
|
||||
.Lk https://reproducible-builds.org/docs/source-date-epoch/ reproducible-builds.org .
|
||||
.Sh DEFINING DATA
|
||||
.Ss Statically allocating space in RAM
|
||||
.Ic DS
|
||||
statically allocates a number of empty bytes.
|
||||
This is the preferred method of allocating space in a RAM section.
|
||||
You can also use
|
||||
.Ic DB , DW
|
||||
and
|
||||
.Ic DL
|
||||
without any arguments instead (see
|
||||
.Sx Defining constant data in ROM
|
||||
below).
|
||||
.Bd -literal -offset indent
|
||||
DS 42 ;\ Allocates 42 bytes
|
||||
.Ed
|
||||
.Pp
|
||||
Empty space in RAM sections will not be initialized.
|
||||
In ROM sections, it will be filled with the value passed to the
|
||||
.Fl p
|
||||
command-line option, except when using overlays with
|
||||
.Fl O .
|
||||
.Pp
|
||||
Instead of an exact number of bytes, you can specify
|
||||
.Ic ALIGN Ns Bq Ar align , offset
|
||||
to allocate however many bytes are required to align the subsequent data.
|
||||
Thus,
|
||||
.Sq Ic DS ALIGN Ns Bo Ar align , offset Bc , No ...
|
||||
is equivalent to
|
||||
.Sq Ic DS Ar n , No ...
|
||||
followed by
|
||||
.Sq Ic ALIGN Ns Bq Ar align , offset ,
|
||||
where
|
||||
.Ar n
|
||||
is the minimum value needed to satisfy the
|
||||
.Ic ALIGN
|
||||
constraint (see
|
||||
.Sx Requesting alignment
|
||||
below).
|
||||
Note that
|
||||
.Ic ALIGN Ns Bq Ar align
|
||||
is a shorthand for
|
||||
.Ic ALIGN Ns Bq Ar align , No 0 .
|
||||
.Ss Defining constant data in ROM
|
||||
.Ic DB
|
||||
defines a list of bytes that will be stored in the final image.
|
||||
@@ -1660,7 +1644,7 @@ can be used in a
|
||||
/
|
||||
.Ic SRAM
|
||||
section.
|
||||
.Ss Including binary files
|
||||
.Ss Including binary data files
|
||||
You probably have some graphics, level data, etc. you'd like to include.
|
||||
Use
|
||||
.Ic INCBIN
|
||||
@@ -1684,7 +1668,48 @@ INCBIN "data.bin", 78, 256
|
||||
.Pp
|
||||
The length argument is optional.
|
||||
If only the start position is specified, the bytes from the start position until the end of the file will be included.
|
||||
.Ss Unions
|
||||
.Ss Statically allocating space in RAM
|
||||
.Ic DS
|
||||
statically allocates a number of empty bytes.
|
||||
This is the preferred method of allocating space in a RAM section.
|
||||
You can also use
|
||||
.Ic DB , DW
|
||||
and
|
||||
.Ic DL
|
||||
without any arguments instead (see
|
||||
.Sx Defining constant data in ROM
|
||||
below).
|
||||
.Bd -literal -offset indent
|
||||
DS 42 ;\ Allocates 42 bytes
|
||||
.Ed
|
||||
.Pp
|
||||
Empty space in RAM sections will not be initialized.
|
||||
In ROM sections, it will be filled with the value passed to the
|
||||
.Fl p
|
||||
command-line option, except when using overlays with
|
||||
.Fl O .
|
||||
.Pp
|
||||
Instead of an exact number of bytes, you can specify
|
||||
.Ic ALIGN Ns Bq Ar align , offset
|
||||
to allocate however many bytes are required to align the subsequent data.
|
||||
Thus,
|
||||
.Sq Ic DS ALIGN Ns Bo Ar align , offset Bc , No ...
|
||||
is equivalent to
|
||||
.Sq Ic DS Ar n , No ...
|
||||
followed by
|
||||
.Sq Ic ALIGN Ns Bq Ar align , offset ,
|
||||
where
|
||||
.Ar n
|
||||
is the minimum value needed to satisfy the
|
||||
.Ic ALIGN
|
||||
constraint (see
|
||||
.Sx Requesting alignment
|
||||
below).
|
||||
Note that
|
||||
.Ic ALIGN Ns Bq Ar align
|
||||
is a shorthand for
|
||||
.Ic ALIGN Ns Bq Ar align , No 0 .
|
||||
.Ss Allocating overlapping spaces in RAM
|
||||
Unions allow multiple static memory allocations to overlap, like unions in C.
|
||||
This does not increase the amount of memory available, but allows re-using the same memory region for different purposes.
|
||||
.Pp
|
||||
@@ -1745,6 +1770,37 @@ Unions may be used in any section, but they may only contain space-allocating di
|
||||
.Ic DS
|
||||
(see
|
||||
.Sx Statically allocating space in RAM ) .
|
||||
.Ss Requesting alignment
|
||||
While
|
||||
.Ic ALIGN
|
||||
as presented in
|
||||
.Sx SECTIONS
|
||||
is often useful as-is, sometimes you instead want a particular piece of data (or code) in the middle of the section to be aligned.
|
||||
This is made easier through the use of mid-section
|
||||
.Ic ALIGN Ar align , offset .
|
||||
It will retroactively alter the section's attributes to ensure that the location the
|
||||
.Ic ALIGN
|
||||
directive is at, has its
|
||||
.Ar align
|
||||
lower bits equal to
|
||||
.Ar offset .
|
||||
.Pp
|
||||
If the constraint cannot be met (for example because the section is fixed at an incompatible address), an error is produced.
|
||||
Note that
|
||||
.Ic ALIGN Ar align
|
||||
is a shorthand for
|
||||
.Ic ALIGN Ar align , No 0 .
|
||||
.Pp
|
||||
There may be times when you don't just want to specify an alignment constraint at the current location, but also skip ahead until the constraint can be satisfied.
|
||||
In that case, you can use
|
||||
.Ic DS ALIGN Ns Bq Ar align , offset
|
||||
to allocate however many bytes are required to align the subsequent data.
|
||||
.Pp
|
||||
If the constraint cannot be met by skipping any amount of space, an error is produced.
|
||||
Note that
|
||||
.Ic ALIGN Ns Bq Ar align
|
||||
is a shorthand for
|
||||
.Ic ALIGN Ns Bq Ar align , No 0 .
|
||||
.Sh THE MACRO LANGUAGE
|
||||
.Ss Invoking macros
|
||||
A macro is invoked by using its name at the beginning of a line, like a directive, followed by any comma-separated arguments.
|
||||
@@ -2283,9 +2339,9 @@ POPO
|
||||
.Pp
|
||||
.Ic OPT
|
||||
can modify the options
|
||||
.Cm b , g , p , Q ,
|
||||
.Cm b , g , p , Q , r ,
|
||||
and
|
||||
.Cm r .
|
||||
.Cm W .
|
||||
.Pp
|
||||
.Ic POPO
|
||||
and
|
||||
@@ -2306,37 +2362,6 @@ PUSHO b.X, g.oOX
|
||||
DW `..ooOOXX
|
||||
POPO
|
||||
.Ed
|
||||
.Ss Requesting alignment
|
||||
While
|
||||
.Ic ALIGN
|
||||
as presented in
|
||||
.Sx SECTIONS
|
||||
is often useful as-is, sometimes you instead want a particular piece of data (or code) in the middle of the section to be aligned.
|
||||
This is made easier through the use of mid-section
|
||||
.Ic ALIGN Ar align , offset .
|
||||
It will alter the section's attributes to ensure that the location the
|
||||
.Ic ALIGN
|
||||
directive is at, has its
|
||||
.Ar align
|
||||
lower bits equal to
|
||||
.Ar offset .
|
||||
.Pp
|
||||
If the constraint cannot be met (for example because the section is fixed at an incompatible address), an error is produced.
|
||||
Note that
|
||||
.Ic ALIGN Ar align
|
||||
is a shorthand for
|
||||
.Ic ALIGN Ar align , No 0 .
|
||||
.Pp
|
||||
There may be times when you don't just want to specify an alignment constraint at the current location, but also skip ahead until the constraint can be satisfied.
|
||||
In that case, you can use
|
||||
.Ic DS ALIGN Ns Bq Ar align , offset
|
||||
to allocate however many bytes are required to align the subsequent data.
|
||||
.Pp
|
||||
If the constraint cannot be met by skipping any amount of space, an error is produced.
|
||||
Note that
|
||||
.Ic ALIGN Ns Bq Ar align
|
||||
is a shorthand for
|
||||
.Ic ALIGN Ns Bq Ar align , No 0 .
|
||||
.Sh SEE ALSO
|
||||
.Xr rgbasm 1 ,
|
||||
.Xr rgblink 1 ,
|
||||
@@ -2344,6 +2369,7 @@ is a shorthand for
|
||||
.Xr rgbfix 1 ,
|
||||
.Xr rgbgfx 1 ,
|
||||
.Xr gbz80 7 ,
|
||||
.Xr rgbasm-old 5 ,
|
||||
.Xr rgbds 5 ,
|
||||
.Xr rgbds 7
|
||||
.Sh HISTORY
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd September 18, 2024
|
||||
.Dd December 25, 2024
|
||||
.Dt RGBDS 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -254,7 +254,7 @@ Size of the
|
||||
below.
|
||||
.It Cm BYTE Ar RPNExpr Ns Bq RPNSize
|
||||
The patch's value, encoded as a RPN expression
|
||||
.Pq see Sx RPN EXPRESSIONS .
|
||||
.Pq see Sx RPN expressions .
|
||||
.El
|
||||
.It Cm ENDR
|
||||
.El
|
||||
@@ -294,14 +294,14 @@ Size of the
|
||||
below.
|
||||
.It Cm BYTE Ar RPNExpr Ns Bq RPNSize
|
||||
The patch's value, encoded as a RPN expression
|
||||
.Pq see Sx RPN EXPRESSIONS .
|
||||
.Pq see Sx RPN expressions .
|
||||
.It Cm STRING Ar Message
|
||||
The message displayed if the expression evaluates to a non-zero value.
|
||||
If empty, a generic message is displayed instead.
|
||||
.El
|
||||
.It Cm ENDR
|
||||
.El
|
||||
.Ss RPN EXPRESSIONS
|
||||
.Ss RPN expressions
|
||||
Expressions in the object file are stored as RPN, or
|
||||
.Dq Reverse Polish Notation ,
|
||||
which is a notation that allows computing arbitrary expressions with just a simple stack.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd September 18, 2024
|
||||
.Dd December 25, 2024
|
||||
.Dt RGBDS 7
|
||||
.Os
|
||||
.Sh NAME
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd September 18, 2024
|
||||
.Dd December 25, 2024
|
||||
.Dt RGBFIX 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
|
||||
17
man/rgbgfx.1
17
man/rgbgfx.1
@@ -2,7 +2,7 @@
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd September 18, 2024
|
||||
.Dd December 25, 2024
|
||||
.Dt RGBGFX 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -10,7 +10,7 @@
|
||||
.Nd Game Boy graphics converter
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl CmOuVZ
|
||||
.Op Fl CmOuVXYZ
|
||||
.Op Fl v Op Fl v No ...
|
||||
.Op Fl a Ar attrmap | Fl A
|
||||
.Op Fl b Ar base_ids
|
||||
@@ -229,9 +229,8 @@ The second number pair specifies how many tiles to process horizontally and vert
|
||||
.Pp
|
||||
.Fl L Sy is ignored in reverse mode , No no padding is inserted .
|
||||
.It Fl m , Fl \-mirror-tiles
|
||||
Deduplicate tiles that are symmetrical mirror images of each other.
|
||||
Deduplicate tiles that are horizontally and/or vertically symmetrical mirror images of each other.
|
||||
Only one of each unique tile will be saved in the tile data file, with mirror images counting as duplicates.
|
||||
Tiles are checked for horizontal, vertical, and horizontal-vertical mirroring.
|
||||
Useful with a tile map and attribute map together (see
|
||||
.Fl a
|
||||
and
|
||||
@@ -239,6 +238,8 @@ and
|
||||
to keep track of the duplicated tiles and the dimension(s) mirrored.
|
||||
Implies
|
||||
.Fl u .
|
||||
Equivalent to
|
||||
.Fl XY .
|
||||
.It Fl N Ar nb_tiles , Fl \-nb-tiles Ar nb_tiles
|
||||
Set a maximum number of tiles that can be placed in each VRAM bank.
|
||||
.Ar nb_tiles
|
||||
@@ -353,6 +354,10 @@ Some internal debug printing is enabled.
|
||||
The verbosity level does not go past 6.
|
||||
.Pp
|
||||
Note that verbose output is only intended to be consumed by humans, and may change without notice between RGBDS releases; relying on those for scripts is not advised.
|
||||
.It Fl X , Fl \-mirror-x
|
||||
Deduplicate tiles that are horizontally symmetrical mirror images of each other across the X axis.
|
||||
Implies
|
||||
.Fl u .
|
||||
.It Fl x Ar quantity , Fl \-trim-end Ar quantity
|
||||
Do not output the last
|
||||
.Ar quantity
|
||||
@@ -373,6 +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
|
||||
.Fl N Ap s
|
||||
limit.
|
||||
.It Fl Y , Fl \-mirror-y
|
||||
Deduplicate tiles that are vertically symmetrical mirror images of each other across the Y axis.
|
||||
Implies
|
||||
.Fl u .
|
||||
.It Fl Z , Fl \-columns
|
||||
Read squares from the PNG in column-major order (column by column), instead of the default row-major order (line by line).
|
||||
This primarily affects tile map and attribute map output, although it may also change generated tile data and palettes.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd September 18, 2024
|
||||
.Dd December 25, 2024
|
||||
.Dt RGBLINK 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd September 18, 2024
|
||||
.Dd December 25, 2024
|
||||
.Dt RGBLINK 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
|
||||
@@ -91,10 +91,11 @@ foreach(PROG "asm" "fix" "gfx" "link")
|
||||
${rgb${PROG}_src}
|
||||
${common_src}
|
||||
)
|
||||
if(SUFFIX)
|
||||
set_target_properties(rgb${PROG} PROPERTIES SUFFIX ${SUFFIX})
|
||||
endif()
|
||||
install(TARGETS rgb${PROG} RUNTIME DESTINATION bin)
|
||||
# Required to run tests
|
||||
set_target_properties(rgb${PROG} PROPERTIES
|
||||
# hack for MSVC: no-op generator expression to stop generation of "per-configuration subdirectory"
|
||||
RUNTIME_OUTPUT_DIRECTORY $<1:${CMAKE_SOURCE_DIR}>)
|
||||
endforeach()
|
||||
|
||||
if(LIBPNG_FOUND) # pkg-config
|
||||
|
||||
@@ -53,7 +53,7 @@ bool charmap_ForEach(
|
||||
mappings[nodeIdx] = mapping;
|
||||
for (unsigned c = 0; c < 256; c++) {
|
||||
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);
|
||||
@@ -64,7 +64,7 @@ bool charmap_ForEach(
|
||||
}
|
||||
|
||||
void charmap_New(std::string const &name, std::string const *baseName) {
|
||||
size_t baseIdx = (size_t)-1;
|
||||
size_t baseIdx = SIZE_MAX;
|
||||
|
||||
if (baseName != nullptr) {
|
||||
if (auto search = charmapMap.find(*baseName); search == charmapMap.end())
|
||||
@@ -82,7 +82,7 @@ void charmap_New(std::string const &name, std::string const *baseName) {
|
||||
charmapMap[name] = charmapList.size();
|
||||
Charmap &charmap = charmapList.emplace_back();
|
||||
|
||||
if (baseIdx != (size_t)-1)
|
||||
if (baseIdx != SIZE_MAX)
|
||||
charmap.nodes = charmapList[baseIdx].nodes; // Copies `charmapList[baseIdx].nodes`
|
||||
else
|
||||
charmap.nodes.emplace_back(); // Zero-init the root node
|
||||
@@ -113,6 +113,12 @@ void charmap_Pop() {
|
||||
charmapStack.pop();
|
||||
}
|
||||
|
||||
void charmap_CheckStack() {
|
||||
if (!charmapStack.empty()) {
|
||||
warning(WARNING_UNMATCHED_DIRECTIVE, "`PUSHC` without corresponding `POPC`\n");
|
||||
}
|
||||
}
|
||||
|
||||
void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value) {
|
||||
if (mapping.empty()) {
|
||||
error("Cannot map an empty string\n");
|
||||
@@ -123,7 +129,7 @@ void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value) {
|
||||
size_t nodeIdx = 0;
|
||||
|
||||
for (char c : mapping) {
|
||||
size_t &nextIdxRef = charmap.nodes[nodeIdx].next[(uint8_t)c];
|
||||
size_t &nextIdxRef = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(c)];
|
||||
size_t nextIdx = nextIdxRef;
|
||||
|
||||
if (!nextIdx) {
|
||||
@@ -151,7 +157,7 @@ bool charmap_HasChar(std::string const &input) {
|
||||
size_t nodeIdx = 0;
|
||||
|
||||
for (char c : input) {
|
||||
nodeIdx = charmap.nodes[nodeIdx].next[(uint8_t)c];
|
||||
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(c)];
|
||||
|
||||
if (!nodeIdx)
|
||||
return false;
|
||||
@@ -178,7 +184,7 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output
|
||||
size_t inputIdx = 0;
|
||||
|
||||
for (size_t nodeIdx = 0; inputIdx < input.length();) {
|
||||
nodeIdx = charmap.nodes[nodeIdx].next[(uint8_t)input[inputIdx]];
|
||||
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(input[inputIdx])];
|
||||
|
||||
if (!nodeIdx)
|
||||
break;
|
||||
|
||||
@@ -29,7 +29,7 @@ static int32_t double2fix(double d, int32_t q) {
|
||||
return 0;
|
||||
if (isinf(d))
|
||||
return d < 0 ? INT32_MIN : INT32_MAX;
|
||||
return (int32_t)round(d * pow(2.0, q));
|
||||
return static_cast<int32_t>(round(d * pow(2.0, q)));
|
||||
}
|
||||
|
||||
static double turn2rad(double t) {
|
||||
|
||||
@@ -186,7 +186,8 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
||||
useExact = true;
|
||||
}
|
||||
|
||||
if (useType != 'X' && useType != 'x' && useType != 'b' && useType != 'o' && useType != 'f' && useExact)
|
||||
if (useType != 'X' && useType != 'x' && useType != 'b' && useType != 'o' && useType != 'f'
|
||||
&& useExact)
|
||||
error("Formatting type '%c' with exact flag '#'\n", useType);
|
||||
if (useType != 'f' && hasFrac)
|
||||
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));
|
||||
if (useExact)
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*fq%zu", (int)useFracWidth, fval, usePrec);
|
||||
if (int fracWidthArg = static_cast<int>(useFracWidth); useExact)
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*fq%zu", fracWidthArg, fval, usePrec);
|
||||
else
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*f", (int)useFracWidth, fval);
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*f", fracWidthArg, fval);
|
||||
} else if (useType == 'd') {
|
||||
// Decimal numbers may be formatted with a '-' sign by `snprintf`, so `abs` prevents that,
|
||||
// with a special case for `INT32_MIN` since `labs(INT32_MIN)` is UB. The sign will be
|
||||
// printed later from `signChar`.
|
||||
uint32_t uval = value != (uint32_t)INT32_MIN ? labs((int32_t)value) : value;
|
||||
uint32_t uval =
|
||||
value != static_cast<uint32_t>(INT32_MIN) ? labs(static_cast<int32_t>(value)) : value;
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%" PRIu32, uval);
|
||||
} else {
|
||||
char const *spec = useType == 'u' ? "%" PRIu32
|
||||
|
||||
@@ -162,7 +162,7 @@ bool yywrap() {
|
||||
// If the node is referenced outside this context, we can't edit it, so duplicate it
|
||||
if (context.fileInfo.use_count() > 1) {
|
||||
context.fileInfo = std::make_shared<FileStackNode>(*context.fileInfo);
|
||||
context.fileInfo->ID = -1; // The copy is not yet registered
|
||||
context.fileInfo->ID = UINT32_MAX; // The copy is not yet registered
|
||||
}
|
||||
|
||||
std::vector<uint32_t> &fileInfoIters = context.fileInfo->iters();
|
||||
@@ -170,8 +170,10 @@ bool yywrap() {
|
||||
// If this is a FOR, update the symbol value
|
||||
if (context.isForLoop && fileInfoIters.front() <= context.nbReptIters) {
|
||||
// Avoid arithmetic overflow runtime error
|
||||
uint32_t forValue = (uint32_t)context.forValue + (uint32_t)context.forStep;
|
||||
context.forValue = forValue <= INT32_MAX ? forValue : -(int32_t)~forValue - 1;
|
||||
uint32_t forValue =
|
||||
static_cast<uint32_t>(context.forValue) + static_cast<uint32_t>(context.forStep);
|
||||
context.forValue =
|
||||
forValue <= INT32_MAX ? forValue : -static_cast<int32_t>(~forValue) - 1;
|
||||
Symbol *sym = sym_AddVar(context.forName, context.forValue);
|
||||
|
||||
// This error message will refer to the current iteration
|
||||
@@ -347,9 +349,9 @@ void fstk_RunFor(
|
||||
|
||||
uint32_t count = 0;
|
||||
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)
|
||||
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)
|
||||
error("FOR cannot have a step value of 0\n");
|
||||
|
||||
|
||||
@@ -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());
|
||||
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 {
|
||||
@@ -102,7 +102,7 @@ struct FileUnmapDeleter {
|
||||
using namespace std::literals;
|
||||
|
||||
// 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 {
|
||||
int type;
|
||||
@@ -329,6 +329,8 @@ static std::unordered_map<std::string, int, CaseInsensitive, CaseInsensitive> ke
|
||||
{"OPT", T_(POP_OPT) },
|
||||
};
|
||||
|
||||
static auto ldio = keywordDict.find("LDIO");
|
||||
|
||||
static bool isWhitespace(int c) {
|
||||
return c == ' ' || c == '\t';
|
||||
}
|
||||
@@ -422,7 +424,7 @@ bool LexerState::setFileAsNextState(std::string const &filePath, bool updateStat
|
||||
|
||||
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
|
||||
if (char *mappingAddr = mapFile(fd, path, size); mappingAddr != nullptr) {
|
||||
close(fd);
|
||||
@@ -541,7 +543,7 @@ size_t BufferedContent::readMore(size_t startIndex, size_t nbChars) {
|
||||
size += nbReadChars;
|
||||
|
||||
// `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) {
|
||||
@@ -707,20 +709,20 @@ int LexerState::peekChar() {
|
||||
// This is `.peekCharAhead()` modified for zero lookahead distance
|
||||
for (Expansion &exp : expansions) {
|
||||
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>()) {
|
||||
auto &view = content.get<ViewedContent>();
|
||||
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 {
|
||||
auto &cbuf = content.get<BufferedContent>();
|
||||
if (cbuf.size == 0)
|
||||
cbuf.refill();
|
||||
assume(cbuf.offset < LEXER_BUF_SIZE);
|
||||
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
|
||||
@@ -736,21 +738,21 @@ int LexerState::peekCharAhead() {
|
||||
// and `.peekCharAhead()` will continue with its parent
|
||||
assume(exp.offset <= 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;
|
||||
}
|
||||
|
||||
if (content.holds<ViewedContent>()) {
|
||||
auto &view = content.get<ViewedContent>();
|
||||
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 {
|
||||
auto &cbuf = content.get<BufferedContent>();
|
||||
assume(distance < LEXER_BUF_SIZE);
|
||||
if (cbuf.size <= distance)
|
||||
cbuf.refill();
|
||||
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
|
||||
@@ -797,11 +799,9 @@ static int peek() {
|
||||
} else if (c == '{' && !lexerState->disableInterpolation) {
|
||||
// If character is an open brace, do symbol interpolation
|
||||
shiftChar();
|
||||
|
||||
if (auto str = readInterpolation(0); str) {
|
||||
beginExpansion(str, *str);
|
||||
}
|
||||
|
||||
return peek();
|
||||
}
|
||||
|
||||
@@ -1032,11 +1032,12 @@ static uint32_t readFractionalPart(uint32_t integer) {
|
||||
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");
|
||||
|
||||
// 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;
|
||||
}
|
||||
@@ -1170,8 +1171,12 @@ static Token readIdentifier(char firstChar, bool raw) {
|
||||
|
||||
// Attempt to check for a keyword if the identifier is not 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);
|
||||
}
|
||||
}
|
||||
|
||||
// Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot
|
||||
@@ -1201,9 +1206,9 @@ static std::shared_ptr<std::string> readInterpolation(size_t depth) {
|
||||
|
||||
if (c == '{') { // Nested interpolation
|
||||
shiftChar();
|
||||
auto str = readInterpolation(depth + 1);
|
||||
|
||||
beginExpansion(str, *str);
|
||||
if (auto str = readInterpolation(depth + 1); str) {
|
||||
beginExpansion(str, *str);
|
||||
}
|
||||
continue; // Restart, reading from the new buffer
|
||||
} else if (c == EOF || c == '\r' || c == '\n' || c == '"') {
|
||||
error("Missing }\n");
|
||||
@@ -1373,6 +1378,7 @@ static std::string readString(bool raw) {
|
||||
|
||||
// Line continuation
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\r':
|
||||
case '\n':
|
||||
discardLineContinuation();
|
||||
@@ -1505,6 +1511,7 @@ static void appendStringLiteral(std::string &str, bool raw) {
|
||||
|
||||
// Line continuation
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\r':
|
||||
case '\n':
|
||||
discardLineContinuation();
|
||||
@@ -1731,7 +1738,25 @@ static Token yylex_NORMAL() {
|
||||
|
||||
// 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 '2':
|
||||
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);
|
||||
|
||||
return token;
|
||||
@@ -1970,6 +2007,7 @@ backslash:
|
||||
break;
|
||||
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\r':
|
||||
case '\n':
|
||||
discardLineContinuation();
|
||||
|
||||
@@ -55,10 +55,10 @@ void MacroArgs::appendArg(std::shared_ptr<std::string> arg) {
|
||||
|
||||
void MacroArgs::shiftArgs(int32_t count) {
|
||||
if (size_t nbArgs = args.size();
|
||||
count > 0 && ((uint32_t)count > nbArgs || shift > nbArgs - count)) {
|
||||
count > 0 && (static_cast<uint32_t>(count) > nbArgs || shift > nbArgs - count)) {
|
||||
warning(WARNING_MACRO_SHIFT, "Cannot shift macro arguments past their end\n");
|
||||
shift = nbArgs;
|
||||
} else if (count < 0 && shift < (uint32_t)-count) {
|
||||
} else if (count < 0 && shift < static_cast<uint32_t>(-count)) {
|
||||
warning(WARNING_MACRO_SHIFT, "Cannot shift macro arguments past their beginning\n");
|
||||
shift = 0;
|
||||
} else {
|
||||
|
||||
@@ -112,7 +112,7 @@ int main(int argc, char *argv[]) {
|
||||
// Support SOURCE_DATE_EPOCH for reproducible builds
|
||||
// https://reproducible-builds.org/docs/source-date-epoch/
|
||||
if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch)
|
||||
now = (time_t)strtoul(sourceDateEpoch, nullptr, 0);
|
||||
now = static_cast<time_t>(strtoul(sourceDateEpoch, nullptr, 0));
|
||||
|
||||
Defer closeDependFile{[&] {
|
||||
if (dependFile)
|
||||
@@ -293,7 +293,7 @@ int main(int argc, char *argv[]) {
|
||||
break;
|
||||
|
||||
case 'W':
|
||||
processWarningFlag(musl_optarg);
|
||||
opt_W(musl_optarg);
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
@@ -381,8 +381,13 @@ int main(int argc, char *argv[]) {
|
||||
nbErrors = 1;
|
||||
|
||||
sect_CheckUnionClosed();
|
||||
sect_CheckLoadClosed();
|
||||
sect_CheckSizes();
|
||||
|
||||
charmap_CheckStack();
|
||||
opt_CheckStack();
|
||||
sect_CheckStack();
|
||||
|
||||
if (nbErrors != 0)
|
||||
errx("Assembly aborted (%u error%s)!", nbErrors, nbErrors == 1 ? "" : "s");
|
||||
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
#include "asm/section.hpp"
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
static constexpr size_t numWarningStates = sizeof(warningStates);
|
||||
|
||||
struct OptStackEntry {
|
||||
char binary[2];
|
||||
char gbgfx[4];
|
||||
@@ -22,7 +20,7 @@ struct OptStackEntry {
|
||||
uint8_t fillByte;
|
||||
bool warningsAreErrors;
|
||||
size_t maxRecursionDepth;
|
||||
WarningState warningStates[numWarningStates];
|
||||
Diagnostics warningStates;
|
||||
};
|
||||
|
||||
static std::stack<OptStackEntry> stack;
|
||||
@@ -160,7 +158,7 @@ void opt_Push() {
|
||||
|
||||
// Both of these pulled from warning.hpp
|
||||
entry.warningsAreErrors = warningsAreErrors;
|
||||
memcpy(entry.warningStates, warningStates, numWarningStates);
|
||||
entry.warningStates = warningStates;
|
||||
|
||||
entry.maxRecursionDepth = maxRecursionDepth; // Pulled from fstack.h
|
||||
|
||||
@@ -184,5 +182,11 @@ void opt_Pop() {
|
||||
|
||||
// opt_W does not apply a whole warning state; it processes one flag string
|
||||
warningsAreErrors = entry.warningsAreErrors;
|
||||
memcpy(warningStates, entry.warningStates, numWarningStates);
|
||||
warningStates = entry.warningStates;
|
||||
}
|
||||
|
||||
void opt_CheckStack() {
|
||||
if (!stack.empty()) {
|
||||
warning(WARNING_UNMATCHED_DIRECTIVE, "`PUSHO` without corresponding `POPO`\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "error.hpp"
|
||||
#include "helpers.hpp" // assume, Defer
|
||||
#include "platform.hpp"
|
||||
|
||||
#include "asm/charmap.hpp"
|
||||
#include "asm/fstack.hpp"
|
||||
@@ -39,10 +40,10 @@ static std::deque<std::shared_ptr<FileStackNode>> fileStackNodes;
|
||||
|
||||
static void putLong(uint32_t n, FILE *file) {
|
||||
uint8_t bytes[] = {
|
||||
(uint8_t)n,
|
||||
(uint8_t)(n >> 8),
|
||||
(uint8_t)(n >> 16),
|
||||
(uint8_t)(n >> 24),
|
||||
static_cast<uint8_t>(n),
|
||||
static_cast<uint8_t>(n >> 8),
|
||||
static_cast<uint8_t>(n >> 16),
|
||||
static_cast<uint8_t>(n >> 24),
|
||||
};
|
||||
fwrite(bytes, 1, sizeof(bytes), file);
|
||||
}
|
||||
@@ -54,25 +55,25 @@ static void putString(std::string const &s, FILE *file) {
|
||||
|
||||
void out_RegisterNode(std::shared_ptr<FileStackNode> node) {
|
||||
// If node is not already registered, register it (and parents), and give it a unique ID
|
||||
for (; node && node->ID == (uint32_t)-1; node = node->parent) {
|
||||
for (; node && node->ID == UINT32_MAX; node = node->parent) {
|
||||
node->ID = fileStackNodes.size();
|
||||
fileStackNodes.push_front(node);
|
||||
}
|
||||
}
|
||||
|
||||
// Return a section's ID, or -1 if the section is not in the list
|
||||
// Return a section's ID, or UINT32_MAX if the section is not in the list
|
||||
static uint32_t getSectIDIfAny(Section *sect) {
|
||||
if (!sect)
|
||||
return (uint32_t)-1;
|
||||
return UINT32_MAX;
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
static void writePatch(Patch const &patch, FILE *file) {
|
||||
assume(patch.src->ID != (uint32_t)-1);
|
||||
assume(patch.src->ID != UINT32_MAX);
|
||||
|
||||
putLong(patch.src->ID, file);
|
||||
putLong(patch.lineNo, file);
|
||||
@@ -85,7 +86,7 @@ static void writePatch(Patch const &patch, FILE *file) {
|
||||
}
|
||||
|
||||
static void writeSection(Section const §, FILE *file) {
|
||||
assume(sect.src->ID != (uint32_t)-1);
|
||||
assume(sect.src->ID != UINT32_MAX);
|
||||
|
||||
putString(sect.name, file);
|
||||
|
||||
@@ -118,7 +119,7 @@ static void writeSymbol(Symbol const &sym, FILE *file) {
|
||||
if (!sym.isDefined()) {
|
||||
putc(SYMTYPE_IMPORT, file);
|
||||
} else {
|
||||
assume(sym.src->ID != (uint32_t)-1);
|
||||
assume(sym.src->ID != UINT32_MAX);
|
||||
|
||||
putc(sym.isExported ? SYMTYPE_EXPORT : SYMTYPE_LOCAL, file);
|
||||
putLong(sym.src->ID, file);
|
||||
@@ -130,7 +131,7 @@ static void writeSymbol(Symbol const &sym, FILE *file) {
|
||||
|
||||
static void registerUnregisteredSymbol(Symbol &sym) {
|
||||
// Check for `sym.src`, to skip any built-in symbol from rgbasm
|
||||
if (sym.src && sym.ID == (uint32_t)-1 && !sym_IsPC(&sym)) {
|
||||
if (sym.src && sym.ID == UINT32_MAX && !sym_IsPC(&sym)) {
|
||||
sym.ID = objectSymbols.size(); // Set the symbol's ID within the object file
|
||||
objectSymbols.push_back(&sym);
|
||||
out_RegisterNode(sym.src);
|
||||
@@ -287,7 +288,7 @@ static void writeAssert(Assertion &assert, 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);
|
||||
putc(node.type, file);
|
||||
if (node.type != NODE_REPT) {
|
||||
@@ -311,7 +312,8 @@ void out_WriteObject() {
|
||||
file = fopen(objectFileName.c_str(), "wb");
|
||||
} else {
|
||||
objectFileName = "<stdout>";
|
||||
file = fdopen(STDOUT_FILENO, "wb");
|
||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||
file = stdout;
|
||||
}
|
||||
if (!file)
|
||||
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");
|
||||
} else {
|
||||
name = "<stdout>";
|
||||
file = fdopen(STDOUT_FILENO, "wb");
|
||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||
file = stdout;
|
||||
}
|
||||
if (!file)
|
||||
err("Failed to open state file '%s'", name.c_str());
|
||||
|
||||
154
src/asm/parser.y
154
src/asm/parser.y
@@ -103,7 +103,7 @@
|
||||
/******************** Tokens ********************/
|
||||
|
||||
%token YYEOF 0 "end of file"
|
||||
%token NEWLINE "newline"
|
||||
%token NEWLINE "end of line"
|
||||
%token EOB "end of buffer"
|
||||
|
||||
// General punctuation
|
||||
@@ -140,7 +140,7 @@
|
||||
%left OP_SHL OP_SHR OP_USHR
|
||||
%left OP_MUL OP_DIV OP_MOD
|
||||
%precedence NEG // applies to unary OP_LOGICNOT, OP_ADD, OP_SUB, OP_NOT
|
||||
%left OP_EXP
|
||||
%right OP_EXP
|
||||
|
||||
// Assignment operators (only for variables)
|
||||
%token POP_EQUAL "="
|
||||
@@ -335,7 +335,7 @@
|
||||
%type <Expression> reloc_16bit_no_str
|
||||
|
||||
// Constant numbers
|
||||
%type <int32_t> const
|
||||
%type <int32_t> iconst
|
||||
%type <int32_t> const_no_str
|
||||
%type <int32_t> uconst
|
||||
// Constant numbers used only in specific contexts
|
||||
@@ -404,6 +404,14 @@ asm_file: lines;
|
||||
lines:
|
||||
%empty
|
||||
| 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:
|
||||
@@ -425,30 +433,6 @@ diff_mark:
|
||||
line:
|
||||
plain_directive endofline
|
||||
| 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;
|
||||
@@ -470,7 +454,7 @@ line_directive:
|
||||
;
|
||||
|
||||
if:
|
||||
POP_IF const NEWLINE {
|
||||
POP_IF iconst NEWLINE {
|
||||
lexer_IncIFDepth();
|
||||
|
||||
if ($2)
|
||||
@@ -481,7 +465,7 @@ if:
|
||||
;
|
||||
|
||||
elif:
|
||||
POP_ELIF const NEWLINE {
|
||||
POP_ELIF iconst NEWLINE {
|
||||
if (lexer_GetIFDepth() == 0)
|
||||
fatalerror("Found ELIF outside of an IF construct\n");
|
||||
|
||||
@@ -716,14 +700,14 @@ align_spec:
|
||||
$$.alignOfs = 0;
|
||||
}
|
||||
}
|
||||
| uconst COMMA const {
|
||||
| uconst COMMA iconst {
|
||||
if ($1 > 16) {
|
||||
::error("Alignment must be between 0 and 16, not %u\n", $1);
|
||||
$$.alignment = $$.alignOfs = 0;
|
||||
} else if ($3 <= -(1 << $1) || $3 >= 1 << $1) {
|
||||
::error(
|
||||
"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
|
||||
);
|
||||
$$.alignment = $$.alignOfs = 0;
|
||||
@@ -833,11 +817,11 @@ assert:
|
||||
failAssertMsg($2, $5);
|
||||
}
|
||||
}
|
||||
| POP_STATIC_ASSERT assert_type const {
|
||||
| POP_STATIC_ASSERT assert_type iconst {
|
||||
if ($3 == 0)
|
||||
failAssert($2);
|
||||
}
|
||||
| POP_STATIC_ASSERT assert_type const COMMA string {
|
||||
| POP_STATIC_ASSERT assert_type iconst COMMA string {
|
||||
if ($3 == 0)
|
||||
failAssertMsg($2, $5);
|
||||
}
|
||||
@@ -857,7 +841,7 @@ shift_const:
|
||||
%empty {
|
||||
$$ = 1;
|
||||
}
|
||||
| const
|
||||
| iconst
|
||||
;
|
||||
|
||||
load:
|
||||
@@ -865,7 +849,7 @@ load:
|
||||
sect_SetLoadSection($3, $5, $6, $7, $2);
|
||||
}
|
||||
| POP_ENDL {
|
||||
sect_EndLoadSection();
|
||||
sect_EndLoadSection(nullptr);
|
||||
}
|
||||
;
|
||||
|
||||
@@ -894,17 +878,17 @@ capture_rept:
|
||||
;
|
||||
|
||||
for_args:
|
||||
const {
|
||||
iconst {
|
||||
$$.start = 0;
|
||||
$$.stop = $1;
|
||||
$$.step = 1;
|
||||
}
|
||||
| const COMMA const {
|
||||
| iconst COMMA iconst {
|
||||
$$.start = $1;
|
||||
$$.stop = $3;
|
||||
$$.step = 1;
|
||||
}
|
||||
| const COMMA const COMMA const {
|
||||
| iconst COMMA iconst COMMA iconst {
|
||||
$$.start = $1;
|
||||
$$.stop = $3;
|
||||
$$.step = $5;
|
||||
@@ -1025,33 +1009,33 @@ dl:
|
||||
;
|
||||
|
||||
def_equ:
|
||||
def_id POP_EQU const {
|
||||
def_id POP_EQU iconst {
|
||||
$$ = std::move($1);
|
||||
sym_AddEqu($$, $3);
|
||||
}
|
||||
;
|
||||
|
||||
redef_equ:
|
||||
redef_id POP_EQU const {
|
||||
redef_id POP_EQU iconst {
|
||||
$$ = std::move($1);
|
||||
sym_RedefEqu($$, $3);
|
||||
}
|
||||
;
|
||||
|
||||
def_set:
|
||||
def_id POP_EQUAL const {
|
||||
def_id POP_EQUAL iconst {
|
||||
$$ = std::move($1);
|
||||
sym_AddVar($$, $3);
|
||||
}
|
||||
| redef_id POP_EQUAL const {
|
||||
| redef_id POP_EQUAL iconst {
|
||||
$$ = std::move($1);
|
||||
sym_AddVar($$, $3);
|
||||
}
|
||||
| def_id compound_eq const {
|
||||
| def_id compound_eq iconst {
|
||||
$$ = std::move($1);
|
||||
compoundAssignment($$, $2, $3);
|
||||
}
|
||||
| redef_id compound_eq const {
|
||||
| redef_id compound_eq iconst {
|
||||
$$ = std::move($1);
|
||||
compoundAssignment($$, $2, $3);
|
||||
}
|
||||
@@ -1151,12 +1135,12 @@ incbin:
|
||||
if (failedOnMissingInclude)
|
||||
YYACCEPT;
|
||||
}
|
||||
| POP_INCBIN string COMMA const {
|
||||
| POP_INCBIN string COMMA iconst {
|
||||
sect_BinaryFile($2, $4);
|
||||
if (failedOnMissingInclude)
|
||||
YYACCEPT;
|
||||
}
|
||||
| POP_INCBIN string COMMA const COMMA const {
|
||||
| POP_INCBIN string COMMA iconst COMMA iconst {
|
||||
sect_BinaryFileSlice($2, $4, $6);
|
||||
if (failedOnMissingInclude)
|
||||
YYACCEPT;
|
||||
@@ -1170,10 +1154,10 @@ charmap:
|
||||
;
|
||||
|
||||
charmap_args:
|
||||
const {
|
||||
iconst {
|
||||
$$.push_back(std::move($1));
|
||||
}
|
||||
| charmap_args COMMA const {
|
||||
| charmap_args COMMA iconst {
|
||||
$$ = std::move($1);
|
||||
$$.push_back(std::move($3));
|
||||
}
|
||||
@@ -1242,7 +1226,7 @@ print_expr:
|
||||
;
|
||||
|
||||
bit_const:
|
||||
const {
|
||||
iconst {
|
||||
$$ = $1;
|
||||
if ($$ < 0 || $$ > 7) {
|
||||
::error("Bit number must be between 0 and 7, not %" PRId32 "\n", $$);
|
||||
@@ -1464,49 +1448,49 @@ relocexpr_no_str:
|
||||
$$.makeNumber(sym_FindScopedValidSymbol($4) != nullptr);
|
||||
lexer_ToggleStringExpansion(true);
|
||||
}
|
||||
| OP_ROUND LPAREN const precision_arg RPAREN {
|
||||
| OP_ROUND LPAREN iconst precision_arg RPAREN {
|
||||
$$.makeNumber(fix_Round($3, $4));
|
||||
}
|
||||
| OP_CEIL LPAREN const precision_arg RPAREN {
|
||||
| OP_CEIL LPAREN iconst precision_arg RPAREN {
|
||||
$$.makeNumber(fix_Ceil($3, $4));
|
||||
}
|
||||
| OP_FLOOR LPAREN const precision_arg RPAREN {
|
||||
| OP_FLOOR LPAREN iconst precision_arg RPAREN {
|
||||
$$.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));
|
||||
}
|
||||
| OP_FMUL LPAREN const COMMA const precision_arg RPAREN {
|
||||
| OP_FMUL LPAREN iconst COMMA iconst precision_arg RPAREN {
|
||||
$$.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));
|
||||
}
|
||||
| OP_POW LPAREN const COMMA const precision_arg RPAREN {
|
||||
| OP_POW LPAREN iconst COMMA iconst precision_arg RPAREN {
|
||||
$$.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));
|
||||
}
|
||||
| OP_SIN LPAREN const precision_arg RPAREN {
|
||||
| OP_SIN LPAREN iconst precision_arg RPAREN {
|
||||
$$.makeNumber(fix_Sin($3, $4));
|
||||
}
|
||||
| OP_COS LPAREN const precision_arg RPAREN {
|
||||
| OP_COS LPAREN iconst precision_arg RPAREN {
|
||||
$$.makeNumber(fix_Cos($3, $4));
|
||||
}
|
||||
| OP_TAN LPAREN const precision_arg RPAREN {
|
||||
| OP_TAN LPAREN iconst precision_arg RPAREN {
|
||||
$$.makeNumber(fix_Tan($3, $4));
|
||||
}
|
||||
| OP_ASIN LPAREN const precision_arg RPAREN {
|
||||
| OP_ASIN LPAREN iconst precision_arg RPAREN {
|
||||
$$.makeNumber(fix_ASin($3, $4));
|
||||
}
|
||||
| OP_ACOS LPAREN const precision_arg RPAREN {
|
||||
| OP_ACOS LPAREN iconst precision_arg RPAREN {
|
||||
$$.makeNumber(fix_ACos($3, $4));
|
||||
}
|
||||
| OP_ATAN LPAREN const precision_arg RPAREN {
|
||||
| OP_ATAN LPAREN iconst precision_arg RPAREN {
|
||||
$$.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));
|
||||
}
|
||||
| OP_STRCMP LPAREN string COMMA string RPAREN {
|
||||
@@ -1537,14 +1521,14 @@ relocexpr_no_str:
|
||||
;
|
||||
|
||||
uconst:
|
||||
const {
|
||||
iconst {
|
||||
$$ = $1;
|
||||
if ($$ < 0)
|
||||
fatalerror("Constant must not be negative: %d\n", $$);
|
||||
}
|
||||
;
|
||||
|
||||
const:
|
||||
iconst:
|
||||
relocexpr {
|
||||
$$ = $1.getConstVal();
|
||||
}
|
||||
@@ -1560,7 +1544,7 @@ precision_arg:
|
||||
%empty {
|
||||
$$ = fix_Precision();
|
||||
}
|
||||
| COMMA const {
|
||||
| COMMA iconst {
|
||||
$$ = $2;
|
||||
if ($$ < 1 || $$ > 31) {
|
||||
::error("Fixed-point precision must be between 1 and 31, not %" PRId32 "\n", $$);
|
||||
@@ -1573,19 +1557,19 @@ string:
|
||||
STRING {
|
||||
$$ = 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);
|
||||
uint32_t pos = adjustNegativePos($5, len, "STRSUB");
|
||||
|
||||
$$ = strsubUTF8($3, pos, $7);
|
||||
}
|
||||
| OP_STRSUB LPAREN string COMMA const RPAREN {
|
||||
| OP_STRSUB LPAREN string COMMA iconst RPAREN {
|
||||
size_t len = strlenUTF8($3);
|
||||
uint32_t pos = adjustNegativePos($5, len, "STRSUB");
|
||||
|
||||
$$ = 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);
|
||||
uint32_t pos = adjustNegativePos($5, len, "CHARSUB");
|
||||
|
||||
@@ -1652,7 +1636,7 @@ strfmt_va_args:
|
||||
%empty {}
|
||||
| strfmt_va_args COMMA const_no_str {
|
||||
$$ = std::move($1);
|
||||
$$.args.push_back((uint32_t)$3);
|
||||
$$.args.push_back(static_cast<uint32_t>($3));
|
||||
}
|
||||
| strfmt_va_args COMMA string {
|
||||
$$ = std::move($1);
|
||||
@@ -1718,7 +1702,7 @@ sect_org:
|
||||
}
|
||||
| LBRACK uconst RBRACK {
|
||||
$$ = $2;
|
||||
if ($$ < 0 || $$ >= 0x10000) {
|
||||
if ($$ < 0 || $$ > 0xFFFF) {
|
||||
::error("Address $%x is not 16-bit\n", $$);
|
||||
$$ = -1;
|
||||
}
|
||||
@@ -1768,8 +1752,8 @@ cpu_command:
|
||||
| z80_jr
|
||||
| z80_ld
|
||||
| z80_ldd
|
||||
| z80_ldh
|
||||
| z80_ldi
|
||||
| z80_ldio
|
||||
| z80_nop
|
||||
| z80_or
|
||||
| z80_pop
|
||||
@@ -1963,15 +1947,25 @@ z80_ldd:
|
||||
}
|
||||
;
|
||||
|
||||
z80_ldio:
|
||||
z80_ldh:
|
||||
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_RelByte($4, 1);
|
||||
}
|
||||
| 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_RelByte($2, 1);
|
||||
@@ -1987,7 +1981,7 @@ z80_ldio:
|
||||
c_ind:
|
||||
LBRACK 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)
|
||||
::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 COMMA MODE_A {
|
||||
warning(WARNING_OBSOLETE, "LD [C], A is deprecated; use LDH [C], A\n");
|
||||
sect_ConstByte(0xE2);
|
||||
}
|
||||
;
|
||||
@@ -2070,6 +2065,7 @@ z80_ld_a:
|
||||
sect_ConstByte(0x40 | ($2 << 3) | $4);
|
||||
}
|
||||
| Z80_LD reg_a COMMA c_ind {
|
||||
warning(WARNING_OBSOLETE, "LD A, [C] is deprecated; use LDH A, [C]\n");
|
||||
sect_ConstByte(0xF2);
|
||||
}
|
||||
| Z80_LD reg_a COMMA reg_rr {
|
||||
@@ -2472,7 +2468,7 @@ static uint32_t strToNum(std::vector<int32_t> const &s) {
|
||||
if (length == 1) {
|
||||
// The string is a single character with a single value,
|
||||
// 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");
|
||||
@@ -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);
|
||||
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) {
|
||||
|
||||
@@ -48,7 +48,7 @@ int32_t Expression::getConstVal() const {
|
||||
Symbol const *Expression::symbolOf() const {
|
||||
if (!isSymbol)
|
||||
return nullptr;
|
||||
return sym_FindScopedSymbol((char const *)&rpn[1]);
|
||||
return sym_FindScopedSymbol(reinterpret_cast<char const *>(&rpn[1]));
|
||||
}
|
||||
|
||||
bool Expression::isDiffConstant(Symbol const *sym) const {
|
||||
@@ -65,7 +65,7 @@ bool Expression::isDiffConstant(Symbol const *sym) const {
|
||||
|
||||
void Expression::makeNumber(uint32_t value) {
|
||||
clear();
|
||||
data = (int32_t)value;
|
||||
data = static_cast<int32_t>(value);
|
||||
}
|
||||
|
||||
void Expression::makeSymbol(std::string const &symName) {
|
||||
@@ -89,7 +89,7 @@ void Expression::makeSymbol(std::string const &symName) {
|
||||
*ptr++ = RPN_SYM;
|
||||
memcpy(ptr, sym->name.c_str(), nameLen);
|
||||
} else {
|
||||
data = (int32_t)sym_GetConstantValue(symName);
|
||||
data = static_cast<int32_t>(sym_GetConstantValue(symName));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,12 +100,12 @@ void Expression::makeBankSymbol(std::string const &symName) {
|
||||
if (!currentSection) {
|
||||
error("PC has no bank outside of a section\n");
|
||||
data = 1;
|
||||
} else if (currentSection->bank == (uint32_t)-1) {
|
||||
} else if (currentSection->bank == UINT32_MAX) {
|
||||
data = "Current section's bank is not known";
|
||||
|
||||
*reserveSpace(1) = RPN_BANK_SELF;
|
||||
} else {
|
||||
data = (int32_t)currentSection->bank;
|
||||
data = static_cast<int32_t>(currentSection->bank);
|
||||
}
|
||||
return;
|
||||
} else if (sym && !sym->isLabel()) {
|
||||
@@ -115,13 +115,13 @@ void Expression::makeBankSymbol(std::string const &symName) {
|
||||
sym = sym_Ref(symName);
|
||||
assume(sym); // If the symbol didn't exist, it should have been created
|
||||
|
||||
if (sym->getSection() && sym->getSection()->bank != (uint32_t)-1) {
|
||||
if (sym->getSection() && sym->getSection()->bank != UINT32_MAX) {
|
||||
// Symbol's section is known and bank is fixed
|
||||
data = (int32_t)sym->getSection()->bank;
|
||||
data = static_cast<int32_t>(sym->getSection()->bank);
|
||||
} else {
|
||||
data = sym_IsPurgedScoped(symName)
|
||||
? "\""s + symName + "\"'s bank is not known; it was purged"
|
||||
: "\""s + symName + "\"'s bank is not known";
|
||||
? "\""s + symName + "\"'s bank is not known; it was purged"
|
||||
: "\""s + symName + "\"'s bank is not known";
|
||||
|
||||
size_t nameLen = sym->name.length() + 1; // Room for NUL!
|
||||
|
||||
@@ -135,8 +135,8 @@ void Expression::makeBankSymbol(std::string const &symName) {
|
||||
|
||||
void Expression::makeBankSection(std::string const §Name) {
|
||||
clear();
|
||||
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->bank != (uint32_t)-1) {
|
||||
data = (int32_t)sect->bank;
|
||||
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->bank != UINT32_MAX) {
|
||||
data = static_cast<int32_t>(sect->bank);
|
||||
} else {
|
||||
data = "Section \""s + sectName + "\"'s bank is not known";
|
||||
|
||||
@@ -151,7 +151,7 @@ void Expression::makeBankSection(std::string const §Name) {
|
||||
void Expression::makeSizeOfSection(std::string const §Name) {
|
||||
clear();
|
||||
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->isSizeKnown()) {
|
||||
data = (int32_t)sect->size;
|
||||
data = static_cast<int32_t>(sect->size);
|
||||
} else {
|
||||
data = "Section \""s + sectName + "\"'s size is not known";
|
||||
|
||||
@@ -165,8 +165,8 @@ void Expression::makeSizeOfSection(std::string const §Name) {
|
||||
|
||||
void Expression::makeStartOfSection(std::string const §Name) {
|
||||
clear();
|
||||
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->org != (uint32_t)-1) {
|
||||
data = (int32_t)sect->org;
|
||||
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->org != UINT32_MAX) {
|
||||
data = static_cast<int32_t>(sect->org);
|
||||
} else {
|
||||
data = "Section \""s + sectName + "\"'s start is not known";
|
||||
|
||||
@@ -216,9 +216,9 @@ static bool tryConstLogNot(Expression const &expr) {
|
||||
Section const § = *sym->getSection();
|
||||
int32_t unknownBits = (1 << 16) - (1 << sect.align);
|
||||
|
||||
// `sym->getValue()` attempts to add the section's address, but that's "-1"
|
||||
// `sym->getValue()` attempts to add the section's address, but that's `UINT32_MAX`
|
||||
// because the section is floating (otherwise we wouldn't be here)
|
||||
assume(sect.org == (uint32_t)-1);
|
||||
assume(sect.org == UINT32_MAX);
|
||||
int32_t symbolOfs = sym->getValue() + 1;
|
||||
|
||||
int32_t knownBits = (symbolOfs + sect.alignOfs) & ~unknownBits;
|
||||
@@ -243,9 +243,9 @@ static int32_t tryConstLow(Expression const &expr) {
|
||||
if (sect.align < 8)
|
||||
return -1;
|
||||
|
||||
// `sym->getValue()` attempts to add the section's address, but that's "-1"
|
||||
// `sym->getValue()` attempts to add the section's address, but that's `UINT32_MAX`
|
||||
// because the section is floating (otherwise we wouldn't be here)
|
||||
assume(sect.org == (uint32_t)-1);
|
||||
assume(sect.org == UINT32_MAX);
|
||||
int32_t symbolOfs = sym->getValue() + 1;
|
||||
|
||||
return (symbolOfs + sect.alignOfs) & 0xFF;
|
||||
@@ -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)
|
||||
return -1;
|
||||
|
||||
// `sym.getValue()` attempts to add the section's address, but that's "-1"
|
||||
// `sym.getValue()` attempts to add the section's address, but that's `UINT32_MAX`
|
||||
// because the section is floating (otherwise we wouldn't be here)
|
||||
assume(sect.org == (uint32_t)-1);
|
||||
assume(sect.org == UINT32_MAX);
|
||||
int32_t symbolOfs = sym.getValue() + 1;
|
||||
|
||||
return (symbolOfs + sect.alignOfs) & mask;
|
||||
@@ -298,10 +298,11 @@ void Expression::makeUnaryOp(RPNCommand op, Expression &&src) {
|
||||
if (src.isKnown()) {
|
||||
// If the expressions is known, just compute the value
|
||||
int32_t val = src.value();
|
||||
uint32_t uval = static_cast<uint32_t>(val);
|
||||
|
||||
switch (op) {
|
||||
case RPN_NEG:
|
||||
data = (int32_t) - (uint32_t)val;
|
||||
data = static_cast<int32_t>(-uval);
|
||||
break;
|
||||
case RPN_NOT:
|
||||
data = ~val;
|
||||
@@ -310,16 +311,16 @@ void Expression::makeUnaryOp(RPNCommand op, Expression &&src) {
|
||||
data = !val;
|
||||
break;
|
||||
case RPN_HIGH:
|
||||
data = (int32_t)((uint32_t)val >> 8 & 0xFF);
|
||||
data = static_cast<int32_t>(uval >> 8 & 0xFF);
|
||||
break;
|
||||
case RPN_LOW:
|
||||
data = val & 0xFF;
|
||||
break;
|
||||
case RPN_BITWIDTH:
|
||||
data = val != 0 ? 32 - clz((uint32_t)val) : 0;
|
||||
data = val != 0 ? 32 - clz(uval) : 0;
|
||||
break;
|
||||
case RPN_TZCOUNT:
|
||||
data = val != 0 ? ctz((uint32_t)val) : 32;
|
||||
data = val != 0 ? ctz(uval) : 32;
|
||||
break;
|
||||
|
||||
case RPN_LOGOR:
|
||||
@@ -374,6 +375,7 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
if (src1.isKnown() && src2.isKnown()) {
|
||||
// If both expressions are known, just compute the value
|
||||
int32_t lval = src1.value(), rval = src2.value();
|
||||
uint32_t ulval = static_cast<uint32_t>(lval), urval = static_cast<uint32_t>(rval);
|
||||
|
||||
switch (op) {
|
||||
case RPN_LOGOR:
|
||||
@@ -401,10 +403,10 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
data = lval != rval;
|
||||
break;
|
||||
case RPN_ADD:
|
||||
data = (int32_t)((uint32_t)lval + (uint32_t)rval);
|
||||
data = static_cast<int32_t>(ulval + urval);
|
||||
break;
|
||||
case RPN_SUB:
|
||||
data = (int32_t)((uint32_t)lval - (uint32_t)rval);
|
||||
data = static_cast<int32_t>(ulval - urval);
|
||||
break;
|
||||
case RPN_XOR:
|
||||
data = lval ^ rval;
|
||||
@@ -452,7 +454,7 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
data = op_shift_right_unsigned(lval, rval);
|
||||
break;
|
||||
case RPN_MUL:
|
||||
data = (int32_t)((uint32_t)lval * (uint32_t)rval);
|
||||
data = static_cast<int32_t>(ulval * urval);
|
||||
break;
|
||||
case RPN_DIV:
|
||||
if (rval == 0)
|
||||
@@ -522,10 +524,10 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
uint32_t lval = src1.value();
|
||||
uint8_t bytes[] = {
|
||||
RPN_CONST,
|
||||
(uint8_t)lval,
|
||||
(uint8_t)(lval >> 8),
|
||||
(uint8_t)(lval >> 16),
|
||||
(uint8_t)(lval >> 24),
|
||||
static_cast<uint8_t>(lval),
|
||||
static_cast<uint8_t>(lval >> 8),
|
||||
static_cast<uint8_t>(lval >> 16),
|
||||
static_cast<uint8_t>(lval >> 24),
|
||||
};
|
||||
rpn.clear();
|
||||
rpnPatchSize = 0;
|
||||
@@ -546,10 +548,10 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
uint32_t rval = src2.value();
|
||||
uint8_t bytes[] = {
|
||||
RPN_CONST,
|
||||
(uint8_t)rval,
|
||||
(uint8_t)(rval >> 8),
|
||||
(uint8_t)(rval >> 16),
|
||||
(uint8_t)(rval >> 24),
|
||||
static_cast<uint8_t>(rval),
|
||||
static_cast<uint8_t>(rval >> 8),
|
||||
static_cast<uint8_t>(rval >> 16),
|
||||
static_cast<uint8_t>(rval >> 24),
|
||||
};
|
||||
uint8_t *ptr = reserveSpace(sizeof(bytes) + 1, sizeof(bytes) + 1);
|
||||
memcpy(ptr, bytes, sizeof(bytes));
|
||||
@@ -566,16 +568,20 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
}
|
||||
}
|
||||
|
||||
void Expression::makeCheckHRAM() {
|
||||
bool Expression::makeCheckHRAM() {
|
||||
isSymbol = false;
|
||||
if (!isKnown()) {
|
||||
*reserveSpace(1) = RPN_HRAM;
|
||||
} else if (int32_t val = value(); val >= 0xFF00 && val <= 0xFFFF) {
|
||||
// That range is valid, but only keep the lower byte
|
||||
data = val & 0xFF;
|
||||
} else if (val < 0 || val > 0xFF) {
|
||||
} else if (val >= 0 && val <= 0xFF) {
|
||||
// That range is valid, but deprecated
|
||||
return true;
|
||||
} else {
|
||||
error("Source address $%" PRIx32 " not between $FF00 to $FFFF\n", val);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Expression::makeCheckRST() {
|
||||
@@ -584,9 +590,6 @@ void Expression::makeCheckRST() {
|
||||
} else if (int32_t val = value(); val & ~0x38) {
|
||||
// A valid RST address must be masked with 0x38
|
||||
error("Invalid address $%" PRIx32 " for RST\n", val);
|
||||
} else {
|
||||
// The target is in the "0x38" bits, all other bits are set
|
||||
data = val | 0xC7;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -109,9 +109,9 @@ static unsigned int mergeSectUnion(
|
||||
if (sect_HasData(type))
|
||||
sectError("Cannot declare ROM sections as UNION\n");
|
||||
|
||||
if (org != (uint32_t)-1) {
|
||||
if (org != UINT32_MAX) {
|
||||
// If both are fixed, they must be the same
|
||||
if (sect.org != (uint32_t)-1 && sect.org != org)
|
||||
if (sect.org != UINT32_MAX && sect.org != org)
|
||||
sectError(
|
||||
"Section already declared as fixed at different address $%04" PRIx32 "\n", sect.org
|
||||
);
|
||||
@@ -127,7 +127,7 @@ static unsigned int mergeSectUnion(
|
||||
|
||||
} else if (alignment != 0) {
|
||||
// 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))
|
||||
sectError(
|
||||
"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
|
||||
// combination of both.
|
||||
// The merging is however performed at the *end* of the original section!
|
||||
if (org != (uint32_t)-1) {
|
||||
if (org != UINT32_MAX) {
|
||||
uint16_t curOrg = org - sect.size;
|
||||
|
||||
// If both are fixed, they must be the same
|
||||
if (sect.org != (uint32_t)-1 && sect.org != curOrg)
|
||||
if (sect.org != UINT32_MAX && sect.org != curOrg)
|
||||
sectError(
|
||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
||||
sect.org
|
||||
@@ -185,7 +185,7 @@ static unsigned int
|
||||
curOfs += 1U << alignment;
|
||||
|
||||
// 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))
|
||||
sectError(
|
||||
"Section already declared as fixed at incompatible address $%04" PRIx32 "\n",
|
||||
@@ -238,10 +238,10 @@ static void mergeSections(
|
||||
// Common checks
|
||||
|
||||
// If the section's bank is unspecified, override it
|
||||
if (sect.bank == (uint32_t)-1)
|
||||
if (sect.bank == UINT32_MAX)
|
||||
sect.bank = bank;
|
||||
// If both specify a bank, it must be the same one
|
||||
else if (bank != (uint32_t)-1 && sect.bank != bank)
|
||||
else if (bank != UINT32_MAX && sect.bank != bank)
|
||||
sectError("Section already declared with different bank %" PRIu32 "\n", sect.bank);
|
||||
break;
|
||||
|
||||
@@ -312,7 +312,7 @@ static Section *getSection(
|
||||
|
||||
// First, validate parameters, and normalize them if applicable
|
||||
|
||||
if (bank != (uint32_t)-1) {
|
||||
if (bank != UINT32_MAX) {
|
||||
if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM && type != SECTTYPE_SRAM
|
||||
&& type != SECTTYPE_WRAMX)
|
||||
error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n");
|
||||
@@ -338,7 +338,7 @@ static Section *getSection(
|
||||
alignOffset = 0;
|
||||
}
|
||||
|
||||
if (org != (uint32_t)-1) {
|
||||
if (org != UINT32_MAX) {
|
||||
if (org < sectionTypeInfo[type].startAddr || org > endaddr(type))
|
||||
error(
|
||||
"Section \"%s\"'s fixed address $%04" PRIx32 " is outside of range [$%04" PRIx16
|
||||
@@ -358,7 +358,7 @@ static Section *getSection(
|
||||
// It doesn't make sense to have both alignment and org set
|
||||
uint32_t mask = mask(alignment);
|
||||
|
||||
if (org != (uint32_t)-1) {
|
||||
if (org != UINT32_MAX) {
|
||||
if ((org - alignOffset) & mask)
|
||||
error("Section \"%s\"'s fixed address doesn't match its alignment\n", name.c_str());
|
||||
alignment = 0; // Ignore it if it's satisfied
|
||||
@@ -425,14 +425,14 @@ void sect_NewSection(
|
||||
SectionSpec const &attrs,
|
||||
SectionModifier mod
|
||||
) {
|
||||
if (currentLoadSection)
|
||||
fatalerror("Cannot change the section within a `LOAD` block\n");
|
||||
|
||||
for (SectionStackEntry &entry : sectionStack) {
|
||||
if (entry.section && entry.section->name == name)
|
||||
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);
|
||||
|
||||
changeSection();
|
||||
@@ -457,20 +457,13 @@ void sect_SetLoadSection(
|
||||
if (!requireCodeSection())
|
||||
return;
|
||||
|
||||
if (currentLoadSection) {
|
||||
error("`LOAD` blocks cannot be nested\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (sect_HasData(type)) {
|
||||
error("`LOAD` blocks cannot create a ROM section\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mod == SECTION_FRAGMENT) {
|
||||
error("`LOAD FRAGMENT` is not allowed\n");
|
||||
return;
|
||||
}
|
||||
if (currentLoadSection)
|
||||
sect_EndLoadSection("LOAD");
|
||||
|
||||
Section *sect = getSection(name, type, org, attrs, mod);
|
||||
|
||||
@@ -481,7 +474,14 @@ void sect_SetLoadSection(
|
||||
currentLoadSection = sect;
|
||||
}
|
||||
|
||||
void sect_EndLoadSection() {
|
||||
void sect_EndLoadSection(char const *cause) {
|
||||
if (cause)
|
||||
warning(
|
||||
WARNING_UNTERMINATED_LOAD,
|
||||
"`LOAD` block without `ENDL` terminated by `%s`\n",
|
||||
cause
|
||||
);
|
||||
|
||||
if (!currentLoadSection) {
|
||||
error("Found `ENDL` outside of a `LOAD` block\n");
|
||||
return;
|
||||
@@ -494,6 +494,11 @@ void sect_EndLoadSection() {
|
||||
sym_SetCurrentLabelScopes(currentLoadLabelScopes);
|
||||
}
|
||||
|
||||
void sect_CheckLoadClosed() {
|
||||
if (currentLoadSection)
|
||||
warning(WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by EOF\n");
|
||||
}
|
||||
|
||||
Section *sect_GetSymbolSection() {
|
||||
return currentLoadSection ? currentLoadSection : currentSection;
|
||||
}
|
||||
@@ -513,7 +518,7 @@ uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) {
|
||||
if (!sect)
|
||||
return 0;
|
||||
|
||||
bool isFixed = sect->org != (uint32_t)-1;
|
||||
bool isFixed = sect->org != UINT32_MAX;
|
||||
|
||||
// If the section is not aligned, no bytes are needed
|
||||
// (fixed sections count as being maximally aligned for this purpose)
|
||||
@@ -534,7 +539,7 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
||||
Section *sect = sect_GetSymbolSection();
|
||||
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)
|
||||
error(
|
||||
"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");
|
||||
|
||||
if (currentLoadSection)
|
||||
fatalerror("Cannot change the section within a `LOAD` block\n");
|
||||
sect_EndLoadSection("POPS");
|
||||
|
||||
SectionStackEntry entry = sectionStack.front();
|
||||
sectionStack.pop_front();
|
||||
@@ -968,16 +973,22 @@ void sect_PopSection() {
|
||||
std::swap(currentUnionStack, entry.unionStack);
|
||||
}
|
||||
|
||||
void sect_CheckStack() {
|
||||
if (!sectionStack.empty()) {
|
||||
warning(WARNING_UNMATCHED_DIRECTIVE, "`PUSHS` without corresponding `POPS`\n");
|
||||
}
|
||||
}
|
||||
|
||||
void sect_EndSection() {
|
||||
if (!currentSection)
|
||||
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())
|
||||
fatalerror("Cannot end the section within a UNION\n");
|
||||
|
||||
if (currentLoadSection)
|
||||
sect_EndLoadSection("ENDSECTION");
|
||||
|
||||
// Reset the section scope
|
||||
currentSection = nullptr;
|
||||
sym_ResetCurrentLabelScopes();
|
||||
|
||||
@@ -23,7 +23,7 @@ std::unordered_map<std::string, Symbol> symbols;
|
||||
std::unordered_set<std::string> purgedSymbols;
|
||||
|
||||
static Symbol const *globalScope = nullptr; // Current section's global label scope
|
||||
static Symbol const *localScope = nullptr; // Current section's local label scope
|
||||
static Symbol const *localScope = nullptr; // Current section's local label scope
|
||||
static Symbol *PCSymbol;
|
||||
static Symbol *NARGSymbol;
|
||||
static Symbol *globalScopeSymbol;
|
||||
@@ -123,7 +123,7 @@ static void updateSymbolFilename(Symbol &sym) {
|
||||
sym.fileLine = sym.src ? lexer_GetLineNo() : 0;
|
||||
|
||||
// If the old node was registered, ensure the new one is too
|
||||
if (oldSrc && oldSrc->ID != (uint32_t)-1)
|
||||
if (oldSrc && oldSrc->ID != UINT32_MAX)
|
||||
out_RegisterNode(sym.src);
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ static Symbol &createSymbol(std::string const &symName) {
|
||||
sym.section = nullptr;
|
||||
sym.src = fstk_GetFileStack();
|
||||
sym.fileLine = sym.src ? lexer_GetLineNo() : 0;
|
||||
sym.ID = -1;
|
||||
sym.ID = UINT32_MAX;
|
||||
sym.defIndex = nextDefIndex++;
|
||||
|
||||
return sym;
|
||||
@@ -258,7 +258,7 @@ void sym_Purge(std::string const &symName) {
|
||||
error("'%s' not defined\n", symName.c_str());
|
||||
} else if (sym->isBuiltin) {
|
||||
error("Built-in symbol '%s' cannot be purged\n", symName.c_str());
|
||||
} else if (sym->ID != (uint32_t)-1) {
|
||||
} else if (sym->ID != UINT32_MAX) {
|
||||
error("Symbol \"%s\" is referenced and thus cannot be purged\n", symName.c_str());
|
||||
} else {
|
||||
if (sym->isExported)
|
||||
@@ -460,7 +460,7 @@ static Symbol *addLabel(std::string const &symName) {
|
||||
}
|
||||
// If the symbol already exists as a ref, just "take over" it
|
||||
sym->type = SYM_LABEL;
|
||||
sym->data = (int32_t)sect_GetSymbolOffset();
|
||||
sym->data = static_cast<int32_t>(sect_GetSymbolOffset());
|
||||
// Don't export anonymous labels
|
||||
if (exportAll && !symName.starts_with('!'))
|
||||
sym->isExported = true;
|
||||
@@ -627,7 +627,7 @@ void sym_Init(time_t now) {
|
||||
sym_AddEqu("__RGBDS_RC__"s, PACKAGE_VERSION_RC)->isBuiltin = true;
|
||||
#endif
|
||||
|
||||
if (now == (time_t)-1) {
|
||||
if (now == static_cast<time_t>(-1)) {
|
||||
warn("Failed to determine current time");
|
||||
// Fall back by pretending we are at the Epoch
|
||||
now = 0;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "error.hpp"
|
||||
#include "helpers.hpp" // QUOTEDSTRLEN
|
||||
#include "helpers.hpp"
|
||||
#include "itertools.hpp"
|
||||
|
||||
#include "asm/fstack.hpp"
|
||||
@@ -20,265 +20,165 @@
|
||||
unsigned int nbErrors = 0;
|
||||
unsigned int maxErrors = 0;
|
||||
|
||||
static WarningState const defaultWarnings[ARRAY_SIZE(warningStates)] = {
|
||||
WARNING_ENABLED, // WARNING_ASSERT
|
||||
WARNING_DISABLED, // WARNING_BACKWARDS_FOR
|
||||
WARNING_DISABLED, // WARNING_BUILTIN_ARG
|
||||
WARNING_DISABLED, // WARNING_CHARMAP_REDEF
|
||||
WARNING_DISABLED, // WARNING_DIV
|
||||
WARNING_DISABLED, // WARNING_EMPTY_DATA_DIRECTIVE
|
||||
WARNING_DISABLED, // WARNING_EMPTY_MACRO_ARG
|
||||
WARNING_DISABLED, // WARNING_EMPTY_STRRPL
|
||||
WARNING_DISABLED, // WARNING_LARGE_CONSTANT
|
||||
WARNING_DISABLED, // WARNING_MACRO_SHIFT
|
||||
WARNING_ENABLED, // WARNING_NESTED_COMMENT
|
||||
WARNING_ENABLED, // WARNING_OBSOLETE
|
||||
WARNING_DISABLED, // WARNING_SHIFT
|
||||
WARNING_DISABLED, // WARNING_SHIFT_AMOUNT
|
||||
WARNING_ENABLED, // WARNING_USER
|
||||
Diagnostics warningStates;
|
||||
bool warningsAreErrors;
|
||||
|
||||
WARNING_DISABLED, // WARNING_NUMERIC_STRING_1
|
||||
WARNING_DISABLED, // WARNING_NUMERIC_STRING_2
|
||||
WARNING_ENABLED, // WARNING_TRUNCATION_1
|
||||
WARNING_DISABLED, // WARNING_TRUNCATION_2
|
||||
WARNING_ENABLED, // WARNING_PURGE_1
|
||||
WARNING_DISABLED, // WARNING_PURGE_2
|
||||
WARNING_ENABLED, // WARNING_UNMAPPED_CHAR_1
|
||||
WARNING_DISABLED, // WARNING_UNMAPPED_CHAR_2
|
||||
enum WarningLevel {
|
||||
LEVEL_DEFAULT, // Warnings that are enabled by default
|
||||
LEVEL_ALL, // Warnings that probably indicate an error
|
||||
LEVEL_EXTRA, // Warnings that are less likely to indicate an error
|
||||
LEVEL_EVERYTHING, // Literally every warning
|
||||
};
|
||||
|
||||
WarningState warningStates[ARRAY_SIZE(warningStates)];
|
||||
struct WarningFlag {
|
||||
char const *name;
|
||||
WarningLevel level;
|
||||
};
|
||||
|
||||
bool warningsAreErrors; // Set if `-Werror` was specified
|
||||
|
||||
static WarningState warningState(WarningID id) {
|
||||
// Check if warnings are globally disabled
|
||||
if (!warnings)
|
||||
return WARNING_DISABLED;
|
||||
|
||||
// Get the actual state
|
||||
WarningState state = warningStates[id];
|
||||
|
||||
if (state == WARNING_DEFAULT)
|
||||
// The state isn't set, grab its default state
|
||||
state = defaultWarnings[id];
|
||||
|
||||
if (warningsAreErrors && state == WARNING_ENABLED)
|
||||
state = WARNING_ERROR;
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
static char const * const warningFlags[NB_WARNINGS] = {
|
||||
"assert",
|
||||
"backwards-for",
|
||||
"builtin-args",
|
||||
"charmap-redef",
|
||||
"div",
|
||||
"empty-data-directive",
|
||||
"empty-macro-arg",
|
||||
"empty-strrpl",
|
||||
"large-constant",
|
||||
"macro-shift",
|
||||
"nested-comment",
|
||||
"obsolete",
|
||||
"shift",
|
||||
"shift-amount",
|
||||
"user",
|
||||
static const WarningFlag metaWarnings[] = {
|
||||
{"all", LEVEL_ALL },
|
||||
{"extra", LEVEL_EXTRA },
|
||||
{"everything", LEVEL_EVERYTHING},
|
||||
};
|
||||
|
||||
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
|
||||
"numeric-string",
|
||||
"numeric-string",
|
||||
"purge",
|
||||
"purge",
|
||||
"truncation",
|
||||
"truncation",
|
||||
"unmapped-char",
|
||||
"unmapped-char",
|
||||
|
||||
// Meta warnings
|
||||
"all",
|
||||
"extra",
|
||||
"everything", // Especially useful for testing
|
||||
{"numeric-string", LEVEL_EVERYTHING},
|
||||
{"numeric-string", LEVEL_EVERYTHING},
|
||||
{"purge", LEVEL_DEFAULT },
|
||||
{"purge", LEVEL_ALL },
|
||||
{"truncation", LEVEL_DEFAULT },
|
||||
{"truncation", LEVEL_EXTRA },
|
||||
{"unmapped-char", LEVEL_DEFAULT },
|
||||
{"unmapped-char", LEVEL_ALL },
|
||||
};
|
||||
|
||||
static const struct {
|
||||
char const *name;
|
||||
uint8_t nbLevels;
|
||||
WarningID firstID;
|
||||
WarningID lastID;
|
||||
uint8_t defaultLevel;
|
||||
} paramWarnings[] = {
|
||||
{"numeric-string", 2, 1},
|
||||
{"purge", 2, 1},
|
||||
{"truncation", 2, 2},
|
||||
{"unmapped-char", 2, 1},
|
||||
{WARNING_NUMERIC_STRING_1, WARNING_NUMERIC_STRING_2, 1},
|
||||
{WARNING_PURGE_1, WARNING_PURGE_2, 1},
|
||||
{WARNING_TRUNCATION_1, WARNING_TRUNCATION_2, 2},
|
||||
{WARNING_UNMAPPED_CHAR_1, WARNING_UNMAPPED_CHAR_2, 1},
|
||||
};
|
||||
|
||||
static bool tryProcessParamWarning(char const *flag, uint8_t param, WarningState state) {
|
||||
WarningID baseID = PARAM_WARNINGS_START;
|
||||
enum WarningBehavior { DISABLED, ENABLED, ERROR };
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(paramWarnings); i++) {
|
||||
uint8_t maxParam = paramWarnings[i].nbLevels;
|
||||
static WarningBehavior getWarningBehavior(WarningID id) {
|
||||
// Check if warnings are globally disabled
|
||||
if (!warnings)
|
||||
return WarningBehavior::DISABLED;
|
||||
|
||||
if (!strcmp(flag, paramWarnings[i].name)) { // Match!
|
||||
if (!strcmp(flag, "numeric-string"))
|
||||
warning(WARNING_OBSOLETE, "Warning flag \"numeric-string\" is deprecated\n");
|
||||
// Get the state of this warning flag
|
||||
WarningState const &flagState = warningStates.flagStates[id];
|
||||
WarningState const &metaState = warningStates.metaStates[id];
|
||||
|
||||
// If making the warning an error but param is 0, set to the maximum
|
||||
// This accommodates `-Werror=flag`, but also `-Werror=flag=0`, which is
|
||||
// thus filtered out by the caller.
|
||||
// A param of 0 makes sense for disabling everything, but neither for
|
||||
// enabling nor "erroring". Use the default for those.
|
||||
if (param == 0 && state != WARNING_DISABLED) {
|
||||
param = paramWarnings[i].defaultLevel;
|
||||
} else if (param > maxParam) {
|
||||
if (param != 255) // Don't warn if already capped
|
||||
warnx(
|
||||
"Got parameter %" PRIu8
|
||||
" for warning flag \"%s\", but the maximum is %" PRIu8 "; capping.\n",
|
||||
param,
|
||||
flag,
|
||||
maxParam
|
||||
);
|
||||
param = maxParam;
|
||||
}
|
||||
// If subsequent checks determine that the warning flag is enabled, this checks whether it has
|
||||
// -Werror without -Wno-error=<flag> or -Wno-error=<meta>, which makes it into an error
|
||||
bool warningIsError = warningsAreErrors && flagState.error != WARNING_DISABLED
|
||||
&& metaState.error != WARNING_DISABLED;
|
||||
WarningBehavior enabledBehavior =
|
||||
warningIsError ? WarningBehavior::ERROR : WarningBehavior::ENABLED;
|
||||
|
||||
// Set the first <param> to enabled/error, and disable the rest
|
||||
for (uint8_t ofs = 0; ofs < maxParam; ofs++) {
|
||||
warningStates[baseID + ofs] = ofs < param ? state : WARNING_DISABLED;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// First, check the state of the specific warning flag
|
||||
if (flagState.state == WARNING_DISABLED) // -Wno-<flag>
|
||||
return WarningBehavior::DISABLED;
|
||||
if (flagState.error == WARNING_ENABLED) // -Werror=<flag>
|
||||
return WarningBehavior::ERROR;
|
||||
if (flagState.state == WARNING_ENABLED) // -W<flag>
|
||||
return enabledBehavior;
|
||||
|
||||
baseID = (WarningID)(baseID + maxParam);
|
||||
}
|
||||
return false;
|
||||
// If no flag is specified, check the state of the "meta" flags that affect this warning flag
|
||||
if (metaState.state == WARNING_DISABLED) // -Wno-<meta>
|
||||
return WarningBehavior::DISABLED;
|
||||
if (metaState.error == WARNING_ENABLED) // -Werror=<meta>
|
||||
return WarningBehavior::ERROR;
|
||||
if (metaState.state == WARNING_ENABLED) // -W<meta>
|
||||
return enabledBehavior;
|
||||
|
||||
// If no meta flag is specified, check the default state of this warning flag
|
||||
if (warningFlags[id].level == LEVEL_DEFAULT) // enabled by default
|
||||
return enabledBehavior;
|
||||
|
||||
// No flag enables this warning, explicitly or implicitly
|
||||
return WarningBehavior::DISABLED;
|
||||
}
|
||||
|
||||
enum MetaWarningCommand { META_WARNING_DONE = NB_WARNINGS };
|
||||
|
||||
// Warnings that probably indicate an error
|
||||
static uint8_t const _wallCommands[] = {
|
||||
WARNING_BACKWARDS_FOR,
|
||||
WARNING_BUILTIN_ARG,
|
||||
WARNING_CHARMAP_REDEF,
|
||||
WARNING_EMPTY_DATA_DIRECTIVE,
|
||||
WARNING_EMPTY_STRRPL,
|
||||
WARNING_LARGE_CONSTANT,
|
||||
WARNING_NESTED_COMMENT,
|
||||
WARNING_OBSOLETE,
|
||||
WARNING_PURGE_1,
|
||||
WARNING_PURGE_2,
|
||||
WARNING_UNMAPPED_CHAR_1,
|
||||
META_WARNING_DONE,
|
||||
};
|
||||
|
||||
// Warnings that are less likely to indicate an error
|
||||
static uint8_t const _wextraCommands[] = {
|
||||
WARNING_EMPTY_MACRO_ARG,
|
||||
WARNING_MACRO_SHIFT,
|
||||
WARNING_NESTED_COMMENT,
|
||||
WARNING_OBSOLETE,
|
||||
WARNING_PURGE_1,
|
||||
WARNING_PURGE_2,
|
||||
WARNING_TRUNCATION_1,
|
||||
WARNING_TRUNCATION_2,
|
||||
WARNING_UNMAPPED_CHAR_1,
|
||||
WARNING_UNMAPPED_CHAR_2,
|
||||
META_WARNING_DONE,
|
||||
};
|
||||
|
||||
// Literally everything. Notably useful for testing
|
||||
static uint8_t const _weverythingCommands[] = {
|
||||
WARNING_BACKWARDS_FOR,
|
||||
WARNING_BUILTIN_ARG,
|
||||
WARNING_DIV,
|
||||
WARNING_EMPTY_DATA_DIRECTIVE,
|
||||
WARNING_EMPTY_MACRO_ARG,
|
||||
WARNING_EMPTY_STRRPL,
|
||||
WARNING_LARGE_CONSTANT,
|
||||
WARNING_MACRO_SHIFT,
|
||||
WARNING_NESTED_COMMENT,
|
||||
WARNING_OBSOLETE,
|
||||
WARNING_PURGE_1,
|
||||
WARNING_PURGE_2,
|
||||
WARNING_SHIFT,
|
||||
WARNING_SHIFT_AMOUNT,
|
||||
WARNING_TRUNCATION_1,
|
||||
WARNING_TRUNCATION_2,
|
||||
WARNING_UNMAPPED_CHAR_1,
|
||||
WARNING_UNMAPPED_CHAR_2,
|
||||
// WARNING_USER,
|
||||
META_WARNING_DONE,
|
||||
};
|
||||
|
||||
static uint8_t const *metaWarningCommands[NB_META_WARNINGS] = {
|
||||
_wallCommands,
|
||||
_wextraCommands,
|
||||
_weverythingCommands,
|
||||
};
|
||||
void WarningState::update(WarningState other) {
|
||||
if (other.state != WARNING_DEFAULT)
|
||||
state = other.state;
|
||||
if (other.error != WARNING_DEFAULT)
|
||||
error = other.error;
|
||||
}
|
||||
|
||||
void processWarningFlag(char const *flag) {
|
||||
static bool setError = false;
|
||||
std::string rootFlag = flag;
|
||||
|
||||
// First, try to match against a "meta" warning
|
||||
for (WarningID id : EnumSeq(META_WARNINGS_START, NB_WARNINGS)) {
|
||||
// TODO: improve the matching performance?
|
||||
if (!strcmp(flag, warningFlags[id])) {
|
||||
// We got a match!
|
||||
if (setError)
|
||||
errx("Cannot make meta warning \"%s\" into an error", flag);
|
||||
|
||||
for (uint8_t const *ptr = metaWarningCommands[id - META_WARNINGS_START];
|
||||
*ptr != META_WARNING_DONE;
|
||||
ptr++) {
|
||||
// Warning flag, set without override
|
||||
if (warningStates[*ptr] == WARNING_DEFAULT)
|
||||
warningStates[*ptr] = WARNING_ENABLED;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
// Check for `-Werror` or `-Wno-error` to return early
|
||||
if (rootFlag == "error") {
|
||||
// `-Werror` promotes warnings to errors
|
||||
warningsAreErrors = true;
|
||||
return;
|
||||
} else if (rootFlag == "no-error") {
|
||||
// `-Wno-error` disables promotion of warnings to errors
|
||||
warningsAreErrors = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// If it's not a meta warning, specially check against `-Werror`
|
||||
if (!strncmp(flag, "error", QUOTEDSTRLEN("error"))) {
|
||||
char const *errorFlag = flag + QUOTEDSTRLEN("error");
|
||||
|
||||
switch (*errorFlag) {
|
||||
case '\0':
|
||||
// `-Werror`
|
||||
warningsAreErrors = true;
|
||||
return;
|
||||
|
||||
case '=':
|
||||
// `-Werror=XXX`
|
||||
setError = true;
|
||||
processWarningFlag(errorFlag + 1); // Skip the `=`
|
||||
setError = false;
|
||||
return;
|
||||
|
||||
// Otherwise, allow parsing as another flag
|
||||
}
|
||||
// Check for prefixes that affect what the flag does
|
||||
WarningState state;
|
||||
if (rootFlag.starts_with("error=")) {
|
||||
// `-Werror=<flag>` enables the flag as an error
|
||||
state = {.state = WARNING_ENABLED, .error = WARNING_ENABLED};
|
||||
rootFlag.erase(0, 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};
|
||||
}
|
||||
|
||||
// Well, it's either a normal warning or a mistake
|
||||
|
||||
WarningState state = setError ? WARNING_ERROR
|
||||
// Not an error, then check if this is a negation
|
||||
: strncmp(flag, "no-", QUOTEDSTRLEN("no-")) ? WARNING_ENABLED
|
||||
: WARNING_DISABLED;
|
||||
char const *rootFlag = state == WARNING_DISABLED ? flag + QUOTEDSTRLEN("no-") : flag;
|
||||
|
||||
// Is this a "parametric" warning?
|
||||
if (state != WARNING_DISABLED) { // The `no-` form cannot be parametrized
|
||||
// Check for an `=` parameter to process as a parametric warning
|
||||
// `-Wno-<flag>` and `-Wno-error=<flag>` negation cannot have an `=` parameter, but without a
|
||||
// parameter, the 0 value will apply to all levels of a parametric warning
|
||||
uint8_t param = 0;
|
||||
bool hasParam = false;
|
||||
if (state.state == WARNING_ENABLED) {
|
||||
// First, check if there is an "equals" sign followed by a decimal number
|
||||
char const *equals = strchr(rootFlag, '=');
|
||||
// Ignore an equal sign at the very end of the string
|
||||
if (auto equals = rootFlag.find('=');
|
||||
equals != rootFlag.npos && equals != rootFlag.size() - 1) {
|
||||
hasParam = true;
|
||||
|
||||
if (equals && equals[1] != '\0') { // Ignore an equal sign at the very end as well
|
||||
// Is the rest of the string a decimal number?
|
||||
// We want to avoid `strtoul`'s whitespace and sign, so we parse manually
|
||||
uint8_t param = 0;
|
||||
char const *ptr = equals + 1;
|
||||
char const *ptr = rootFlag.c_str() + equals + 1;
|
||||
bool warned = false;
|
||||
|
||||
// The `if`'s condition above ensures that this will run at least once
|
||||
@@ -289,7 +189,7 @@ void processWarningFlag(char const *flag) {
|
||||
// Avoid overflowing!
|
||||
if (param > UINT8_MAX - (*ptr - '0')) {
|
||||
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
|
||||
param = 255;
|
||||
continue;
|
||||
@@ -299,40 +199,83 @@ void processWarningFlag(char const *flag) {
|
||||
ptr++;
|
||||
} while (*ptr);
|
||||
|
||||
// If we managed to the end of the string, check that the warning indeed
|
||||
// accepts a parameter
|
||||
// If we reached the end of the string, truncate it at the '='
|
||||
if (*ptr == '\0') {
|
||||
if (setError && param == 0) {
|
||||
warnx("Ignoring nonsensical warning flag \"%s\"\n", flag);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string truncFlag = rootFlag;
|
||||
|
||||
truncFlag.resize(equals - rootFlag); // Truncate the param at the '='
|
||||
if (tryProcessParamWarning(
|
||||
truncFlag.c_str(), param, param == 0 ? WARNING_DISABLED : state
|
||||
))
|
||||
return;
|
||||
rootFlag.resize(equals);
|
||||
// `-W<flag>=0` is equivalent to `-Wno-<flag>`
|
||||
if (param == 0)
|
||||
state.state = WARNING_DISABLED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to match the flag against a "normal" flag
|
||||
for (WarningID id : EnumSeq(NB_PLAIN_WARNINGS)) {
|
||||
if (!strcmp(rootFlag, warningFlags[id])) {
|
||||
// We got a match!
|
||||
warningStates[id] = state;
|
||||
// Try to match the flag against a parametric warning
|
||||
// If there was an equals sign, it will have set `param`; if not, `param` will be 0, which
|
||||
// applies to all levels
|
||||
for (auto const ¶mWarning : paramWarnings) {
|
||||
WarningID baseID = paramWarning.firstID;
|
||||
uint8_t maxParam = paramWarning.lastID - baseID + 1;
|
||||
assume(paramWarning.defaultLevel <= maxParam);
|
||||
|
||||
if (rootFlag == warningFlags[baseID].name) { // Match!
|
||||
if (rootFlag == "numeric-string")
|
||||
warning(WARNING_OBSOLETE, "Warning flag \"numeric-string\" is deprecated\n");
|
||||
|
||||
// If making the warning an error but param is 0, set to the maximum
|
||||
// This accommodates `-Werror=<flag>`, but also `-Werror=<flag>=0`, which is
|
||||
// thus filtered out by the caller.
|
||||
// A param of 0 makes sense for disabling everything, but neither for
|
||||
// enabling nor "erroring". Use the default for those.
|
||||
if (param == 0) {
|
||||
param = paramWarning.defaultLevel;
|
||||
} else if (param > maxParam) {
|
||||
if (param != 255) // Don't warn if already capped
|
||||
warnx(
|
||||
"Invalid parameter %" PRIu8
|
||||
" for warning flag \"%s\"; capping at maximum %" PRIu8,
|
||||
param,
|
||||
rootFlag.c_str(),
|
||||
maxParam
|
||||
);
|
||||
param = maxParam;
|
||||
}
|
||||
|
||||
// Set the first <param> to enabled/error, and disable the rest
|
||||
for (uint8_t ofs = 0; ofs < maxParam; ofs++) {
|
||||
WarningState &warning = warningStates.flagStates[baseID + ofs];
|
||||
if (ofs < param)
|
||||
warning.update(state);
|
||||
else
|
||||
warning.state = WARNING_DISABLED;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Lastly, this might be a "parametric" warning without an equals sign
|
||||
// If it is, treat the param as 1 if enabling, or 0 if disabling
|
||||
if (tryProcessParamWarning(rootFlag, 0, state))
|
||||
return;
|
||||
// Try to match against a non-parametric warning, unless there was an equals sign
|
||||
if (!hasParam) {
|
||||
// Try to match against a "meta" warning
|
||||
for (WarningFlag const &metaWarning : metaWarnings) {
|
||||
if (rootFlag == metaWarning.name) {
|
||||
// Set each of the warning flags that meets this level
|
||||
for (WarningID id : EnumSeq(NB_WARNINGS)) {
|
||||
if (metaWarning.level >= warningFlags[id].level)
|
||||
warningStates.metaStates[id].update(state);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
warnx("Unknown warning `%s`", flag);
|
||||
// Try to match the flag against a "normal" flag
|
||||
for (WarningID id : EnumSeq(NB_PLAIN_WARNINGS)) {
|
||||
if (rootFlag == warningFlags[id].name) {
|
||||
warningStates.flagStates[id].update(state);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
warnx("Unknown warning flag \"%s\"", flag);
|
||||
}
|
||||
|
||||
void printDiag(
|
||||
@@ -376,26 +319,22 @@ void error(char const *fmt, ...) {
|
||||
}
|
||||
|
||||
void warning(WarningID id, char const *fmt, ...) {
|
||||
char const *flag = warningFlags[id];
|
||||
char const *flag = warningFlags[id].name;
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
|
||||
switch (warningState(id)) {
|
||||
case WARNING_DISABLED:
|
||||
switch (getWarningBehavior(id)) {
|
||||
case WarningBehavior::DISABLED:
|
||||
break;
|
||||
|
||||
case WARNING_ENABLED:
|
||||
case WarningBehavior::ENABLED:
|
||||
printDiag(fmt, args, "warning", ": [-W%s]", flag);
|
||||
break;
|
||||
|
||||
case WARNING_ERROR:
|
||||
case WarningBehavior::ERROR:
|
||||
printDiag(fmt, args, "error", ": [-Werror=%s]", flag);
|
||||
break;
|
||||
|
||||
case WARNING_DEFAULT:
|
||||
unreachable_();
|
||||
// Not reached
|
||||
}
|
||||
|
||||
va_end(args);
|
||||
|
||||
@@ -227,7 +227,7 @@ static MbcType parseMBC(char const *name) {
|
||||
return MBC_BAD;
|
||||
if (mbc > 0xFF)
|
||||
return MBC_BAD_RANGE;
|
||||
return (MbcType)mbc;
|
||||
return static_cast<MbcType>(mbc);
|
||||
|
||||
} else {
|
||||
// Begin by reading the MBC type:
|
||||
@@ -568,7 +568,7 @@ static MbcType parseMBC(char const *name) {
|
||||
if (*ptr)
|
||||
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(
|
||||
"FATAL: \"%s\" too short, expected at least %jd ($%jx) bytes, got only %jd\n",
|
||||
name,
|
||||
(intmax_t)headerSize,
|
||||
(intmax_t)headerSize,
|
||||
(intmax_t)rom0Len
|
||||
static_cast<intmax_t>(headerSize),
|
||||
static_cast<intmax_t>(headerSize),
|
||||
static_cast<intmax_t>(rom0Len)
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
if (title)
|
||||
overwriteBytes(rom0, 0x134, (uint8_t const *)title, titleLen, "title");
|
||||
overwriteBytes(rom0, 0x134, reinterpret_cast<uint8_t const *>(title), titleLen, "title");
|
||||
|
||||
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)
|
||||
overwriteByte(rom0, 0x143, model == BOTH ? 0x80 : 0xC0, "CGB flag");
|
||||
|
||||
if (newLicensee)
|
||||
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)
|
||||
@@ -1076,7 +1086,10 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
if (fixSpec & TRASH_GLOBAL_SUM)
|
||||
globalSum = ~globalSum;
|
||||
|
||||
uint8_t bytes[2] = {(uint8_t)(globalSum >> 8), (uint8_t)(globalSum & 0xFF)};
|
||||
uint8_t bytes[2] = {
|
||||
static_cast<uint8_t>(globalSum >> 8),
|
||||
static_cast<uint8_t>(globalSum & 0xFF)
|
||||
};
|
||||
|
||||
overwriteBytes(rom0, 0x14E, bytes, sizeof(bytes), "global checksum");
|
||||
}
|
||||
@@ -1086,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
|
||||
// write the header
|
||||
if (input == output) {
|
||||
if (lseek(output, 0, SEEK_SET) == (off_t)-1) {
|
||||
if (lseek(output, 0, SEEK_SET) == static_cast<off_t>(-1)) {
|
||||
report("FATAL: Failed to rewind \"%s\": %s\n", name, strerror(errno));
|
||||
return;
|
||||
}
|
||||
@@ -1103,9 +1116,9 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
} else if (writeLen < rom0Len) {
|
||||
report(
|
||||
"FATAL: Could only write %jd of \"%s\"'s %jd ROM0 bytes\n",
|
||||
(intmax_t)writeLen,
|
||||
static_cast<intmax_t>(writeLen),
|
||||
name,
|
||||
(intmax_t)rom0Len
|
||||
static_cast<intmax_t>(rom0Len)
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -1118,10 +1131,10 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
if (writeLen == -1) {
|
||||
report("FATAL: Failed to write \"%s\"'s ROMX: %s\n", name, strerror(errno));
|
||||
return;
|
||||
} else if ((size_t)writeLen < totalRomxLen) {
|
||||
} else if (static_cast<size_t>(writeLen) < totalRomxLen) {
|
||||
report(
|
||||
"FATAL: Could only write %jd of \"%s\"'s %zu ROMX bytes\n",
|
||||
(intmax_t)writeLen,
|
||||
static_cast<intmax_t>(writeLen),
|
||||
name,
|
||||
totalRomxLen
|
||||
);
|
||||
@@ -1132,7 +1145,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
||||
// Output padding
|
||||
if (padValue != UNSPECIFIED) {
|
||||
if (input == output) {
|
||||
if (lseek(output, 0, SEEK_END) == (off_t)-1) {
|
||||
if (lseek(output, 0, SEEK_END) == static_cast<off_t>(-1)) {
|
||||
report("FATAL: Failed to seek to end of \"%s\": %s\n", name, strerror(errno));
|
||||
return;
|
||||
}
|
||||
@@ -1147,7 +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`,
|
||||
// so it's fine to cast to `size_t`
|
||||
if ((size_t)ret != thisLen) {
|
||||
if (static_cast<size_t>(ret) != thisLen) {
|
||||
report("FATAL: Failed to write \"%s\"'s padding: %s\n", name, strerror(errno));
|
||||
break;
|
||||
}
|
||||
@@ -1160,9 +1173,9 @@ static bool processFilename(char const *name) {
|
||||
nbErrors = 0;
|
||||
|
||||
if (!strcmp(name, "-")) {
|
||||
name = "<stdin>";
|
||||
(void)setmode(STDIN_FILENO, O_BINARY);
|
||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||
name = "<stdin>";
|
||||
processFile(STDIN_FILENO, STDOUT_FILENO, name, 0);
|
||||
} else {
|
||||
// POSIX specifies that the results of O_RDWR on a FIFO are undefined.
|
||||
@@ -1188,7 +1201,7 @@ static bool processFilename(char const *name) {
|
||||
report(
|
||||
"FATAL: \"%s\" too short, expected at least 336 ($150) bytes, got only %jd\n",
|
||||
name,
|
||||
(intmax_t)stat.st_size
|
||||
static_cast<intmax_t>(stat.st_size)
|
||||
);
|
||||
} else {
|
||||
processFile(input, input, name, stat.st_size);
|
||||
@@ -1435,7 +1448,8 @@ int main(int argc, char *argv[]) {
|
||||
logoFile = fopen(logoFilename, "rb");
|
||||
} else {
|
||||
logoFilename = "<stdin>";
|
||||
logoFile = fdopen(STDIN_FILENO, "rb");
|
||||
(void)setmode(STDIN_FILENO, O_BINARY);
|
||||
logoFile = stdin;
|
||||
}
|
||||
if (!logoFile) {
|
||||
fprintf(
|
||||
|
||||
@@ -108,7 +108,7 @@ void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
|
||||
}
|
||||
|
||||
// Short options
|
||||
static char const *optstring = "-Aa:b:Cc:Dd:Ffhi:L:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvx:Z";
|
||||
static char const *optstring = "-Aa:b:Cc:d:i:L:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvXx:YZ";
|
||||
|
||||
/*
|
||||
* Equivalent long options
|
||||
@@ -139,6 +139,7 @@ static option const longopts[] = {
|
||||
{"auto-palette-map", no_argument, nullptr, 'Q'},
|
||||
{"palette-map", required_argument, nullptr, 'q'},
|
||||
{"reverse", required_argument, nullptr, 'r'},
|
||||
{"palette-size", required_argument, nullptr, 's'},
|
||||
{"auto-tilemap", no_argument, nullptr, 'T'},
|
||||
{"tilemap", required_argument, nullptr, 't'},
|
||||
{"unit-size", required_argument, nullptr, 'U'},
|
||||
|
||||
@@ -5,20 +5,19 @@
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
#include <inttypes.h>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <stdint.h>
|
||||
#include <type_traits>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include "gfx/main.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:
|
||||
// "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
|
||||
@@ -114,8 +113,8 @@ private:
|
||||
}
|
||||
|
||||
friend void swap(Iter &lhs, Iter &rhs) {
|
||||
swap(lhs._array, rhs._array);
|
||||
swap(lhs._iter, rhs._iter);
|
||||
std::swap(lhs._array, rhs._array);
|
||||
std::swap(lhs._iter, rhs._iter);
|
||||
}
|
||||
};
|
||||
public:
|
||||
@@ -203,21 +202,44 @@ public:
|
||||
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!
|
||||
double relSize = 0.;
|
||||
|
||||
uint32_t relSize = 0;
|
||||
for (uint16_t color : protoPal) {
|
||||
auto n = std::count_if(RANGE(*this), [this, &color](ProtoPalAttrs const &attrs) {
|
||||
ProtoPalette const &pal = (*_protoPals)[attrs.protoPalIndex];
|
||||
return std::find(RANGE(pal), color) != pal.end();
|
||||
});
|
||||
// NOTE: The paper and the associated code disagree on this: the code has
|
||||
// this `1 +`, whereas the paper does not; its lack causes a division by 0
|
||||
// if the symbol is not found anywhere, so I'm assuming the paper is wrong.
|
||||
relSize += 1. / (1 + n);
|
||||
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];
|
||||
return std::find(RANGE(pal), color) != pal.end();
|
||||
});
|
||||
// We increase the denominator by 1 here; the reference code does this,
|
||||
// but the paper does not. Not adding 1 makes a multiplicity of 0 cause a division by 0
|
||||
// (that is, if the color is not found in any proto-palette), and adding 1 still seems
|
||||
// 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;
|
||||
}
|
||||
@@ -379,13 +401,15 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
|
||||
for (; !queue.empty(); 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];
|
||||
size_t bestPalIndex = assignments.size();
|
||||
// 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
|
||||
double bestRelSize = protoPal.size();
|
||||
uint32_t bestRelSize = protoPal.size() * AssignedProtos::scaleFactor;
|
||||
|
||||
for (size_t i = 0; i < assignments.size(); ++i) {
|
||||
// Skip the page if this one is banned from it
|
||||
@@ -393,11 +417,11 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
continue;
|
||||
}
|
||||
|
||||
double relSize = assignments[i].relSizeOf(protoPal);
|
||||
uint32_t relSize = assignments[i].relSizeOf(protoPal);
|
||||
options.verbosePrint(
|
||||
Options::VERB_DEBUG,
|
||||
"%zu/%zu: Rel size: %f (size = %zu)\n",
|
||||
i + 1,
|
||||
Options::VERB_TRACE,
|
||||
" Relative size to palette %zu (of %zu): %" PRIu32 " (size = %zu)\n",
|
||||
i,
|
||||
assignments.size(),
|
||||
relSize,
|
||||
protoPal.size()
|
||||
@@ -410,8 +434,20 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
|
||||
if (bestPalIndex == assignments.size()) {
|
||||
// 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));
|
||||
} else {
|
||||
options.verbosePrint(
|
||||
Options::VERB_TRACE,
|
||||
"Assigning proto-palette %zu to palette %zu\n",
|
||||
attrs.protoPalIndex,
|
||||
bestPalIndex
|
||||
);
|
||||
auto &bestPal = assignments[bestPalIndex];
|
||||
// Add the color to that palette
|
||||
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)
|
||||
while (bestPal.volume() > options.maxOpaqueColors()) {
|
||||
options.verbosePrint(
|
||||
Options::VERB_DEBUG,
|
||||
Options::VERB_TRACE,
|
||||
"Palette %zu is overloaded! (%zu > %" PRIu8 ")\n",
|
||||
bestPalIndex,
|
||||
bestPal.volume(),
|
||||
@@ -427,28 +463,67 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
);
|
||||
|
||||
// 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(
|
||||
RANGE(bestPal),
|
||||
[&efficiency,
|
||||
&protoPalettes](ProtoPalAttrs const &lhs, ProtoPalAttrs const &rhs) {
|
||||
return efficiency(protoPalettes[lhs.protoPalIndex])
|
||||
< efficiency(protoPalettes[rhs.protoPalIndex]);
|
||||
[&bestPal, &protoPalettes](ProtoPalAttrs const &lhs, ProtoPalAttrs const &rhs) {
|
||||
ProtoPalette const &lhsProtoPal = protoPalettes[lhs.protoPalIndex];
|
||||
ProtoPalette const &rhsProtoPal = 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
|
||||
// TODO: maybe not ideal to re-compute these two?
|
||||
// TODO: yikes for float comparison! I *think* this threshold is OK?
|
||||
if (efficiency(protoPalettes[maxEfficiencyIter->protoPalIndex])
|
||||
- efficiency(protoPalettes[minEfficiencyIter->protoPalIndex])
|
||||
< .001) {
|
||||
ProtoPalette const &minProtoPal = protoPalettes[minEfficiencyIter->protoPalIndex];
|
||||
ProtoPalette const &maxProtoPal = protoPalettes[maxEfficiencyIter->protoPalIndex];
|
||||
size_t minSize = minProtoPal.size();
|
||||
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;
|
||||
}
|
||||
|
||||
// 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.back().banFrom(bestPalIndex); // Ban it from this palette
|
||||
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
|
||||
options.verbosePrint(
|
||||
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(),
|
||||
attrs.protoPalIndex
|
||||
);
|
||||
@@ -491,7 +566,7 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
} else {
|
||||
options.verbosePrint(
|
||||
Options::VERB_DEBUG,
|
||||
"Assigning overflowing proto-pal %zu to palette %zu\n",
|
||||
"Assigning overflowing proto-palette %zu to palette %zu\n",
|
||||
attrs.protoPalIndex,
|
||||
iter - assignments.begin()
|
||||
);
|
||||
@@ -537,5 +612,3 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
||||
}
|
||||
return {mappings, assignments.size()};
|
||||
}
|
||||
|
||||
} // namespace packing
|
||||
|
||||
@@ -8,9 +8,7 @@
|
||||
|
||||
#include "gfx/main.hpp"
|
||||
|
||||
namespace sorting {
|
||||
|
||||
void indexed(
|
||||
void sortIndexed(
|
||||
std::vector<Palette> &palettes,
|
||||
int palSize,
|
||||
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
|
||||
) {
|
||||
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
|
||||
// we should only have a single palette.
|
||||
@@ -58,7 +56,7 @@ void grayscale(
|
||||
|
||||
Palette &palette = palettes[0];
|
||||
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()) {
|
||||
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 green = color >> 5 & 0b11111;
|
||||
uint8_t blue = color >> 10;
|
||||
return 2126 * red + 7152 * green + 722 * blue;
|
||||
}
|
||||
|
||||
void rgb(std::vector<Palette> &palettes) {
|
||||
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palettes by \"\"\"luminance\"\"\"...\n");
|
||||
void sortRgb(std::vector<Palette> &palettes) {
|
||||
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palettes by luminance...\n");
|
||||
|
||||
for (Palette &pal : palettes) {
|
||||
std::sort(RANGE(pal), [](uint16_t lhs, uint16_t rhs) {
|
||||
return legacyLuminance(lhs) > legacyLuminance(rhs);
|
||||
return luminance(lhs) > luminance(rhs);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sorting
|
||||
|
||||
@@ -211,11 +211,12 @@ static T readLE(U const *bytes) {
|
||||
[[gnu::warn_unused_result]] // Ignoring EOF is a bad idea.
|
||||
static bool
|
||||
readLine(std::filebuf &file, std::string &buffer) {
|
||||
assume(buffer.empty());
|
||||
// TODO: maybe this can be optimized to bulk reads?
|
||||
for (;;) {
|
||||
auto c = file.sbumpc();
|
||||
if (c == std::filebuf::traits_type::eof()) {
|
||||
return false;
|
||||
return !buffer.empty();
|
||||
}
|
||||
if (c == '\n') {
|
||||
// Discard a trailing CRLF
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "defaultinitalloc.hpp"
|
||||
#include "defaultinitvec.hpp"
|
||||
#include "file.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "itertools.hpp"
|
||||
@@ -104,7 +104,7 @@ class Png {
|
||||
"bytes after reading %zu)",
|
||||
self->c_str(),
|
||||
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");
|
||||
|
||||
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) {
|
||||
fatal("Failed to allocate PNG structure: %s", strerror(errno));
|
||||
@@ -446,7 +446,7 @@ public:
|
||||
};
|
||||
|
||||
private:
|
||||
struct iterator {
|
||||
struct Iterator {
|
||||
TilesVisitor const &parent;
|
||||
uint32_t const limit;
|
||||
uint32_t x, y;
|
||||
@@ -458,7 +458,7 @@ public:
|
||||
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);
|
||||
major += 8;
|
||||
if (major == limit) {
|
||||
@@ -468,19 +468,14 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend bool operator==(iterator const &lhs, iterator const &rhs) {
|
||||
return lhs.coords() == rhs.coords(); // Compare the returned coord pairs
|
||||
}
|
||||
|
||||
friend bool operator!=(iterator const &lhs, iterator const &rhs) {
|
||||
return lhs.coords() != rhs.coords(); // Compare the returned coord pairs
|
||||
}
|
||||
bool operator==(Iterator const &rhs) const { return coords() == rhs.coords(); }
|
||||
bool operator!=(Iterator const &rhs) const { return coords() != rhs.coords(); }
|
||||
};
|
||||
|
||||
public:
|
||||
iterator begin() const { return {*this, _limit, 0, 0}; }
|
||||
iterator end() const {
|
||||
iterator it{*this, _limit, _width - 8, _height - 8}; // Last valid one...
|
||||
Iterator begin() const { return {*this, _limit, 0, 0}; }
|
||||
Iterator end() const {
|
||||
Iterator it{*this, _limit, _width - 8, _height - 8}; // Last valid one...
|
||||
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) {
|
||||
// Run a "pagination" problem solver
|
||||
// TODO: allow picking one of several solvers?
|
||||
auto [mappings, nbPalettes] = packing::overloadAndRemove(protoPalettes);
|
||||
auto [mappings, nbPalettes] = overloadAndRemove(protoPalettes);
|
||||
assume(mappings.size() == protoPalettes.size());
|
||||
|
||||
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
|
||||
auto [embPalSize, embPalRGB, embPalAlphaSize, embPalAlpha] = png.getEmbeddedPal();
|
||||
if (embPalRGB != nullptr) {
|
||||
sorting::indexed(palettes, embPalSize, embPalRGB, embPalAlphaSize, embPalAlpha);
|
||||
sortIndexed(palettes, embPalSize, embPalRGB, embPalAlphaSize, embPalAlpha);
|
||||
} else if (png.isSuitableForGrayscale()) {
|
||||
sorting::grayscale(palettes, png.getColors().raw());
|
||||
sortGrayscale(palettes, png.getColors().raw());
|
||||
} else {
|
||||
sorting::rgb(palettes);
|
||||
sortRgb(palettes);
|
||||
}
|
||||
return {mappings, palettes};
|
||||
}
|
||||
@@ -717,6 +712,9 @@ static void hashBitplanes(uint16_t bitplanes, uint16_t &hash) {
|
||||
}
|
||||
|
||||
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;
|
||||
// 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
|
||||
@@ -760,9 +758,7 @@ public:
|
||||
hashBitplanes(bitplanes, _hash);
|
||||
|
||||
_data[writeIndex++] = bitplanes & 0xFF;
|
||||
if (options.bitDepth == 2) {
|
||||
_data[writeIndex++] = bitplanes >> 8;
|
||||
}
|
||||
_data[writeIndex++] = bitplanes >> 8;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -826,9 +822,7 @@ public:
|
||||
|
||||
return MatchType::NOPE;
|
||||
}
|
||||
friend bool operator==(TileData const &lhs, TileData const &rhs) {
|
||||
return lhs.tryMatching(rhs) != MatchType::NOPE;
|
||||
}
|
||||
bool operator==(TileData const &rhs) const { return tryMatching(rhs) != MatchType::NOPE; }
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -836,9 +830,7 @@ struct std::hash<TileData> {
|
||||
std::size_t operator()(TileData const &tile) const { return tile.hash(); }
|
||||
};
|
||||
|
||||
namespace unoptimized {
|
||||
|
||||
static void outputTileData(
|
||||
static void outputUnoptimizedTileData(
|
||||
Png const &png,
|
||||
DefaultInitVec<AttrmapEntry> const &attrmap,
|
||||
std::vector<Palette> const &palettes,
|
||||
@@ -877,7 +869,7 @@ static void outputTileData(
|
||||
assume(remainingTiles == 0);
|
||||
}
|
||||
|
||||
static void outputMaps(
|
||||
static void outputUnoptimizedMaps(
|
||||
DefaultInitVec<AttrmapEntry> const &attrmap, DefaultInitVec<size_t> const &mappings
|
||||
) {
|
||||
std::optional<File> tilemapOutput, attrmapOutput, palmapOutput;
|
||||
@@ -916,10 +908,6 @@ static void outputMaps(
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace unoptimized
|
||||
|
||||
namespace optimized {
|
||||
|
||||
struct UniqueTiles {
|
||||
std::unordered_set<TileData> tileset;
|
||||
std::vector<TileData const *> tiles;
|
||||
@@ -1045,7 +1033,14 @@ static void outputTileData(UniqueTiles const &tiles) {
|
||||
TileData const *tile = *iter;
|
||||
assume(tile->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() {
|
||||
options.verbosePrint(Options::VERB_CFG, "Using libpng %s\n", png_get_libpng_ver(nullptr));
|
||||
|
||||
@@ -1130,33 +1123,44 @@ void process() {
|
||||
DefaultInitVec<AttrmapEntry> attrmap{};
|
||||
|
||||
for (auto tile : png.visitAsTiles()) {
|
||||
ProtoPalette tileColors;
|
||||
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 x = 0; x < 8; ++x) {
|
||||
Rgba color = tile.pixel(x, y);
|
||||
if (!color.isTransparent()) { // Do not count transparency in for packing
|
||||
// Add the color to the proto-pal (if not full), and count it if it was unique.
|
||||
if (tileColors.add(color.cgbColor())) {
|
||||
++nbColorsInTile;
|
||||
}
|
||||
if (Rgba color = tile.pixel(x, y); !color.isTransparent()) {
|
||||
tileColors.insert(color.cgbColor());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
// "Empty" proto-palettes screw with the packing process, so discard those
|
||||
attrs.protoPaletteID = AttrmapEntry::transparent;
|
||||
continue;
|
||||
}
|
||||
|
||||
ProtoPalette protoPalette;
|
||||
for (uint16_t cgbColor : tileColors) {
|
||||
protoPalette.add(cgbColor);
|
||||
}
|
||||
|
||||
// Insert the proto-palette, making sure to avoid overlaps
|
||||
for (size_t n = 0; n < protoPalettes.size(); ++n) {
|
||||
switch (tileColors.compare(protoPalettes[n])) {
|
||||
switch (protoPalette.compare(protoPalettes[n])) {
|
||||
case ProtoPalette::WE_BIGGER:
|
||||
protoPalettes[n] = tileColors; // Override them
|
||||
protoPalettes[n] = protoPalette; // Override them
|
||||
// Remove any other proto-palettes that we encompass
|
||||
// (Example [(0, 1), (0, 2)], inserting (0, 1, 2))
|
||||
/*
|
||||
@@ -1166,7 +1170,7 @@ void process() {
|
||||
* Investigation is necessary, especially if pathological cases are found.
|
||||
*
|
||||
* 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);
|
||||
* }
|
||||
* }
|
||||
@@ -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();
|
||||
if (protoPalettes.size() == AttrmapEntry::transparent) { // Check for overflow
|
||||
fatal(
|
||||
@@ -1201,7 +1194,7 @@ void process() {
|
||||
AttrmapEntry::transparent
|
||||
);
|
||||
}
|
||||
protoPalettes.push_back(tileColors);
|
||||
protoPalettes.push_back(protoPalette);
|
||||
continue_visiting_tiles:;
|
||||
}
|
||||
|
||||
@@ -1251,7 +1244,7 @@ continue_visiting_tiles:;
|
||||
|
||||
if (!options.output.empty()) {
|
||||
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()) {
|
||||
@@ -1259,12 +1252,12 @@ continue_visiting_tiles:;
|
||||
Options::VERB_LOG_ACT,
|
||||
"Generating unoptimized tilemap and/or attrmap and/or palmap...\n"
|
||||
);
|
||||
unoptimized::outputMaps(attrmap, mappings);
|
||||
outputUnoptimizedMaps(attrmap, mappings);
|
||||
}
|
||||
} else {
|
||||
// All of these require the deduplication process to be performed to be output
|
||||
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]) {
|
||||
fatal(
|
||||
@@ -1277,22 +1270,22 @@ continue_visiting_tiles:;
|
||||
|
||||
if (!options.output.empty()) {
|
||||
options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized tile data...\n");
|
||||
optimized::outputTileData(tiles);
|
||||
outputTileData(tiles);
|
||||
}
|
||||
|
||||
if (!options.tilemap.empty()) {
|
||||
options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized tilemap...\n");
|
||||
optimized::outputTilemap(attrmap);
|
||||
outputTilemap(attrmap);
|
||||
}
|
||||
|
||||
if (!options.attrmap.empty()) {
|
||||
options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized attrmap...\n");
|
||||
optimized::outputAttrmap(attrmap, mappings);
|
||||
outputAttrmap(attrmap, mappings);
|
||||
}
|
||||
|
||||
if (!options.palmap.empty()) {
|
||||
options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized palmap...\n");
|
||||
optimized::outputPalmap(attrmap, mappings);
|
||||
outputPalmap(attrmap, mappings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
bool ProtoPalette::add(uint16_t color) {
|
||||
void ProtoPalette::add(uint16_t color) {
|
||||
size_t i = 0;
|
||||
|
||||
// Seek the first slot greater than the new color
|
||||
@@ -16,12 +16,12 @@ bool ProtoPalette::add(uint16_t color) {
|
||||
++i;
|
||||
if (i == _colorIndices.size()) {
|
||||
// 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 (_colorIndices[i] == color) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Swap entries until the end
|
||||
@@ -30,12 +30,11 @@ bool ProtoPalette::add(uint16_t color) {
|
||||
++i;
|
||||
if (i == _colorIndices.size()) {
|
||||
// The set is full, but doesn't include the new color.
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Write that last one into the new slot
|
||||
_colorIndices[i] = color;
|
||||
return true;
|
||||
}
|
||||
|
||||
ProtoPalette::ComparisonResult ProtoPalette::compare(ProtoPalette const &other) const {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
|
||||
#include "defaultinitalloc.hpp"
|
||||
#include "defaultinitvec.hpp"
|
||||
#include "file.hpp"
|
||||
#include "helpers.hpp" // assume
|
||||
|
||||
@@ -164,7 +164,7 @@ void reverse() {
|
||||
// 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.
|
||||
// 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) {
|
||||
if (mapSize % width == 0)
|
||||
break;
|
||||
@@ -389,7 +389,9 @@ void reverse() {
|
||||
palmap = readInto(options.palmap);
|
||||
if (palmap->size() != mapSize) {
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,7 +248,7 @@ static void placeSection(Section §ion) {
|
||||
bankMem.insert(
|
||||
bankMem.begin() + spaceIdx + 1,
|
||||
{.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`
|
||||
// invalidates all references to itself!**
|
||||
@@ -279,7 +279,7 @@ static void placeSection(Section §ion) {
|
||||
sizeof(where),
|
||||
"in bank $%02" PRIx32 " with align mask $%" PRIx16,
|
||||
section.bank,
|
||||
(uint16_t)~section.alignMask
|
||||
static_cast<uint16_t>(~section.alignMask)
|
||||
);
|
||||
else
|
||||
snprintf(where, sizeof(where), "in bank $%02" PRIx32, section.bank);
|
||||
@@ -291,7 +291,7 @@ static void placeSection(Section §ion) {
|
||||
where,
|
||||
sizeof(where),
|
||||
"with align mask $%" PRIx16 " and offset $%" PRIx16,
|
||||
(uint16_t)~section.alignMask,
|
||||
static_cast<uint16_t>(~section.alignMask),
|
||||
section.alignOfs
|
||||
);
|
||||
else
|
||||
|
||||
@@ -209,8 +209,8 @@ static void parseScrambleSpec(char const *spec) {
|
||||
// Remember where the region's name begins and ends
|
||||
char const *regionName = spec;
|
||||
size_t regionNameLen = strcspn(spec, "=, \t");
|
||||
// Length of region name string slice for printing, truncated if too long
|
||||
int regionNamePrintLen = regionNameLen > INT_MAX ? INT_MAX : (int)regionNameLen;
|
||||
// Length of region name string slice for print formatting, truncated if too long
|
||||
int regionNameFmtLen = regionNameLen > INT_MAX ? INT_MAX : static_cast<int>(regionNameLen);
|
||||
ScrambledRegion region = SCRAMBLE_UNK;
|
||||
|
||||
// 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");
|
||||
if (*spec != '\0' && *spec != ',' && *spec != '=') {
|
||||
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
|
||||
spec += 1 + strcspn(&spec[1], ",=");
|
||||
@@ -246,7 +246,7 @@ static void parseScrambleSpec(char const *spec) {
|
||||
}
|
||||
|
||||
if (region == SCRAMBLE_UNK)
|
||||
argErr('S', "Unknown region \"%.*s\"", regionNamePrintLen, regionName);
|
||||
argErr('S', "Unknown region \"%.*s\"", regionNameFmtLen, regionName);
|
||||
|
||||
if (*spec == '=') {
|
||||
spec++; // `strtoul` will skip the whitespace on its own
|
||||
@@ -254,7 +254,7 @@ static void parseScrambleSpec(char const *spec) {
|
||||
char *endptr;
|
||||
|
||||
if (*spec == '\0' || *spec == ',') {
|
||||
argErr('S', "Empty limit for region \"%.*s\"", regionNamePrintLen, regionName);
|
||||
argErr('S', "Empty limit for region \"%.*s\"", regionNameFmtLen, regionName);
|
||||
goto next;
|
||||
}
|
||||
limit = strtoul(spec, &endptr, 10);
|
||||
@@ -263,7 +263,7 @@ static void parseScrambleSpec(char const *spec) {
|
||||
argErr(
|
||||
'S',
|
||||
"Invalid non-numeric limit for region \"%.*s\"",
|
||||
regionNamePrintLen,
|
||||
regionNameFmtLen,
|
||||
regionName
|
||||
);
|
||||
endptr = strchr(endptr, ',');
|
||||
@@ -274,7 +274,7 @@ static void parseScrambleSpec(char const *spec) {
|
||||
argErr(
|
||||
'S',
|
||||
"Limit for region \"%.*s\" may not exceed %" PRIu16,
|
||||
regionNamePrintLen,
|
||||
regionNameFmtLen,
|
||||
regionName,
|
||||
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
|
||||
scrambleWRAMX = 7;
|
||||
} 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
|
||||
|
||||
@@ -30,23 +30,21 @@ static std::vector<std::vector<FileStackNode>> nodes;
|
||||
|
||||
// Helper functions for reading object files
|
||||
|
||||
// Internal, DO NOT USE.
|
||||
// For helper wrapper macros defined below, such as `tryReadLong`
|
||||
// For internal use only by `tryReadLong` and `tryGetc`!
|
||||
#define tryRead(func, type, errval, vartype, var, file, ...) \
|
||||
do { \
|
||||
FILE *tmpFile = file; \
|
||||
type tmpVal = func(tmpFile); \
|
||||
/* TODO: maybe mark the condition as `unlikely`; how to do that portably? */ \
|
||||
if (tmpVal == (errval)) { \
|
||||
errx(__VA_ARGS__, feof(tmpFile) ? "Unexpected end of file" : strerror(errno)); \
|
||||
} \
|
||||
var = (vartype)tmpVal; \
|
||||
var = static_cast<vartype>(tmpVal); \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* Reads an unsigned long (32-bit) value from a file.
|
||||
* @param file The file to read from. This will read 4 bytes from the file.
|
||||
* @return The value read, cast to a int64_t, or -1 on failure.
|
||||
* @return The value read, cast to a int64_t, or `INT64_MAX` on failure.
|
||||
*/
|
||||
static int64_t readLong(FILE *file) {
|
||||
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
|
||||
// however causes values larger than 127 to be too large when
|
||||
// shifted, potentially triggering undefined behavior.
|
||||
value |= (unsigned int)byte << shift;
|
||||
value |= static_cast<unsigned int>(byte) << shift;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -128,7 +126,7 @@ static void readFileStackNode(
|
||||
uint32_t parentID;
|
||||
|
||||
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(
|
||||
node.lineNo, file, "%s: Cannot read node #%" PRIu32 "'s line number: %s", fileName, i
|
||||
);
|
||||
@@ -329,7 +327,7 @@ static void readPatch(
|
||||
static void
|
||||
linkPatchToPCSect(Patch &patch, std::vector<std::unique_ptr<Section>> const &fileSections) {
|
||||
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());
|
||||
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.offset = 0;
|
||||
tryGetc(
|
||||
@@ -379,7 +377,7 @@ static void readSection(
|
||||
tryReadLong(tmp, file, "%s: Cannot read \"%s\"'s org: %s", fileName, section.name.c_str());
|
||||
section.isAddressFixed = tmp >= 0;
|
||||
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;
|
||||
}
|
||||
section.org = tmp;
|
||||
@@ -405,7 +403,7 @@ static void readSection(
|
||||
error(
|
||||
nullptr,
|
||||
0,
|
||||
"\"%s\"'s alignment offset is too large (%" PRId32 ")",
|
||||
"\"%s\"'s alignment offset is too large ($%" PRIx32 ")",
|
||||
section.name.c_str(),
|
||||
tmp
|
||||
);
|
||||
@@ -490,7 +488,8 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
||||
file = fopen(fileName, "rb");
|
||||
} else {
|
||||
fileName = "<stdin>";
|
||||
file = fdopen(STDIN_FILENO, "rb"); // `stdin` is in text mode by default
|
||||
(void)setmode(STDIN_FILENO, O_BINARY);
|
||||
file = stdin;
|
||||
}
|
||||
if (!file)
|
||||
err("Failed to open file \"%s\"", fileName);
|
||||
|
||||
@@ -207,7 +207,8 @@ static void writeROM() {
|
||||
outputFile = fopen(outputFileName, "wb");
|
||||
} else {
|
||||
outputFileName = "<stdout>";
|
||||
outputFile = fdopen(STDOUT_FILENO, "wb");
|
||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||
outputFile = stdout;
|
||||
}
|
||||
if (!outputFile)
|
||||
err("Failed to open output file \"%s\"", outputFileName);
|
||||
@@ -222,7 +223,8 @@ static void writeROM() {
|
||||
overlayFile = fopen(overlayFileName, "rb");
|
||||
} else {
|
||||
overlayFileName = "<stdin>";
|
||||
overlayFile = fdopen(STDIN_FILENO, "rb");
|
||||
(void)setmode(STDIN_FILENO, O_BINARY);
|
||||
overlayFile = stdin;
|
||||
}
|
||||
if (!overlayFile)
|
||||
err("Failed to open overlay file \"%s\"", overlayFileName);
|
||||
@@ -264,15 +266,15 @@ static bool isLegalForSymName(char 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`.
|
||||
static void printSymName(char const *name) {
|
||||
for (char const *ptr = name; *ptr != '\0';) {
|
||||
static void printSymName(std::string const &name, FILE *file) {
|
||||
for (char const *ptr = name.c_str(); *ptr != '\0';) {
|
||||
char c = *ptr;
|
||||
|
||||
if (isLegalForSymName(c)) {
|
||||
// Output legal ASCII characters as-is
|
||||
putc(c, symFile);
|
||||
putc(c, file);
|
||||
++ptr;
|
||||
} else {
|
||||
// Output illegal characters using Unicode escapes
|
||||
@@ -293,7 +295,7 @@ static void printSymName(char const *name) {
|
||||
++ptr;
|
||||
} 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]))
|
||||
symList.push_back({
|
||||
.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) {
|
||||
fprintf(symFile, "%02" PRIx32 ":%04" PRIx16 " ", symBank, sym.addr);
|
||||
printSymName(sym.sym->name.c_str());
|
||||
printSymName(sym.sym->name, 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
|
||||
*/
|
||||
@@ -425,35 +452,22 @@ static void writeMapBank(SortedSections const §List, SectionType type, uint3
|
||||
|
||||
prevEndAddr = sect->org + sect->size;
|
||||
|
||||
fprintf(mapFile, "\tSECTION: $%04" PRIx16, sect->org);
|
||||
if (sect->size != 0)
|
||||
fprintf(
|
||||
mapFile,
|
||||
"\tSECTION: $%04" PRIx16 "-$%04x ($%04" PRIx16 " byte%s) [\"%s\"]\n",
|
||||
sect->org,
|
||||
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()
|
||||
);
|
||||
fprintf(mapFile, "-$%04x", prevEndAddr - 1);
|
||||
fprintf(mapFile, " ($%04" PRIx16 " byte%s) [\"", sect->size, sect->size == 1 ? "" : "s");
|
||||
printSectionName(sect->name, mapFile);
|
||||
fputs("\"]\n", mapFile);
|
||||
|
||||
if (!noSymInMap) {
|
||||
// Also print symbols in the following "pieces"
|
||||
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 ..."
|
||||
fprintf(
|
||||
mapFile,
|
||||
"\t $%04" PRIx32 " = %s\n",
|
||||
sym->label().offset + org,
|
||||
sym->name.c_str()
|
||||
);
|
||||
fprintf(mapFile, "\t $%04" PRIx32 " = ", sym->label().offset + org);
|
||||
printSymName(sym->name, mapFile);
|
||||
putc('\n', mapFile);
|
||||
}
|
||||
|
||||
if (sect->nextu) {
|
||||
// Announce the following "piece"
|
||||
@@ -545,7 +559,8 @@ static void writeSym() {
|
||||
symFile = fopen(symFileName, "w");
|
||||
} else {
|
||||
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)
|
||||
err("Failed to open sym file \"%s\"", symFileName);
|
||||
@@ -576,7 +591,9 @@ static void writeSym() {
|
||||
for (Symbol *sym : constants) {
|
||||
int32_t val = sym->data.get<int32_t>();
|
||||
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");
|
||||
} else {
|
||||
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)
|
||||
err("Failed to open map file \"%s\"", mapFileName);
|
||||
|
||||
@@ -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) {
|
||||
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];
|
||||
|
||||
// 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) {
|
||||
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();
|
||||
|
||||
while (size > 0) {
|
||||
RPNCommand command = (RPNCommand)getRPNByte(expression, size, patch);
|
||||
RPNCommand command = static_cast<RPNCommand>(getRPNByte(expression, size, patch));
|
||||
int32_t value;
|
||||
|
||||
isError = false;
|
||||
@@ -101,9 +101,10 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
case RPN_DIV:
|
||||
value = popRPN(patch);
|
||||
if (value == 0) {
|
||||
if (!isError)
|
||||
if (!isError) {
|
||||
error(patch.src, patch.lineNo, "Division by 0");
|
||||
isError = true;
|
||||
isError = true;
|
||||
}
|
||||
popRPN(patch);
|
||||
value = INT32_MAX;
|
||||
} else {
|
||||
@@ -113,9 +114,10 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
case RPN_MOD:
|
||||
value = popRPN(patch);
|
||||
if (value == 0) {
|
||||
if (!isError)
|
||||
if (!isError) {
|
||||
error(patch.src, patch.lineNo, "Modulo by 0");
|
||||
isError = true;
|
||||
isError = true;
|
||||
}
|
||||
popRPN(patch);
|
||||
value = 0;
|
||||
} else {
|
||||
@@ -128,9 +130,10 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
case RPN_EXP:
|
||||
value = popRPN(patch);
|
||||
if (value < 0) {
|
||||
if (!isError)
|
||||
error(patch.src, patch.lineNo, "Exponent by negative");
|
||||
isError = true;
|
||||
if (!isError) {
|
||||
error(patch.src, patch.lineNo, "Exponent by negative value %" PRId32, value);
|
||||
isError = true;
|
||||
}
|
||||
popRPN(patch);
|
||||
value = 0;
|
||||
} else {
|
||||
@@ -147,11 +150,11 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
|
||||
case RPN_BITWIDTH:
|
||||
value = popRPN(patch);
|
||||
value = value != 0 ? 32 - clz((uint32_t)value) : 0;
|
||||
value = value != 0 ? 32 - clz(static_cast<uint32_t>(value)) : 0;
|
||||
break;
|
||||
case RPN_TZCOUNT:
|
||||
value = popRPN(patch);
|
||||
value = value != 0 ? ctz((uint32_t)value) : 32;
|
||||
value = value != 0 ? ctz(static_cast<uint32_t>(value)) : 32;
|
||||
break;
|
||||
|
||||
case RPN_OR:
|
||||
@@ -246,7 +249,7 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
case RPN_BANK_SECT: {
|
||||
// `expression` is not guaranteed to be '\0'-terminated. If it is not,
|
||||
// `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))
|
||||
;
|
||||
|
||||
@@ -277,7 +280,7 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
|
||||
case RPN_SIZEOF_SECT: {
|
||||
// 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))
|
||||
;
|
||||
|
||||
@@ -298,7 +301,7 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
|
||||
case RPN_STARTOF_SECT: {
|
||||
// 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))
|
||||
;
|
||||
|
||||
@@ -342,9 +345,23 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
|
||||
case RPN_HRAM:
|
||||
value = popRPN(patch);
|
||||
if (!isError && (value < 0 || (value > 0xFF && value < 0xFF00) || value > 0xFFFF)) {
|
||||
error(patch.src, patch.lineNo, "Value %" PRId32 " is not in HRAM range", value);
|
||||
isError = true;
|
||||
if (value < 0 || (value > 0xFF && value < 0xFF00) || value > 0xFFFF) {
|
||||
if (!isError) {
|
||||
error(
|
||||
patch.src,
|
||||
patch.lineNo,
|
||||
"Address $%" PRIx32 " for LDH is not in HRAM range",
|
||||
value
|
||||
);
|
||||
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;
|
||||
break;
|
||||
@@ -354,9 +371,11 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
||||
// Acceptable values are 0x00, 0x08, 0x10, ..., 0x38
|
||||
// They can be easily checked with a bitmask
|
||||
if (value & ~0x38) {
|
||||
if (!isError)
|
||||
error(patch.src, patch.lineNo, "Value %" PRId32 " is not a RST vector", value);
|
||||
isError = true;
|
||||
if (!isError) {
|
||||
error(patch.src, patch.lineNo, "Value $%" PRIx32 " is not a RST vector", value);
|
||||
isError = true;
|
||||
}
|
||||
value = 0;
|
||||
}
|
||||
value |= 0xC7;
|
||||
break;
|
||||
@@ -415,7 +434,7 @@ void patch_CheckAssertions() {
|
||||
|
||||
for (Assertion &assert : assertions) {
|
||||
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) {
|
||||
switch (type) {
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
/******************** Tokens and data types ********************/
|
||||
|
||||
%token YYEOF 0 "end of file"
|
||||
%token newline
|
||||
%token newline "end of line"
|
||||
%token COMMA ","
|
||||
%token ORG "ORG"
|
||||
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
|
||||
", past $%04" PRIx16,
|
||||
pc,
|
||||
(uint16_t)(pc + length),
|
||||
(uint16_t)(endaddr(activeType) + 1)
|
||||
static_cast<uint16_t>(pc + length),
|
||||
static_cast<uint16_t>(endaddr(activeType) + 1)
|
||||
);
|
||||
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,
|
||||
length,
|
||||
typeInfo.size - offset,
|
||||
(uint16_t)(endaddr(activeType) + 1)
|
||||
static_cast<uint16_t>(endaddr(activeType) + 1)
|
||||
);
|
||||
} else {
|
||||
pc += length;
|
||||
@@ -689,7 +689,7 @@ static void placeSection(std::string const &name, bool isOptional) {
|
||||
name.c_str(),
|
||||
org,
|
||||
alignment,
|
||||
(uint16_t)(org & section->alignMask),
|
||||
static_cast<uint16_t>(org & section->alignMask),
|
||||
alignment,
|
||||
section->alignOfs
|
||||
);
|
||||
|
||||
@@ -468,7 +468,7 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
getToken(nullptr, "'R' line is too short");
|
||||
areaIdx = parseByte(where, lineNo, token, numberType);
|
||||
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())
|
||||
fatal(
|
||||
&where,
|
||||
@@ -532,8 +532,9 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
||||
|
||||
if ((flags & 0xF0) == 0xF0) {
|
||||
getToken(nullptr, "Incomplete relocation");
|
||||
flags =
|
||||
(flags & 0x0F) | (uint16_t)parseByte(where, lineNo, token, numberType) << 4;
|
||||
flags = (flags & 0x0F)
|
||||
| static_cast<uint16_t>(parseByte(where, lineNo, token, numberType))
|
||||
<< 4;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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
|
||||
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[0] = RPN_SIZEOF_SECT;
|
||||
memcpy(
|
||||
(char *)&patch.rpnExpression[1],
|
||||
reinterpret_cast<char *>(&patch.rpnExpression[1]),
|
||||
&sym.name.c_str()[2],
|
||||
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[0] = RPN_STARTOF_SECT;
|
||||
memcpy(
|
||||
(char *)&patch.rpnExpression[1],
|
||||
reinterpret_cast<char *>(&patch.rpnExpression[1]),
|
||||
&sym.name.c_str()[2],
|
||||
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[0] = RPN_STARTOF_SECT;
|
||||
// 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);
|
||||
|
||||
@@ -48,7 +48,7 @@ int32_t op_shift_left(int32_t value, int32_t amount) {
|
||||
|
||||
// Use unsigned to force a bitwise shift
|
||||
// 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) {
|
||||
@@ -63,7 +63,7 @@ int32_t op_shift_right(int32_t value, int32_t amount) {
|
||||
return op_shift_left(value, -amount);
|
||||
|
||||
if (value > 0)
|
||||
return (uint32_t)value >> amount;
|
||||
return static_cast<uint32_t>(value) >> amount;
|
||||
|
||||
// Calculate an OR mask for sign extension
|
||||
// 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
|
||||
// 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) {
|
||||
@@ -83,5 +83,5 @@ int32_t op_shift_right_unsigned(int32_t value, int32_t amount) {
|
||||
if (amount < 0)
|
||||
return op_shift_left(value, -amount);
|
||||
|
||||
return (uint32_t)value >> amount;
|
||||
return static_cast<uint32_t>(value) >> amount;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ char const *printChar(int c) {
|
||||
default: // Print as hex
|
||||
buf[0] = '0';
|
||||
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;
|
||||
}
|
||||
buf[0] = '\'';
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
# 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(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
|
||||
DESTINATION ${rgbds_SOURCE_DIR}/test/gfx
|
||||
COMPONENT "Test support programs"
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
||||
configure_file(CTestCustom.cmake.in ${CMAKE_BINARY_DIR}/CTestCustom.cmake)
|
||||
|
||||
foreach(TARGET randtilegen rgbgfx_test)
|
||||
if(LIBPNG_FOUND) # pkg-config
|
||||
@@ -20,3 +25,8 @@ foreach(TARGET randtilegen rgbgfx_test)
|
||||
target_link_libraries(${TARGET} PRIVATE ${PNG_LIBRARIES})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
add_test(NAME all
|
||||
COMMAND ./run-tests.sh ${ONLY_FREE}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
1
test/CTestCustom.cmake.in
Normal file
1
test/CTestCustom.cmake.in
Normal file
@@ -0,0 +1 @@
|
||||
set(CTEST_CUSTOM_PRE_TEST "bash -c 'cd @CMAKE_CURRENT_SOURCE_DIR@\; ./fetch-test-deps.sh @ONLY_FREE@'")
|
||||
@@ -1,5 +1,5 @@
|
||||
error: charmap-empty.asm(1):
|
||||
Cannot map an empty string
|
||||
error: charmap-empty.asm(2):
|
||||
syntax error, unexpected newline
|
||||
syntax error, unexpected end of line
|
||||
error: Assembly aborted (2 errors)!
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
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):
|
||||
Macro "mac" not defined
|
||||
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):
|
||||
syntax error, unexpected PRINTLN, expecting newline
|
||||
syntax error, unexpected PRINTLN, expecting end of line
|
||||
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):
|
||||
syntax error, unexpected PRINTLN, expecting newline
|
||||
syntax error, unexpected PRINTLN, expecting end of line
|
||||
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)!
|
||||
|
||||
31
test/asm/deprecated-ldio.asm
Normal file
31
test/asm/deprecated-ldio.asm
Normal 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]
|
||||
32
test/asm/deprecated-ldio.err
Normal file
32
test/asm/deprecated-ldio.err
Normal 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
|
||||
1
test/asm/deprecated-ldio.out.bin
Normal file
1
test/asm/deprecated-ldio.out.bin
Normal 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>
|
||||
@@ -1,2 +1,5 @@
|
||||
FATAL: endsection-in-load.asm(3):
|
||||
Cannot end the section within a `LOAD` block
|
||||
warning: endsection-in-load.asm(3): [-Wunterminated-load]
|
||||
`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)!
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
|
||||
SECTION "ff00+c or not to ff00+c", ROMX
|
||||
|
||||
ld a, [$ff00 + c]
|
||||
ld [65280 + c], a
|
||||
ldh a, [$ff00 + c]
|
||||
ldh [65280 + c], a
|
||||
|
||||
; Not ok
|
||||
ld a, [$ff01 + c]
|
||||
ld [xyz + c], a
|
||||
ldh a, [$ff01 + c]
|
||||
ldh [xyz + c], a
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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)
|
||||
ld [ $ff00 + c ], a
|
||||
ld [ $ff00 + c ], a
|
||||
ldh [ $ff00 + c ], a
|
||||
ldh [ $ff00 + c ], a
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
SECTION "invalid", ROM0[$10000]
|
||||
ld [hl], [hl]
|
||||
ld a, [$00ff+c]
|
||||
ldh a, [$00ff+c]
|
||||
ld b, [c]
|
||||
ld b, [bc]
|
||||
ld b, [$4000]
|
||||
|
||||
@@ -17,7 +17,7 @@ error: invalid-opt.asm(8):
|
||||
error: invalid-opt.asm(9):
|
||||
Must specify an argument for option 'W'
|
||||
error: invalid-opt.asm(10):
|
||||
syntax error, unexpected newline, expecting string
|
||||
syntax error, unexpected end of line, expecting string
|
||||
error: invalid-opt.asm(11):
|
||||
No entries in the option stack
|
||||
error: Assembly aborted (11 errors)!
|
||||
|
||||
28
test/asm/lexer-hack.asm
Normal file
28
test/asm/lexer-hack.asm
Normal 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
9
test/asm/lexer-hack.err
Normal 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
6
test/asm/lexer-hack.out
Normal 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
|
||||
@@ -1,3 +1,3 @@
|
||||
println "Line \ ; this comment is ignored
|
||||
continuations\ ; so is this one
|
||||
continuations\ ; so is this one
|
||||
work!" ; =)
|
||||
|
||||
51
test/asm/load-endings.asm
Normal file
51
test/asm/load-endings.asm
Normal 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
|
||||
8
test/asm/load-endings.err
Normal file
8
test/asm/load-endings.err
Normal 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`
|
||||
1
test/asm/load-endings.out.bin
Normal file
1
test/asm/load-endings.out.bin
Normal file
@@ -0,0 +1 @@
|
||||
BRRAPQCS
|
||||
@@ -1,2 +1,29 @@
|
||||
SECTION "A", ROM0
|
||||
AData::
|
||||
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
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
error: load-fragment.asm(2):
|
||||
`LOAD FRAGMENT` is not allowed
|
||||
error: Assembly aborted (1 error)!
|
||||
BIN
test/asm/load-fragment.out.bin
Normal file
BIN
test/asm/load-fragment.out.bin
Normal file
Binary file not shown.
@@ -1,5 +1,5 @@
|
||||
SECTION "outer", ROM0
|
||||
LOAD "inner", WRAM0
|
||||
LOAD "matryoshka", HRAM
|
||||
ENDL
|
||||
ENDL
|
||||
LOAD "inner1", WRAM0 ; starts "inner1"
|
||||
LOAD "inner2", HRAM ; ends "inner1", starts "inner2"
|
||||
ENDL ; ends "inner2"
|
||||
ENDL ; error
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
error: load-in-load.asm(3):
|
||||
`LOAD` blocks cannot be nested
|
||||
warning: load-in-load.asm(3): [-Wunterminated-load]
|
||||
`LOAD` block without `ENDL` terminated by `LOAD`
|
||||
error: load-in-load.asm(5):
|
||||
Found `ENDL` outside of a `LOAD` block
|
||||
error: Assembly aborted (2 errors)!
|
||||
error: Assembly aborted (1 error)!
|
||||
|
||||
@@ -6,4 +6,6 @@ error: macro-syntax.asm(8):
|
||||
'\1' cannot be used outside of a macro
|
||||
error: macro-syntax.asm(9):
|
||||
syntax error, unexpected ENDM
|
||||
error: Assembly aborted (4 errors)!
|
||||
error: macro-syntax.asm(11):
|
||||
"old" is not a macro
|
||||
error: Assembly aborted (5 errors)!
|
||||
|
||||
@@ -2,6 +2,8 @@ warning: multiple-charmaps.asm(46) -> multiple-charmaps.asm::print_mapped(34): [
|
||||
Treating multi-unit strings as numbers is deprecated
|
||||
warning: multiple-charmaps.asm(54) -> multiple-charmaps.asm::print_mapped(34): [-Wobsolete]
|
||||
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]
|
||||
Treating multi-unit strings as numbers is deprecated
|
||||
warning: multiple-charmaps.asm(95) -> multiple-charmaps.asm::print_mapped(34): [-Wobsolete]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user