Compare commits

...

34 Commits

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

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

@@ -0,0 +1,44 @@
name: Build container image
on:
push:
branches:
- master
tags:
- '*' # This triggers the action on all tag pushes
jobs:
publish-docker-image:
if: github.repository_owner == 'gbdev'
runs-on: ubuntu-latest
permissions:
# So that the workflow can write to the ghcr an upload there
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
# When a commit is pushed to master
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
# When a tag is pushed
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

View File

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

View File

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

View File

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

View File

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

View File

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

1
.gitignore vendored
View File

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

View File

@@ -1,11 +1,14 @@
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
# 3.9 required for LTO checks # 3.9 required for LTO checks
cmake_minimum_required(VERSION 3.9 FATAL_ERROR) # 3.17 optional for CMAKE_CTEST_ARGUMENTS
cmake_minimum_required(VERSION 3.9..3.17 FATAL_ERROR)
project(rgbds project(rgbds
LANGUAGES CXX) LANGUAGES CXX)
include(CTest)
# get real path of source and binary directories # get real path of source and binary directories
get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH) get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH)
get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH) get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH)
@@ -41,7 +44,6 @@ else()
# does not recognize this yet. # does not recognize this yet.
add_compile_options(-Wno-gnu-zero-variadic-macro-arguments) add_compile_options(-Wno-gnu-zero-variadic-macro-arguments)
endif() endif()
add_definitions(-D_POSIX_C_SOURCE=200809L)
if(SANITIZERS) if(SANITIZERS)
set(SAN_FLAGS -fsanitize=shift -fsanitize=integer-divide-by-zero set(SAN_FLAGS -fsanitize=shift -fsanitize=integer-divide-by-zero
-fsanitize=unreachable -fsanitize=vla-bound -fsanitize=unreachable -fsanitize=vla-bound
@@ -88,6 +90,8 @@ endif(GIT)
find_package(PkgConfig) find_package(PkgConfig)
if(MSVC OR NOT PKG_CONFIG_FOUND) if(MSVC OR NOT PKG_CONFIG_FOUND)
# fallback to find_package # fallback to find_package
# cmake's FindPNG is very fragile; it breaks when multiple versions are installed
# this is most evident on macOS but can occur on Linux too
find_package(PNG REQUIRED) find_package(PNG REQUIRED)
else() else()
pkg_check_modules(LIBPNG REQUIRED libpng) pkg_check_modules(LIBPNG REQUIRED libpng)
@@ -99,6 +103,7 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True) set(CMAKE_CXX_STANDARD_REQUIRED True)
add_subdirectory(src) add_subdirectory(src)
set(CMAKE_CTEST_ARGUMENTS "--verbose")
add_subdirectory(test) add_subdirectory(test)
# By default, build in Release mode; Debug mode must be explicitly requested # By default, build in Release mode; Debug mode must be explicitly requested

View File

@@ -1,6 +1,6 @@
FROM debian:11-slim FROM debian:11-slim
LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds
ARG version=0.9.0-rc1 ARG version=0.9.0-rc2
WORKDIR /rgbds WORKDIR /rgbds
COPY . . COPY . .

View File

@@ -26,19 +26,16 @@ PNGLDLIBS := `${PKG_CONFIG} --libs-only-l libpng`
# Note: if this comes up empty, `version.cpp` will automatically fall back to last release number # Note: if this comes up empty, `version.cpp` will automatically fall back to last release number
VERSION_STRING := `git --git-dir=.git describe --tags --dirty --always 2>/dev/null` VERSION_STRING := `git --git-dir=.git describe --tags --dirty --always 2>/dev/null`
WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option \ WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option -Wno-gnu-zero-variadic-macro-arguments
-Wno-gnu-zero-variadic-macro-arguments
# Overridable CXXFLAGS # Overridable CXXFLAGS
CXXFLAGS ?= -O3 -flto -DNDEBUG CXXFLAGS ?= -O3 -flto -DNDEBUG
# Non-overridable CXXFLAGS # Non-overridable CXXFLAGS
REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++2a -I include \ REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++2a -I include -fno-exceptions -fno-rtti
-D_POSIX_C_SOURCE=200809L -fno-exceptions -fno-rtti
# Overridable LDFLAGS # Overridable LDFLAGS
LDFLAGS ?= LDFLAGS ?=
# Non-overridable LDFLAGS # Non-overridable LDFLAGS
REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} \ REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} -DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
-DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
# Wrapper around bison that passes flags depending on what the version supports # Wrapper around bison that passes flags depending on what the version supports
BISON := src/bison.sh BISON := src/bison.sh

View File

@@ -3,13 +3,14 @@
This describes for the maintainers of RGBDS how to publish a new release on This describes for the maintainers of RGBDS how to publish a new release on
GitHub. GitHub.
1. Update, commit, and push [include/version.hpp](include/version.hpp) with 1. Update the following files, then commit and push.
values for `PACKAGE_VERSION_MAJOR`, `PACKAGE_VERSION_MINOR`, You can use <code>git commit -m "Release <i>&lt;version&gt;</i>"</code> and `git push origin master`.
`PACKAGE_VERSION_PATCH`, and `PACKAGE_VERSION_RC`, as well as
[Dockerfile](Dockerfile) with a value for `ARG version`. Only define - [include/version.hpp](include/version.hpp): set appropriate values for `PACKAGE_VERSION_MAJOR`, `PACKAGE_VERSION_MINOR`, `PACKAGE_VERSION_PATCH`, and `PACKAGE_VERSION_RC`
`PACKAGE_VERSION_RC` if you are publishing a release candidate! You can **Only** define `PACKAGE_VERSION_RC` if you are publishing a release candidate!
use <code>git commit -m "Release <i>&lt;version&gt;</i>"</code> and - [Dockerfile](Dockerfile): update `ARG version`.
`git push origin master`. - [test/fetch-test-deps.sh](test/fetch-test-deps.sh): update test dependency commits (preferably, use the latest available).
- [man/\*](man/): update dates and authors.
2. Create a Git tag formatted as <code>v<i>&lt;MAJOR&gt;</i>.<i>&lt;MINOR&gt;</i>.<i>&lt;PATCH&gt;</i></code>, 2. Create a Git tag formatted as <code>v<i>&lt;MAJOR&gt;</i>.<i>&lt;MINOR&gt;</i>.<i>&lt;PATCH&gt;</i></code>,
or <code>v<i>&lt;MAJOR&gt;</i>.<i>&lt;MINOR&gt;</i>.<i>&lt;PATCH&gt;</i>-rc<i>&lt;RC&gt;</i></code> or <code>v<i>&lt;MAJOR&gt;</i>.<i>&lt;MINOR&gt;</i>.<i>&lt;PATCH&gt;</i>-rc<i>&lt;RC&gt;</i></code>
@@ -38,7 +39,9 @@ GitHub.
4. GitHub Actions will run the [create-release-docs.yml](.github/workflows/create-release-docs.yml) 4. GitHub Actions will run the [create-release-docs.yml](.github/workflows/create-release-docs.yml)
workflow to add the release documentation to [rgbds-www](https://github.com/gbdev/rgbds-www). workflow to add the release documentation to [rgbds-www](https://github.com/gbdev/rgbds-www).
This is not done automatically for prereleases; if you want to do this manually, This is not done automatically for prereleases, since we do not normally publish documentation
for them. If you want to manually publish prerelease documentation, such as for an April Fools
joke prerelease,
1. Clone [rgbds-www](https://github.com/gbdev/rgbds-www). You can use 1. Clone [rgbds-www](https://github.com/gbdev/rgbds-www). You can use
`git clone https://github.com/gbdev/rgbds-www.git`. `git clone https://github.com/gbdev/rgbds-www.git`.

View File

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

View File

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

View File

@@ -70,7 +70,8 @@ void sect_SetLoadSection(
SectionSpec const &attrs, SectionSpec const &attrs,
SectionModifier mod SectionModifier mod
); );
void sect_EndLoadSection(); void sect_EndLoadSection(char const *cause);
void sect_CheckLoadClosed();
Section *sect_GetSymbolSection(); Section *sect_GetSymbolSection();
uint32_t sect_GetSymbolOffset(); uint32_t sect_GetSymbolOffset();

View File

@@ -5,31 +5,29 @@
extern unsigned int nbErrors, maxErrors; extern unsigned int nbErrors, maxErrors;
enum WarningState { WARNING_DEFAULT, WARNING_DISABLED, WARNING_ENABLED, WARNING_ERROR };
enum WarningID { enum WarningID {
WARNING_ASSERT, // Assertions WARNING_ASSERT, // Assertions
WARNING_BACKWARDS_FOR, // `for` loop with backwards range WARNING_BACKWARDS_FOR, // `FOR` loop with backwards range
WARNING_BUILTIN_ARG, // Invalid args to builtins WARNING_BUILTIN_ARG, // Invalid args to builtins
WARNING_CHARMAP_REDEF, // Charmap entry re-definition WARNING_CHARMAP_REDEF, // Charmap entry re-definition
WARNING_DIV, // Division undefined behavior WARNING_DIV, // Undefined division behavior
WARNING_EMPTY_DATA_DIRECTIVE, // `db`, `dw` or `dl` directive without data in ROM WARNING_EMPTY_DATA_DIRECTIVE, // `db`, `dw` or `dl` directive without data in ROM
WARNING_EMPTY_MACRO_ARG, // Empty macro argument WARNING_EMPTY_MACRO_ARG, // Empty macro argument
WARNING_EMPTY_STRRPL, // Empty second argument in `STRRPL` WARNING_EMPTY_STRRPL, // Empty second argument in `STRRPL`
WARNING_LARGE_CONSTANT, // Constants too large WARNING_LARGE_CONSTANT, // Constants too large
WARNING_MACRO_SHIFT, // Shift past available arguments in macro WARNING_MACRO_SHIFT, // `SHIFT` past available arguments in macro
WARNING_NESTED_COMMENT, // Comment-start delimiter in a block comment WARNING_NESTED_COMMENT, // Comment-start delimiter in a block comment
WARNING_OBSOLETE, // Obsolete things WARNING_OBSOLETE, // Obsolete/deprecated things
WARNING_SHIFT, // Shifting undefined behavior WARNING_SHIFT, // Undefined `SHIFT` behavior
WARNING_SHIFT_AMOUNT, // Strange shift amount WARNING_SHIFT_AMOUNT, // Strange `SHIFT` amount
WARNING_USER, // User warnings WARNING_UNTERMINATED_LOAD, // `LOAD` without `ENDL`
WARNING_USER, // User-defined `WARN`ings
NB_PLAIN_WARNINGS, NB_PLAIN_WARNINGS,
// Warnings past this point are "parametric" warnings, only mapping to a single flag // Warnings past this point are "parametric" warnings, only mapping to a single flag
#define PARAM_WARNINGS_START NB_PLAIN_WARNINGS
// Treating string as number may lose some bits // Treating string as number may lose some bits
WARNING_NUMERIC_STRING_1 = PARAM_WARNINGS_START, WARNING_NUMERIC_STRING_1 = NB_PLAIN_WARNINGS,
WARNING_NUMERIC_STRING_2, WARNING_NUMERIC_STRING_2,
// Purging an exported symbol or label // Purging an exported symbol or label
WARNING_PURGE_1, WARNING_PURGE_1,
@@ -41,20 +39,24 @@ enum WarningID {
WARNING_UNMAPPED_CHAR_1, WARNING_UNMAPPED_CHAR_1,
WARNING_UNMAPPED_CHAR_2, WARNING_UNMAPPED_CHAR_2,
NB_PLAIN_AND_PARAM_WARNINGS,
#define NB_PARAM_WARNINGS (NB_PLAIN_AND_PARAM_WARNINGS - PARAM_WARNINGS_START)
// Warnings past this point are "meta" warnings
#define META_WARNINGS_START NB_PLAIN_AND_PARAM_WARNINGS
WARNING_ALL = META_WARNINGS_START,
WARNING_EXTRA,
WARNING_EVERYTHING,
NB_WARNINGS, NB_WARNINGS,
#define NB_META_WARNINGS (NB_WARNINGS - META_WARNINGS_START)
}; };
extern WarningState warningStates[NB_PLAIN_AND_PARAM_WARNINGS]; enum WarningAbled { WARNING_DEFAULT, WARNING_ENABLED, WARNING_DISABLED };
struct WarningState {
WarningAbled state;
WarningAbled error;
void update(WarningState other);
};
struct Diagnostics {
WarningState flagStates[NB_WARNINGS];
WarningState metaStates[NB_WARNINGS];
};
extern Diagnostics warningStates;
extern bool warningsAreErrors; extern bool warningsAreErrors;
void processWarningFlag(char const *flag); void processWarningFlag(char const *flag);

View File

@@ -107,20 +107,18 @@ struct Palette {
uint8_t size() const; uint8_t size() const;
}; };
namespace detail { // Flipping tends to happen fairly often, so take a bite out of dcache to speed it up
template<typename T, T... i> static constexpr auto flipTable = ([]() constexpr {
static constexpr auto flipTable(std::integer_sequence<T, i...>) { std::array<uint16_t, 256> table{};
return std::array{[](uint8_t byte) { for (uint16_t i = 0; i < table.size(); i++) {
// To flip all the bits, we'll flip both nibbles, then each nibble half, etc. // To flip all the bits, we'll flip both nibbles, then each nibble half, etc.
uint16_t byte = i;
byte = (byte & 0b0000'1111) << 4 | (byte & 0b1111'0000) >> 4; byte = (byte & 0b0000'1111) << 4 | (byte & 0b1111'0000) >> 4;
byte = (byte & 0b0011'0011) << 2 | (byte & 0b1100'1100) >> 2; byte = (byte & 0b0011'0011) << 2 | (byte & 0b1100'1100) >> 2;
byte = (byte & 0b0101'0101) << 1 | (byte & 0b1010'1010) >> 1; byte = (byte & 0b0101'0101) << 1 | (byte & 0b1010'1010) >> 1;
return byte; table[i] = byte;
}(i)...}; }
} return table;
} // namespace detail })();
// Flipping tends to happen fairly often, so take a bite out of dcache to speed it up
static constexpr auto flipTable = detail::flipTable(std::make_integer_sequence<uint16_t, 256>());
#endif // RGBDS_GFX_MAIN_HPP #endif // RGBDS_GFX_MAIN_HPP

View File

@@ -6,19 +6,15 @@
#include <tuple> #include <tuple>
#include <vector> #include <vector>
#include "defaultinitalloc.hpp" #include "defaultinitvec.hpp"
struct Palette; struct Palette;
class ProtoPalette; class ProtoPalette;
namespace packing {
/* /*
* Returns which palette each proto-palette maps to, and how many palettes are necessary * Returns which palette each proto-palette maps to, and how many palettes are necessary
*/ */
std::tuple<DefaultInitVec<size_t>, size_t> std::tuple<DefaultInitVec<size_t>, size_t>
overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes); overloadAndRemove(std::vector<ProtoPalette> const &protoPalettes);
} // namespace packing
#endif // RGBDS_GFX_PAL_PACKING_HPP #endif // RGBDS_GFX_PAL_PACKING_HPP

View File

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

View File

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

View File

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

View File

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

View File

@@ -56,4 +56,9 @@
#define setmode(fd, mode) (0) #define setmode(fd, mode) (0)
#endif #endif
// MingGW and Cygwin need POSIX functions which are not standard C explicitly enabled
#if defined(__MINGW32__) || defined(__CYGWIN__)
#define _POSIX_C_SOURCE 200809L
#endif
#endif // RGBDS_PLATFORM_HPP #endif // RGBDS_PLATFORM_HPP

View File

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

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd September 18, 2024 .Dd October 21, 2024
.Dt GBZ80 7 .Dt GBZ80 7
.Os .Os
.Sh NAME .Sh NAME

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd September 18, 2024 .Dd October 21, 2024
.Dt RGBASM 1 .Dt RGBASM 1
.Os .Os
.Sh NAME .Sh NAME
@@ -212,13 +212,19 @@ The following options alter the way warnings are processed.
.Bl -tag -width Ds .Bl -tag -width Ds
.It Fl Werror .It Fl Werror
Make all warnings into errors. Make all warnings into errors.
This can be negated as
.Fl Wno-error
to prevent turning all warnings into errors.
.It Fl Werror= .It Fl Werror=
Make the specified warning into an error. Make the specified warning or meta warning into an error.
A warning's name is appended A warning's name is appended
.Pq example: Fl Werror=obsolete , .Pq example: Fl Werror=obsolete ,
and this warning is implicitly enabled and turned into an error. and this warning is implicitly enabled and turned into an error.
This is an error if used with a meta warning, such as This can be negated as
.Fl Werror=all . .Fl Wno-error=
to prevent turning a specified warning into an error, even if
.Fl Werror
is in effect.
.El .El
.Pp .Pp
The following warnings are The following warnings are
@@ -240,6 +246,10 @@ Note that each of these flag also has a negation (for example,
.Fl Wcharmap-redef .Fl Wcharmap-redef
enables the warning that enables the warning that
.Fl Wno-charmap-redef .Fl Wno-charmap-redef
disables; and
.Fl Wall
enables every warning that
.Fl Wno-all
disables). disables).
Only the non-default flag is listed here. Only the non-default flag is listed here.
Ignoring the Ignoring the
@@ -291,6 +301,13 @@ This warning is enabled by
Warn when shifting macro arguments past their limits. Warn when shifting macro arguments past their limits.
This warning is enabled by This warning is enabled by
.Fl Wextra . .Fl Wextra .
.It Fl Wno-nested-comment
Warn when the block comment start sequence
.Ql /*
is found inside of a block comment.
Block comments cannot be nested, so the first
.Ql */
will end the whole comment.
.It Fl Wno-obsolete .It Fl Wno-obsolete
Warn when obsolete constructs such as the Warn when obsolete constructs such as the
.Ic _PI .Ic _PI
@@ -344,7 +361,7 @@ also warns when an N-bit value is less than -2**(N-1), which will not fit in two
Warn when a character goes through charmap conversion but has no defined mapping. Warn when a character goes through charmap conversion but has no defined mapping.
.Fl Wunmapped-char=0 .Fl Wunmapped-char=0
or or
.Fl Wunmapped-char .Fl Wno-unmapped-char
disables this warning. disables this warning.
.Fl Wunmapped-char=1 .Fl Wunmapped-char=1
or just or just
@@ -353,6 +370,13 @@ only warns if the active charmap is not empty.
.Fl Wunmapped-char=2 .Fl Wunmapped-char=2
warns if the active charmap is empty, and/or is not the default charmap warns if the active charmap is empty, and/or is not the default charmap
.Sq main . .Sq main .
.It Fl Wunterminated-load
Warn when a
.Ic LOAD
block is not terminated by an
.Ic ENDL .
This warning is enabled by
.Fl Wextra .
.It Fl Wno-user .It Fl Wno-user
Warn when the Warn when the
.Ic WARN .Ic WARN

View File

@@ -2,7 +2,7 @@
.\" .\"
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd September 18, 2024 .Dd October 21, 2024
.Dt RGBASM 5 .Dt RGBASM 5
.Os .Os
.Sh NAME .Sh NAME
@@ -41,7 +41,6 @@ or
Labels tie a name to a specific location within a section (see Labels tie a name to a specific location within a section (see
.Sx Labels .Sx Labels
below). below).
They must come first in the line.
.Pp .Pp
Instructions are assembled into Game Boy opcodes. Instructions are assembled into Game Boy opcodes.
Multiple instructions on one line can be separated by double colons Multiple instructions on one line can be separated by double colons
@@ -273,16 +272,16 @@ further below.
The instructions in the macro-language generally require constant expressions. The instructions in the macro-language generally require constant expressions.
.Ss Numeric formats .Ss Numeric formats
There are a number of numeric formats. There are a number of numeric formats.
.Bl -column -offset indent "Precise fixed-point" "Prefix" .Bl -column -offset indent "Precise fixed-point" "Possible prefixes"
.It Sy Format type Ta Sy Prefix Ta Sy Accepted characters .It Sy Format type Ta Sy Possible prefixes Ta Sy Accepted characters
.It Hexadecimal Ta $ Ta 0123456789ABCDEF
.It Decimal Ta none Ta 0123456789 .It Decimal Ta none Ta 0123456789
.It Octal Ta & Ta 01234567 .It Hexadecimal Ta Li $ , 0x , 0X Ta 0123456789ABCDEF
.It Binary Ta % Ta 01 .It Octal Ta Li & , 0o , 0O Ta 01234567
.It Binary Ta Li % , 0b , 0B Ta 01
.It Fixed-point Ta none Ta 01234.56789 .It Fixed-point Ta none Ta 01234.56789
.It Precise fixed-point Ta none Ta 12.34q8 .It Precise fixed-point Ta none Ta 12.34q8
.It Character constant Ta none Ta \(dqABYZ\(dq .It Character constant Ta none Ta \(dqABYZ\(dq
.It Game Boy graphics Ta \` Ta 0123 .It Game Boy graphics Ta Li \` Ta 0123
.El .El
.Pp .Pp
Underscores are also accepted in numbers, except at the beginning of one. Underscores are also accepted in numbers, except at the beginning of one.
@@ -379,8 +378,8 @@ Even a non-constant operand with any non-zero bits will return 0.
Besides operators, there are also some functions which have more specialized uses. Besides operators, there are also some functions which have more specialized uses.
.Bl -column "BITWIDTH(n)" .Bl -column "BITWIDTH(n)"
.It Sy Name Ta Sy Operation .It Sy Name Ta Sy Operation
.It Fn HIGH n Ta Equivalent to Ql Ar n No & $FF . .It Fn HIGH n Ta Equivalent to Ql Po Ns Ar n No & $FF00 Pc >> 8 .
.It Fn LOW n Ta Equivalent to Ql Po Ns Ar n No & $FF00 Pc >> 8 . .It Fn LOW n Ta Equivalent to Ql Ar n No & $FF .
.EQ .EQ
delim $$ delim $$
.EN .EN
@@ -904,7 +903,7 @@ SECTION "LOAD example", ROMX
CopyCode: CopyCode:
ld de, RAMCode ld de, RAMCode
ld hl, RAMLocation ld hl, RAMLocation
ld c, RAMLocation.end - RAMLocation ld c, RAMCode.end - RAMCode
\&.loop \&.loop
ld a, [de] ld a, [de]
inc de inc de
@@ -928,8 +927,8 @@ RAMLocation:
\&.string \&.string
db "Hello World!\e0" db "Hello World!\e0"
\&.end
ENDL ENDL
\&.end
.Ed .Ed
.Pp .Pp
A A
@@ -939,7 +938,9 @@ block feels similar to a
declaration because it creates a new one. declaration because it creates a new one.
All data and code generated within such a block is placed in the current section like usual, but all labels are created as if they were placed in this newly-created section. All data and code generated within such a block is placed in the current section like usual, but all labels are created as if they were placed in this newly-created section.
.Pp .Pp
In the example above, all of the code and data will end up in the "LOAD example" section. In the example above, all of the code and data will end up in the
.Dq LOAD example
section.
You will notice the You will notice the
.Sq RAMCode .Sq RAMCode
and and
@@ -951,12 +952,25 @@ You cannot nest
.Ic LOAD .Ic LOAD
blocks, nor can you change or stop the current section within them. blocks, nor can you change or stop the current section within them.
.Pp .Pp
The current
.Ic LOAD
block can be ended by using
.Ic ENDL .
This directive is only necessary if you want to resume writing code in its containing ROM section.
Any of
.Ic LOAD , SECTION , ENDSECTION ,
or
.Ic POPS
will end the current
.Ic LOAD
block before performing its own function.
.Pp
.Ic LOAD .Ic LOAD
blocks can use the blocks can use the
.Ic UNION .Ic UNION
or modifier as described below, but not the
.Ic FRAGMENT .Ic FRAGMENT
modifiers, as described below. modifier.
.Ss Unionized sections .Ss Unionized sections
When you're tight on RAM, you may want to define overlapping static memory allocations, as explained in the When you're tight on RAM, you may want to define overlapping static memory allocations, as explained in the
.Sx Unions .Sx Unions
@@ -2283,9 +2297,9 @@ POPO
.Pp .Pp
.Ic OPT .Ic OPT
can modify the options can modify the options
.Cm b , g , p , Q , .Cm b , g , p , Q , r ,
and and
.Cm r . .Cm W .
.Pp .Pp
.Ic POPO .Ic POPO
and and

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd September 18, 2024 .Dd October 21, 2024
.Dt RGBDS 5 .Dt RGBDS 5
.Os .Os
.Sh NAME .Sh NAME

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
.\" .\"
.\" SPDX-License-Identifier: MIT .\" SPDX-License-Identifier: MIT
.\" .\"
.Dd September 18, 2024 .Dd October 21, 2024
.Dt RGBGFX 1 .Dt RGBGFX 1
.Os .Os
.Sh NAME .Sh NAME

View File

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

View File

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

View File

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

View File

@@ -186,7 +186,8 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
useExact = true; useExact = true;
} }
if (useType != 'X' && useType != 'x' && useType != 'b' && useType != 'o' && useType != 'f' && useExact) if (useType != 'X' && useType != 'x' && useType != 'b' && useType != 'o' && useType != 'f'
&& useExact)
error("Formatting type '%c' with exact flag '#'\n", useType); error("Formatting type '%c' with exact flag '#'\n", useType);
if (useType != 'f' && hasFrac) if (useType != 'f' && hasFrac)
error("Formatting type '%c' with fractional width\n", useType); error("Formatting type '%c' with fractional width\n", useType);

View File

@@ -797,11 +797,9 @@ static int peek() {
} else if (c == '{' && !lexerState->disableInterpolation) { } else if (c == '{' && !lexerState->disableInterpolation) {
// If character is an open brace, do symbol interpolation // If character is an open brace, do symbol interpolation
shiftChar(); shiftChar();
if (auto str = readInterpolation(0); str) { if (auto str = readInterpolation(0); str) {
beginExpansion(str, *str); beginExpansion(str, *str);
} }
return peek(); return peek();
} }
@@ -1201,9 +1199,9 @@ static std::shared_ptr<std::string> readInterpolation(size_t depth) {
if (c == '{') { // Nested interpolation if (c == '{') { // Nested interpolation
shiftChar(); shiftChar();
auto str = readInterpolation(depth + 1); if (auto str = readInterpolation(depth + 1); str) {
beginExpansion(str, *str);
beginExpansion(str, *str); }
continue; // Restart, reading from the new buffer continue; // Restart, reading from the new buffer
} else if (c == EOF || c == '\r' || c == '\n' || c == '"') { } else if (c == EOF || c == '\r' || c == '\n' || c == '"') {
error("Missing }\n"); error("Missing }\n");
@@ -1373,6 +1371,7 @@ static std::string readString(bool raw) {
// Line continuation // Line continuation
case ' ': case ' ':
case '\t':
case '\r': case '\r':
case '\n': case '\n':
discardLineContinuation(); discardLineContinuation();
@@ -1505,6 +1504,7 @@ static void appendStringLiteral(std::string &str, bool raw) {
// Line continuation // Line continuation
case ' ': case ' ':
case '\t':
case '\r': case '\r':
case '\n': case '\n':
discardLineContinuation(); discardLineContinuation();
@@ -1731,7 +1731,25 @@ static Token yylex_NORMAL() {
// Handle numbers // Handle numbers
case '0': // Decimal or fixed-point number case '0': // Decimal, fixed-point, or base-prefix number
switch (peek()) {
case 'x':
case 'X':
shiftChar();
return Token(T_(NUMBER), readHexNumber());
case 'o':
case 'O':
shiftChar();
return Token(T_(NUMBER), readNumber(8, 0));
case 'b':
case 'B':
shiftChar();
return Token(T_(NUMBER), readBinaryNumber());
}
[[fallthrough]];
// Decimal or fixed-point number
case '1': case '1':
case '2': case '2':
case '3': case '3':
@@ -1849,7 +1867,19 @@ static Token yylex_NORMAL() {
} }
} }
if (token.type == T_(ID) && (lexerState->atLineStart || peek() == ':')) // This is a "lexer hack"! We need it to distinguish between label definitions
// (which start with `LABEL`) and macro invocations (which start with `ID`).
//
// If we had one `IDENTIFIER` token, the parser would need to perform "lookahead"
// to determine which rule applies. But since macros need to enter "raw" mode to
// parse their arguments, which may not even be valid tokens in "normal" mode, we
// cannot use lookahead to check for the presence of a `COLON`.
//
// Instead, we have separate `ID` and `LABEL` tokens, lexing as a `LABEL` if a ':'
// character *immediately* follows the identifier. Thus, at the beginning of a line,
// "Label:" and "mac:" are treated as label definitions, but "Label :" and "mac :"
// are treated as macro invocations.
if (token.type == T_(ID) && peek() == ':')
token.type = T_(LABEL); token.type = T_(LABEL);
return token; return token;
@@ -1970,6 +2000,7 @@ backslash:
break; break;
case ' ': case ' ':
case '\t':
case '\r': case '\r':
case '\n': case '\n':
discardLineContinuation(); discardLineContinuation();

View File

@@ -293,7 +293,7 @@ int main(int argc, char *argv[]) {
break; break;
case 'W': case 'W':
processWarningFlag(musl_optarg); opt_W(musl_optarg);
break; break;
case 'w': case 'w':
@@ -381,6 +381,7 @@ int main(int argc, char *argv[]) {
nbErrors = 1; nbErrors = 1;
sect_CheckUnionClosed(); sect_CheckUnionClosed();
sect_CheckLoadClosed();
sect_CheckSizes(); sect_CheckSizes();
if (nbErrors != 0) if (nbErrors != 0)

View File

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

View File

@@ -12,6 +12,7 @@
#include "error.hpp" #include "error.hpp"
#include "helpers.hpp" // assume, Defer #include "helpers.hpp" // assume, Defer
#include "platform.hpp"
#include "asm/charmap.hpp" #include "asm/charmap.hpp"
#include "asm/fstack.hpp" #include "asm/fstack.hpp"
@@ -311,7 +312,8 @@ void out_WriteObject() {
file = fopen(objectFileName.c_str(), "wb"); file = fopen(objectFileName.c_str(), "wb");
} else { } else {
objectFileName = "<stdout>"; objectFileName = "<stdout>";
file = fdopen(STDOUT_FILENO, "wb"); (void)setmode(STDOUT_FILENO, O_BINARY);
file = stdout;
} }
if (!file) if (!file)
err("Failed to open object file '%s'", objectFileName.c_str()); err("Failed to open object file '%s'", objectFileName.c_str());
@@ -503,7 +505,8 @@ void out_WriteState(std::string name, std::vector<StateFeature> const &features)
file = fopen(name.c_str(), "wb"); file = fopen(name.c_str(), "wb");
} else { } else {
name = "<stdout>"; name = "<stdout>";
file = fdopen(STDOUT_FILENO, "wb"); (void)setmode(STDOUT_FILENO, O_BINARY);
file = stdout;
} }
if (!file) if (!file)
err("Failed to open state file '%s'", name.c_str()); err("Failed to open state file '%s'", name.c_str());

View File

@@ -103,7 +103,7 @@
/******************** Tokens ********************/ /******************** Tokens ********************/
%token YYEOF 0 "end of file" %token YYEOF 0 "end of file"
%token NEWLINE "newline" %token NEWLINE "end of line"
%token EOB "end of buffer" %token EOB "end of buffer"
// General punctuation // General punctuation
@@ -433,22 +433,6 @@ line:
fstk_StopRept(); fstk_StopRept();
yyerrok; yyerrok;
} }
// Hint about unindented macros parsed as labels
| LABEL error {
lexer_SetMode(LEXER_NORMAL);
lexer_ToggleStringExpansion(true);
} endofline {
Symbol *macro = sym_FindExactSymbol($1);
if (macro && macro->type == SYM_MACRO)
fprintf(
stderr,
" To invoke `%s` as a macro it must be indented\n",
$1.c_str()
);
fstk_StopRept();
yyerrok;
}
; ;
endofline: NEWLINE | EOB; endofline: NEWLINE | EOB;
@@ -865,7 +849,7 @@ load:
sect_SetLoadSection($3, $5, $6, $7, $2); sect_SetLoadSection($3, $5, $6, $7, $2);
} }
| POP_ENDL { | POP_ENDL {
sect_EndLoadSection(); sect_EndLoadSection(nullptr);
} }
; ;

View File

@@ -120,8 +120,8 @@ void Expression::makeBankSymbol(std::string const &symName) {
data = (int32_t)sym->getSection()->bank; data = (int32_t)sym->getSection()->bank;
} else { } else {
data = sym_IsPurgedScoped(symName) data = sym_IsPurgedScoped(symName)
? "\""s + symName + "\"'s bank is not known; it was purged" ? "\""s + symName + "\"'s bank is not known; it was purged"
: "\""s + symName + "\"'s bank is not known"; : "\""s + symName + "\"'s bank is not known";
size_t nameLen = sym->name.length() + 1; // Room for NUL! size_t nameLen = sym->name.length() + 1; // Room for NUL!

View File

@@ -425,14 +425,14 @@ void sect_NewSection(
SectionSpec const &attrs, SectionSpec const &attrs,
SectionModifier mod SectionModifier mod
) { ) {
if (currentLoadSection)
fatalerror("Cannot change the section within a `LOAD` block\n");
for (SectionStackEntry &entry : sectionStack) { for (SectionStackEntry &entry : sectionStack) {
if (entry.section && entry.section->name == name) if (entry.section && entry.section->name == name)
fatalerror("Section '%s' is already on the stack\n", name.c_str()); fatalerror("Section '%s' is already on the stack\n", name.c_str());
} }
if (currentLoadSection)
sect_EndLoadSection("SECTION");
Section *sect = getSection(name, type, org, attrs, mod); Section *sect = getSection(name, type, org, attrs, mod);
changeSection(); changeSection();
@@ -457,11 +457,6 @@ void sect_SetLoadSection(
if (!requireCodeSection()) if (!requireCodeSection())
return; return;
if (currentLoadSection) {
error("`LOAD` blocks cannot be nested\n");
return;
}
if (sect_HasData(type)) { if (sect_HasData(type)) {
error("`LOAD` blocks cannot create a ROM section\n"); error("`LOAD` blocks cannot create a ROM section\n");
return; return;
@@ -472,6 +467,9 @@ void sect_SetLoadSection(
return; return;
} }
if (currentLoadSection)
sect_EndLoadSection("LOAD");
Section *sect = getSection(name, type, org, attrs, mod); Section *sect = getSection(name, type, org, attrs, mod);
currentLoadLabelScopes = sym_GetCurrentLabelScopes(); currentLoadLabelScopes = sym_GetCurrentLabelScopes();
@@ -481,7 +479,14 @@ void sect_SetLoadSection(
currentLoadSection = sect; currentLoadSection = sect;
} }
void sect_EndLoadSection() { void sect_EndLoadSection(char const *cause) {
if (cause)
warning(
WARNING_UNTERMINATED_LOAD,
"`LOAD` block without `ENDL` terminated by `%s`\n",
cause
);
if (!currentLoadSection) { if (!currentLoadSection) {
error("Found `ENDL` outside of a `LOAD` block\n"); error("Found `ENDL` outside of a `LOAD` block\n");
return; return;
@@ -494,6 +499,11 @@ void sect_EndLoadSection() {
sym_SetCurrentLabelScopes(currentLoadLabelScopes); sym_SetCurrentLabelScopes(currentLoadLabelScopes);
} }
void sect_CheckLoadClosed() {
if (currentLoadSection)
warning(WARNING_UNTERMINATED_LOAD, "`LOAD` block without `ENDL` terminated by EOF\n");
}
Section *sect_GetSymbolSection() { Section *sect_GetSymbolSection() {
return currentLoadSection ? currentLoadSection : currentSection; return currentLoadSection ? currentLoadSection : currentSection;
} }
@@ -954,7 +964,7 @@ void sect_PopSection() {
fatalerror("No entries in the section stack\n"); fatalerror("No entries in the section stack\n");
if (currentLoadSection) if (currentLoadSection)
fatalerror("Cannot change the section within a `LOAD` block\n"); sect_EndLoadSection("POPS");
SectionStackEntry entry = sectionStack.front(); SectionStackEntry entry = sectionStack.front();
sectionStack.pop_front(); sectionStack.pop_front();
@@ -972,12 +982,12 @@ void sect_EndSection() {
if (!currentSection) if (!currentSection)
fatalerror("Cannot end the section outside of a SECTION\n"); fatalerror("Cannot end the section outside of a SECTION\n");
if (currentLoadSection)
fatalerror("Cannot end the section within a `LOAD` block\n");
if (!currentUnionStack.empty()) if (!currentUnionStack.empty())
fatalerror("Cannot end the section within a UNION\n"); fatalerror("Cannot end the section within a UNION\n");
if (currentLoadSection)
sect_EndLoadSection("ENDSECTION");
// Reset the section scope // Reset the section scope
currentSection = nullptr; currentSection = nullptr;
sym_ResetCurrentLabelScopes(); sym_ResetCurrentLabelScopes();

View File

@@ -23,7 +23,7 @@ std::unordered_map<std::string, Symbol> symbols;
std::unordered_set<std::string> purgedSymbols; std::unordered_set<std::string> purgedSymbols;
static Symbol const *globalScope = nullptr; // Current section's global label scope 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 *PCSymbol;
static Symbol *NARGSymbol; static Symbol *NARGSymbol;
static Symbol *globalScopeSymbol; static Symbol *globalScopeSymbol;

View File

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

View File

@@ -1160,9 +1160,9 @@ static bool processFilename(char const *name) {
nbErrors = 0; nbErrors = 0;
if (!strcmp(name, "-")) { if (!strcmp(name, "-")) {
name = "<stdin>";
(void)setmode(STDIN_FILENO, O_BINARY); (void)setmode(STDIN_FILENO, O_BINARY);
(void)setmode(STDOUT_FILENO, O_BINARY); (void)setmode(STDOUT_FILENO, O_BINARY);
name = "<stdin>";
processFile(STDIN_FILENO, STDOUT_FILENO, name, 0); processFile(STDIN_FILENO, STDOUT_FILENO, name, 0);
} else { } else {
// POSIX specifies that the results of O_RDWR on a FIFO are undefined. // POSIX specifies that the results of O_RDWR on a FIFO are undefined.
@@ -1435,7 +1435,8 @@ int main(int argc, char *argv[]) {
logoFile = fopen(logoFilename, "rb"); logoFile = fopen(logoFilename, "rb");
} else { } else {
logoFilename = "<stdin>"; logoFilename = "<stdin>";
logoFile = fdopen(STDIN_FILENO, "rb"); (void)setmode(STDIN_FILENO, O_BINARY);
logoFile = stdin;
} }
if (!logoFile) { if (!logoFile) {
fprintf( fprintf(

View File

@@ -15,10 +15,6 @@
#include "gfx/main.hpp" #include "gfx/main.hpp"
#include "gfx/proto_palette.hpp" #include "gfx/proto_palette.hpp"
using std::swap;
namespace packing {
// The solvers here are picked from the paper at https://arxiv.org/abs/1605.00558: // The solvers here are picked from the paper at https://arxiv.org/abs/1605.00558:
// "Algorithms for the Pagination Problem, a Bin Packing with Overlapping Items" // "Algorithms for the Pagination Problem, a Bin Packing with Overlapping Items"
// Their formulation of the problem consists in packing "tiles" into "pages"; here is a // Their formulation of the problem consists in packing "tiles" into "pages"; here is a
@@ -114,8 +110,8 @@ private:
} }
friend void swap(Iter &lhs, Iter &rhs) { friend void swap(Iter &lhs, Iter &rhs) {
swap(lhs._array, rhs._array); std::swap(lhs._array, rhs._array);
swap(lhs._iter, rhs._iter); std::swap(lhs._iter, rhs._iter);
} }
}; };
public: public:
@@ -537,5 +533,3 @@ std::tuple<DefaultInitVec<size_t>, size_t>
} }
return {mappings, assignments.size()}; return {mappings, assignments.size()};
} }
} // namespace packing

View File

@@ -8,9 +8,7 @@
#include "gfx/main.hpp" #include "gfx/main.hpp"
namespace sorting { void sortIndexed(
void indexed(
std::vector<Palette> &palettes, std::vector<Palette> &palettes,
int palSize, int palSize,
png_color const *palRGB, png_color const *palRGB,
@@ -47,7 +45,7 @@ void indexed(
} }
} }
void grayscale( void sortGrayscale(
std::vector<Palette> &palettes, std::array<std::optional<Rgba>, 0x8001> const &colors std::vector<Palette> &palettes, std::array<std::optional<Rgba>, 0x8001> const &colors
) { ) {
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting grayscale-only palette...\n"); options.verbosePrint(Options::VERB_LOG_ACT, "Sorting grayscale-only palette...\n");
@@ -73,7 +71,7 @@ static unsigned int legacyLuminance(uint16_t color) {
return 2126 * red + 7152 * green + 722 * blue; return 2126 * red + 7152 * green + 722 * blue;
} }
void rgb(std::vector<Palette> &palettes) { void sortRgb(std::vector<Palette> &palettes) {
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palettes by \"\"\"luminance\"\"\"...\n"); options.verbosePrint(Options::VERB_LOG_ACT, "Sorting palettes by \"\"\"luminance\"\"\"...\n");
for (Palette &pal : palettes) { for (Palette &pal : palettes) {
@@ -82,5 +80,3 @@ void rgb(std::vector<Palette> &palettes) {
}); });
} }
} }
} // namespace sorting

View File

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

View File

@@ -15,7 +15,7 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "defaultinitalloc.hpp" #include "defaultinitvec.hpp"
#include "file.hpp" #include "file.hpp"
#include "helpers.hpp" #include "helpers.hpp"
#include "itertools.hpp" #include "itertools.hpp"
@@ -446,7 +446,7 @@ public:
}; };
private: private:
struct iterator { struct Iterator {
TilesVisitor const &parent; TilesVisitor const &parent;
uint32_t const limit; uint32_t const limit;
uint32_t x, y; uint32_t x, y;
@@ -458,7 +458,7 @@ public:
return {parent._png, x + options.inputSlice.left, y + options.inputSlice.top}; return {parent._png, x + options.inputSlice.left, y + options.inputSlice.top};
} }
iterator &operator++() { Iterator &operator++() {
auto [major, minor] = parent._columnMajor ? std::tie(y, x) : std::tie(x, y); auto [major, minor] = parent._columnMajor ? std::tie(y, x) : std::tie(x, y);
major += 8; major += 8;
if (major == limit) { if (major == limit) {
@@ -468,19 +468,14 @@ public:
return *this; return *this;
} }
friend bool operator==(iterator const &lhs, iterator const &rhs) { bool operator==(Iterator const &rhs) const { return coords() == rhs.coords(); }
return lhs.coords() == rhs.coords(); // Compare the returned coord pairs bool operator!=(Iterator const &rhs) const { return coords() != rhs.coords(); }
}
friend bool operator!=(iterator const &lhs, iterator const &rhs) {
return lhs.coords() != rhs.coords(); // Compare the returned coord pairs
}
}; };
public: public:
iterator begin() const { return {*this, _limit, 0, 0}; } Iterator begin() const { return {*this, _limit, 0, 0}; }
iterator end() const { Iterator end() const {
iterator it{*this, _limit, _width - 8, _height - 8}; // Last valid one... Iterator it{*this, _limit, _width - 8, _height - 8}; // Last valid one...
return ++it; // ...now one-past-last! return ++it; // ...now one-past-last!
} }
}; };
@@ -567,7 +562,7 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
generatePalettes(std::vector<ProtoPalette> const &protoPalettes, Png const &png) { generatePalettes(std::vector<ProtoPalette> const &protoPalettes, Png const &png) {
// Run a "pagination" problem solver // Run a "pagination" problem solver
// TODO: allow picking one of several solvers? // TODO: allow picking one of several solvers?
auto [mappings, nbPalettes] = packing::overloadAndRemove(protoPalettes); auto [mappings, nbPalettes] = overloadAndRemove(protoPalettes);
assume(mappings.size() == protoPalettes.size()); assume(mappings.size() == protoPalettes.size());
if (options.verbosity >= Options::VERB_INTERM) { if (options.verbosity >= Options::VERB_INTERM) {
@@ -601,11 +596,11 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
// "Sort" colors in the generated palettes, see the man page for the flowchart // "Sort" colors in the generated palettes, see the man page for the flowchart
auto [embPalSize, embPalRGB, embPalAlphaSize, embPalAlpha] = png.getEmbeddedPal(); auto [embPalSize, embPalRGB, embPalAlphaSize, embPalAlpha] = png.getEmbeddedPal();
if (embPalRGB != nullptr) { if (embPalRGB != nullptr) {
sorting::indexed(palettes, embPalSize, embPalRGB, embPalAlphaSize, embPalAlpha); sortIndexed(palettes, embPalSize, embPalRGB, embPalAlphaSize, embPalAlpha);
} else if (png.isSuitableForGrayscale()) { } else if (png.isSuitableForGrayscale()) {
sorting::grayscale(palettes, png.getColors().raw()); sortGrayscale(palettes, png.getColors().raw());
} else { } else {
sorting::rgb(palettes); sortRgb(palettes);
} }
return {mappings, palettes}; return {mappings, palettes};
} }
@@ -826,9 +821,7 @@ public:
return MatchType::NOPE; return MatchType::NOPE;
} }
friend bool operator==(TileData const &lhs, TileData const &rhs) { bool operator==(TileData const &rhs) const { return tryMatching(rhs) != MatchType::NOPE; }
return lhs.tryMatching(rhs) != MatchType::NOPE;
}
}; };
template<> template<>
@@ -836,9 +829,7 @@ struct std::hash<TileData> {
std::size_t operator()(TileData const &tile) const { return tile.hash(); } std::size_t operator()(TileData const &tile) const { return tile.hash(); }
}; };
namespace unoptimized { static void outputUnoptimizedTileData(
static void outputTileData(
Png const &png, Png const &png,
DefaultInitVec<AttrmapEntry> const &attrmap, DefaultInitVec<AttrmapEntry> const &attrmap,
std::vector<Palette> const &palettes, std::vector<Palette> const &palettes,
@@ -877,7 +868,7 @@ static void outputTileData(
assume(remainingTiles == 0); assume(remainingTiles == 0);
} }
static void outputMaps( static void outputUnoptimizedMaps(
DefaultInitVec<AttrmapEntry> const &attrmap, DefaultInitVec<size_t> const &mappings DefaultInitVec<AttrmapEntry> const &attrmap, DefaultInitVec<size_t> const &mappings
) { ) {
std::optional<File> tilemapOutput, attrmapOutput, palmapOutput; std::optional<File> tilemapOutput, attrmapOutput, palmapOutput;
@@ -916,10 +907,6 @@ static void outputMaps(
} }
} }
} // namespace unoptimized
namespace optimized {
struct UniqueTiles { struct UniqueTiles {
std::unordered_set<TileData> tileset; std::unordered_set<TileData> tileset;
std::vector<TileData const *> tiles; std::vector<TileData const *> tiles;
@@ -1089,8 +1076,6 @@ static void outputPalmap(
} }
} }
} // namespace optimized
void processPalettes() { void processPalettes() {
options.verbosePrint(Options::VERB_CFG, "Using libpng %s\n", png_get_libpng_ver(nullptr)); options.verbosePrint(Options::VERB_CFG, "Using libpng %s\n", png_get_libpng_ver(nullptr));
@@ -1130,33 +1115,44 @@ void process() {
DefaultInitVec<AttrmapEntry> attrmap{}; DefaultInitVec<AttrmapEntry> attrmap{};
for (auto tile : png.visitAsTiles()) { for (auto tile : png.visitAsTiles()) {
ProtoPalette tileColors;
AttrmapEntry &attrs = attrmap.emplace_back(); AttrmapEntry &attrs = attrmap.emplace_back();
uint8_t nbColorsInTile = 0;
// Count the unique non-transparent colors for packing
std::unordered_set<uint16_t> tileColors;
for (uint32_t y = 0; y < 8; ++y) { for (uint32_t y = 0; y < 8; ++y) {
for (uint32_t x = 0; x < 8; ++x) { for (uint32_t x = 0; x < 8; ++x) {
Rgba color = tile.pixel(x, y); if (Rgba color = tile.pixel(x, y); !color.isTransparent()) {
if (!color.isTransparent()) { // Do not count transparency in for packing tileColors.insert(color.cgbColor());
// Add the color to the proto-pal (if not full), and count it if it was unique.
if (tileColors.add(color.cgbColor())) {
++nbColorsInTile;
}
} }
} }
} }
if (tileColors.size() > options.maxOpaqueColors()) {
fatal(
"Tile at (%" PRIu32 ", %" PRIu32 ") has %zu colors, more than %" PRIu8 "!",
tile.x,
tile.y,
tileColors.size(),
options.maxOpaqueColors()
);
}
if (tileColors.empty()) { if (tileColors.empty()) {
// "Empty" proto-palettes screw with the packing process, so discard those // "Empty" proto-palettes screw with the packing process, so discard those
attrs.protoPaletteID = AttrmapEntry::transparent; attrs.protoPaletteID = AttrmapEntry::transparent;
continue; continue;
} }
ProtoPalette protoPalette;
for (uint16_t cgbColor : tileColors) {
protoPalette.add(cgbColor);
}
// Insert the proto-palette, making sure to avoid overlaps // Insert the proto-palette, making sure to avoid overlaps
for (size_t n = 0; n < protoPalettes.size(); ++n) { for (size_t n = 0; n < protoPalettes.size(); ++n) {
switch (tileColors.compare(protoPalettes[n])) { switch (protoPalette.compare(protoPalettes[n])) {
case ProtoPalette::WE_BIGGER: case ProtoPalette::WE_BIGGER:
protoPalettes[n] = tileColors; // Override them protoPalettes[n] = protoPalette; // Override them
// Remove any other proto-palettes that we encompass // Remove any other proto-palettes that we encompass
// (Example [(0, 1), (0, 2)], inserting (0, 1, 2)) // (Example [(0, 1), (0, 2)], inserting (0, 1, 2))
/* /*
@@ -1166,7 +1162,7 @@ void process() {
* Investigation is necessary, especially if pathological cases are found. * Investigation is necessary, especially if pathological cases are found.
* *
* for (size_t i = protoPalettes.size(); --i != n;) { * for (size_t i = protoPalettes.size(); --i != n;) {
* if (tileColors.compare(protoPalettes[i]) == ProtoPalette::WE_BIGGER) { * if (protoPalette.compare(protoPalettes[i]) == ProtoPalette::WE_BIGGER) {
* protoPalettes.erase(protoPalettes.begin() + i); * protoPalettes.erase(protoPalettes.begin() + i);
* } * }
* } * }
@@ -1183,17 +1179,6 @@ void process() {
} }
} }
if (nbColorsInTile > options.maxOpaqueColors()) {
fatal(
"Tile at (%" PRIu32 ", %" PRIu32 ") has %" PRIu8 " opaque colors, more than %" PRIu8
"!",
tile.x,
tile.y,
nbColorsInTile,
options.maxOpaqueColors()
);
}
attrs.protoPaletteID = protoPalettes.size(); attrs.protoPaletteID = protoPalettes.size();
if (protoPalettes.size() == AttrmapEntry::transparent) { // Check for overflow if (protoPalettes.size() == AttrmapEntry::transparent) { // Check for overflow
fatal( fatal(
@@ -1201,7 +1186,7 @@ void process() {
AttrmapEntry::transparent AttrmapEntry::transparent
); );
} }
protoPalettes.push_back(tileColors); protoPalettes.push_back(protoPalette);
continue_visiting_tiles:; continue_visiting_tiles:;
} }
@@ -1251,7 +1236,7 @@ continue_visiting_tiles:;
if (!options.output.empty()) { if (!options.output.empty()) {
options.verbosePrint(Options::VERB_LOG_ACT, "Generating unoptimized tile data...\n"); options.verbosePrint(Options::VERB_LOG_ACT, "Generating unoptimized tile data...\n");
unoptimized::outputTileData(png, attrmap, palettes, mappings); outputUnoptimizedTileData(png, attrmap, palettes, mappings);
} }
if (!options.tilemap.empty() || !options.attrmap.empty() || !options.palmap.empty()) { if (!options.tilemap.empty() || !options.attrmap.empty() || !options.palmap.empty()) {
@@ -1259,12 +1244,12 @@ continue_visiting_tiles:;
Options::VERB_LOG_ACT, Options::VERB_LOG_ACT,
"Generating unoptimized tilemap and/or attrmap and/or palmap...\n" "Generating unoptimized tilemap and/or attrmap and/or palmap...\n"
); );
unoptimized::outputMaps(attrmap, mappings); outputUnoptimizedMaps(attrmap, mappings);
} }
} else { } else {
// All of these require the deduplication process to be performed to be output // All of these require the deduplication process to be performed to be output
options.verbosePrint(Options::VERB_LOG_ACT, "Deduplicating tiles...\n"); options.verbosePrint(Options::VERB_LOG_ACT, "Deduplicating tiles...\n");
optimized::UniqueTiles tiles = optimized::dedupTiles(png, attrmap, palettes, mappings); UniqueTiles tiles = dedupTiles(png, attrmap, palettes, mappings);
if (tiles.size() > options.maxNbTiles[0] + options.maxNbTiles[1]) { if (tiles.size() > options.maxNbTiles[0] + options.maxNbTiles[1]) {
fatal( fatal(
@@ -1277,22 +1262,22 @@ continue_visiting_tiles:;
if (!options.output.empty()) { if (!options.output.empty()) {
options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized tile data...\n"); options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized tile data...\n");
optimized::outputTileData(tiles); outputTileData(tiles);
} }
if (!options.tilemap.empty()) { if (!options.tilemap.empty()) {
options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized tilemap...\n"); options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized tilemap...\n");
optimized::outputTilemap(attrmap); outputTilemap(attrmap);
} }
if (!options.attrmap.empty()) { if (!options.attrmap.empty()) {
options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized attrmap...\n"); options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized attrmap...\n");
optimized::outputAttrmap(attrmap, mappings); outputAttrmap(attrmap, mappings);
} }
if (!options.palmap.empty()) { if (!options.palmap.empty()) {
options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized palmap...\n"); options.verbosePrint(Options::VERB_LOG_ACT, "Generating optimized palmap...\n");
optimized::outputPalmap(attrmap, mappings); outputPalmap(attrmap, mappings);
} }
} }
} }

View File

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

View File

@@ -12,7 +12,7 @@
#include <string.h> #include <string.h>
#include <vector> #include <vector>
#include "defaultinitalloc.hpp" #include "defaultinitvec.hpp"
#include "file.hpp" #include "file.hpp"
#include "helpers.hpp" // assume #include "helpers.hpp" // assume
@@ -389,7 +389,9 @@ void reverse() {
palmap = readInto(options.palmap); palmap = readInto(options.palmap);
if (palmap->size() != mapSize) { if (palmap->size() != mapSize) {
fatal( fatal(
"Palette map size (%zu tiles) doesn't match image size (%zu)", palmap->size(), mapSize "Palette map size (%zu tiles) doesn't match image size (%zu)",
palmap->size(),
mapSize
); );
} }
} }

View File

@@ -359,7 +359,7 @@ static void readSection(
); );
tryReadLong(tmp, file, "%s: Cannot read \"%s\"'s' size: %s", fileName, section.name.c_str()); tryReadLong(tmp, file, "%s: Cannot read \"%s\"'s' size: %s", fileName, section.name.c_str());
if (tmp < 0 || tmp > UINT16_MAX) if (tmp < 0 || tmp > UINT16_MAX)
errx("\"%s\"'s section size (%" PRId32 ") is invalid", section.name.c_str(), tmp); errx("\"%s\"'s section size ($%" PRIx32 ") is invalid", section.name.c_str(), tmp);
section.size = tmp; section.size = tmp;
section.offset = 0; section.offset = 0;
tryGetc( tryGetc(
@@ -379,7 +379,7 @@ static void readSection(
tryReadLong(tmp, file, "%s: Cannot read \"%s\"'s org: %s", fileName, section.name.c_str()); tryReadLong(tmp, file, "%s: Cannot read \"%s\"'s org: %s", fileName, section.name.c_str());
section.isAddressFixed = tmp >= 0; section.isAddressFixed = tmp >= 0;
if (tmp > UINT16_MAX) { if (tmp > UINT16_MAX) {
error(nullptr, 0, "\"%s\"'s org is too large (%" PRId32 ")", section.name.c_str(), tmp); error(nullptr, 0, "\"%s\"'s org is too large ($%" PRIx32 ")", section.name.c_str(), tmp);
tmp = UINT16_MAX; tmp = UINT16_MAX;
} }
section.org = tmp; section.org = tmp;
@@ -405,7 +405,7 @@ static void readSection(
error( error(
nullptr, nullptr,
0, 0,
"\"%s\"'s alignment offset is too large (%" PRId32 ")", "\"%s\"'s alignment offset is too large ($%" PRIx32 ")",
section.name.c_str(), section.name.c_str(),
tmp tmp
); );
@@ -490,7 +490,8 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
file = fopen(fileName, "rb"); file = fopen(fileName, "rb");
} else { } else {
fileName = "<stdin>"; fileName = "<stdin>";
file = fdopen(STDIN_FILENO, "rb"); // `stdin` is in text mode by default (void)setmode(STDIN_FILENO, O_BINARY);
file = stdin;
} }
if (!file) if (!file)
err("Failed to open file \"%s\"", fileName); err("Failed to open file \"%s\"", fileName);

View File

@@ -207,7 +207,8 @@ static void writeROM() {
outputFile = fopen(outputFileName, "wb"); outputFile = fopen(outputFileName, "wb");
} else { } else {
outputFileName = "<stdout>"; outputFileName = "<stdout>";
outputFile = fdopen(STDOUT_FILENO, "wb"); (void)setmode(STDOUT_FILENO, O_BINARY);
outputFile = stdout;
} }
if (!outputFile) if (!outputFile)
err("Failed to open output file \"%s\"", outputFileName); err("Failed to open output file \"%s\"", outputFileName);
@@ -222,7 +223,8 @@ static void writeROM() {
overlayFile = fopen(overlayFileName, "rb"); overlayFile = fopen(overlayFileName, "rb");
} else { } else {
overlayFileName = "<stdin>"; overlayFileName = "<stdin>";
overlayFile = fdopen(STDIN_FILENO, "rb"); (void)setmode(STDIN_FILENO, O_BINARY);
overlayFile = stdin;
} }
if (!overlayFile) if (!overlayFile)
err("Failed to open overlay file \"%s\"", overlayFileName); err("Failed to open overlay file \"%s\"", overlayFileName);
@@ -545,7 +547,8 @@ static void writeSym() {
symFile = fopen(symFileName, "w"); symFile = fopen(symFileName, "w");
} else { } else {
symFileName = "<stdout>"; symFileName = "<stdout>";
symFile = fdopen(STDOUT_FILENO, "w"); (void)setmode(STDOUT_FILENO, O_TEXT); // May have been set to O_BINARY previously
symFile = stdout;
} }
if (!symFile) if (!symFile)
err("Failed to open sym file \"%s\"", symFileName); err("Failed to open sym file \"%s\"", symFileName);
@@ -589,7 +592,8 @@ static void writeMap() {
mapFile = fopen(mapFileName, "w"); mapFile = fopen(mapFileName, "w");
} else { } else {
mapFileName = "<stdout>"; mapFileName = "<stdout>";
mapFile = fdopen(STDOUT_FILENO, "w"); (void)setmode(STDOUT_FILENO, O_TEXT); // May have been set to O_BINARY previously
mapFile = stdout;
} }
if (!mapFile) if (!mapFile)
err("Failed to open map file \"%s\"", mapFileName); err("Failed to open map file \"%s\"", mapFileName);

View File

@@ -101,9 +101,10 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
case RPN_DIV: case RPN_DIV:
value = popRPN(patch); value = popRPN(patch);
if (value == 0) { if (value == 0) {
if (!isError) if (!isError) {
error(patch.src, patch.lineNo, "Division by 0"); error(patch.src, patch.lineNo, "Division by 0");
isError = true; isError = true;
}
popRPN(patch); popRPN(patch);
value = INT32_MAX; value = INT32_MAX;
} else { } else {
@@ -113,9 +114,10 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
case RPN_MOD: case RPN_MOD:
value = popRPN(patch); value = popRPN(patch);
if (value == 0) { if (value == 0) {
if (!isError) if (!isError) {
error(patch.src, patch.lineNo, "Modulo by 0"); error(patch.src, patch.lineNo, "Modulo by 0");
isError = true; isError = true;
}
popRPN(patch); popRPN(patch);
value = 0; value = 0;
} else { } else {
@@ -128,9 +130,10 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
case RPN_EXP: case RPN_EXP:
value = popRPN(patch); value = popRPN(patch);
if (value < 0) { if (value < 0) {
if (!isError) if (!isError) {
error(patch.src, patch.lineNo, "Exponent by negative"); error(patch.src, patch.lineNo, "Exponent by negative value %" PRId32, value);
isError = true; isError = true;
}
popRPN(patch); popRPN(patch);
value = 0; value = 0;
} else { } else {
@@ -342,9 +345,17 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
case RPN_HRAM: case RPN_HRAM:
value = popRPN(patch); value = popRPN(patch);
if (!isError && (value < 0 || (value > 0xFF && value < 0xFF00) || value > 0xFFFF)) { if (value < 0 || (value > 0xFF && value < 0xFF00) || value > 0xFFFF) {
error(patch.src, patch.lineNo, "Value %" PRId32 " is not in HRAM range", value); if (!isError) {
isError = true; error(
patch.src,
patch.lineNo,
"Address $%" PRIx32 " for LDH is not in HRAM range",
value
);
isError = true;
}
value = 0;
} }
value &= 0xFF; value &= 0xFF;
break; break;
@@ -354,9 +365,11 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
// Acceptable values are 0x00, 0x08, 0x10, ..., 0x38 // Acceptable values are 0x00, 0x08, 0x10, ..., 0x38
// They can be easily checked with a bitmask // They can be easily checked with a bitmask
if (value & ~0x38) { if (value & ~0x38) {
if (!isError) if (!isError) {
error(patch.src, patch.lineNo, "Value %" PRId32 " is not a RST vector", value); error(patch.src, patch.lineNo, "Value $%" PRIx32 " is not a RST vector", value);
isError = true; isError = true;
}
value = 0;
} }
value |= 0xC7; value |= 0xC7;
break; break;

View File

@@ -55,7 +55,7 @@
/******************** Tokens and data types ********************/ /******************** Tokens and data types ********************/
%token YYEOF 0 "end of file" %token YYEOF 0 "end of file"
%token newline %token newline "end of line"
%token COMMA "," %token COMMA ","
%token ORG "ORG" %token ORG "ORG"
FLOATING "FLOATING" FLOATING "FLOATING"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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

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

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

View File

@@ -1,3 +1,3 @@
println "Line \ ; this comment is ignored println "Line \ ; this comment is ignored
continuations\ ; so is this one continuations\ ; so is this one
work!" ; =) work!" ; =)

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

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

View File

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

View File

@@ -0,0 +1 @@
BRRAPQCS

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,17 @@
error: nested-bad-interpolation.asm(1):
Interpolated symbol "a" is a reserved keyword; add a '#' prefix to use it as a raw symbol
error: nested-bad-interpolation.asm(1):
Interpolated symbol "" does not exist
error: nested-bad-interpolation.asm(1):
syntax error, unexpected end of line
error: nested-bad-interpolation.asm(2):
Interpolated symbol "b" is a reserved keyword; add a '#' prefix to use it as a raw symbol
warning: nested-bad-interpolation.asm(2): [-Wobsolete]
Treating multi-unit strings as numbers is deprecated
error: nested-bad-interpolation.asm(3):
Interpolated symbol "c" is a reserved keyword; add a '#' prefix to use it as a raw symbol
error: nested-bad-interpolation.asm(3):
Interpolated symbol "" does not exist
warning: nested-bad-interpolation.asm(3): [-Wobsolete]
Treating multi-unit strings as numbers is deprecated
error: Assembly aborted (6 errors)!

View File

@@ -0,0 +1,16 @@
MACRO test
assert (\1) == (\2)
ENDM
test 0xDEADbeef, $DEADbeef
test 0o755, &755
test 0b101010, %101010
test 0XcafeBABE, $cafeBABE
test 0O644, &644
test 0B11100100, %11100100
pusho b.X
test 0b.X.X, %.X.X
test 0BX.X., %X.X.
popo

View File

@@ -1,5 +1,6 @@
; Triggering a charmap realloc while the charmap has been pushed onto the stack used ; Triggering a charmap realloc while the charmap has been pushed onto the stack used
; to induce a use-after-free. ; to induce a use-after-free.
opt Wno-charmap-redef
pushc pushc
charmap "000000000000000000000000000000000",12 charmap "000000000000000000000000000000000",12
popc popc

View File

@@ -1,4 +1,4 @@
SECTION "outer", ROM0 SECTION "outer1", ROM0
LOAD "ram", WRAM0 LOAD "ram", WRAM0
SECTION "inner", ROM0 SECTION "outer2", ROM0
ENDL ENDL

View File

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

View File

@@ -0,0 +1,31 @@
; skipping ahead to `elif`/`else`/`endc`/`endr`/`endm` disables expansions!
MACRO mac1
if _NARG != 2
println "args: \#"
elif (\2) == 42
println "forty-two!"
else
println "it's ", (\2)
endc
ENDM
mac1 elif, 6 * 7 ; this prints "forty-two!" because it takes the `elif`
mac1 elif, 6 * 9
mac1 elif
mac1
MACRO mac2
if _NARG != 2
println "args: \#"
\1 (\2) == 42 ; if the `if` is not taken, this is not expanded!
println "forty-two!"
else
println "it's ", (\2)
endc
ENDM
mac2 elif, 6 * 7 ; this prints "it's $2A" because it skips the `\1` line and takes the `else`
mac2 elif, 6 * 9
mac2 elif
mac2

View File

@@ -0,0 +1,5 @@
error: skip-expansions.asm(31) -> skip-expansions.asm::mac2(21):
Macro argument '\1' not defined
error: skip-expansions.asm(31) -> skip-expansions.asm::mac2(21):
syntax error, unexpected (
error: Assembly aborted (2 errors)!

View File

@@ -0,0 +1,8 @@
forty-two!
it's $36
args: elif
args:
it's $2A
it's $36
args: elif
args:

View File

@@ -1,10 +1,11 @@
MACRO mac def x = 1 ; so far so good...
println "got {d:_NARG} args" def n equ 2 + * / ^ 3 ; oops
ENDM def s equs "no closing quote, lol
mac section "test", rom0 ; good again
mac 42 ld a, 42 ; keep going...
notmac ld xor, ret ; oh no :(
mac label1: ; yes...
mac 42 label2:: ; yes...
mac:: label3::: ; no!
mac :: halt stop abort ; please
println "finally!"

View File

@@ -1,13 +1,11 @@
error: syntax-error-after-syntax-error.asm(2):
syntax error, unexpected *
error: syntax-error-after-syntax-error.asm(3):
Unterminated string
error: syntax-error-after-syntax-error.asm(6): error: syntax-error-after-syntax-error.asm(6):
syntax error, unexpected newline, expecting : or :: syntax error, unexpected xor
error: syntax-error-after-syntax-error.asm(7):
syntax error, unexpected newline, expecting : or ::
To invoke `mac` as a macro it must be indented
error: syntax-error-after-syntax-error.asm(8):
syntax error, unexpected number, expecting : or ::
To invoke `mac` as a macro it must be indented
error: syntax-error-after-syntax-error.asm(9): error: syntax-error-after-syntax-error.asm(9):
'mac' already defined at syntax-error-after-syntax-error.asm(1) syntax error, unexpected :
error: syntax-error-after-syntax-error.asm(10): error: syntax-error-after-syntax-error.asm(10):
'mac' already defined at syntax-error-after-syntax-error.asm(1) syntax error, unexpected stop, expecting end of line or end of buffer or ::
error: Assembly aborted (5 errors)! error: Assembly aborted (5 errors)!

View File

@@ -1,2 +1 @@
got 0 args finally!
got 1 args

View File

@@ -1,3 +1,3 @@
error: syntax-error-eof-newline.asm(5) -> syntax-error-eof-newline.inc(1): error: syntax-error-eof-newline.asm(5) -> syntax-error-eof-newline.inc(1):
syntax error, unexpected newline syntax error, unexpected end of line
error: Assembly aborted (1 error)! error: Assembly aborted (1 error)!

View File

@@ -0,0 +1,2 @@
SECTION "rom", ROM0
LOAD "ram", WRAM0

View File

@@ -0,0 +1,2 @@
warning: unterminated-load.asm(3): [-Wunterminated-load]
`LOAD` block without `ENDL` terminated by EOF

View File

@@ -98,10 +98,10 @@ case "$actionname" in
esac esac
if "$nonfree"; then if "$nonfree"; then
action pret pokecrystal 2024-08-27 7b5986006f6b325e471fee25903c769ce67f5da9 action pret pokecrystal 2024-10-16 961fad9e150df324afc9bccd1ce15c3a65d3c124
action pret pokered 2024-09-08 1f6e2bf999401b9444f939bb40c1eb279bc51829 action pret pokered 2024-10-15 a891fc1168a7f998c570e1ea5f15014556df2d95
action zladx LADX-Disassembly 2024-09-16 008d01541f8cab3f4590cbc94a690af2b9a7979f action zladx LADX-Disassembly 2024-09-16 008d01541f8cab3f4590cbc94a690af2b9a7979f
fi fi
action AntonioND ucity 2024-08-03 f3c6377f1fb1ea29644bcd90722abaaa5d478a74 action AntonioND ucity 2024-08-03 f3c6377f1fb1ea29644bcd90722abaaa5d478a74
action pinobatch libbet 2024-06-15 ee60f0e4712a938589edd3e5d258e519a475d754 action pinobatch libbet 2024-10-20 71d04e850534cbe77d1c143153f664dac1960bc9
action LIJI32 SameBoy 2024-09-13 1931c2830fc46cb648dde4fb0bed4f0345b67b2d action LIJI32 SameBoy 2024-10-12 52d5169cc82356288f337d30aa01fb3fa1b37155

View File

@@ -29,7 +29,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "defaultinitalloc.hpp" #include "defaultinitvec.hpp" // Reused from RGBDS
#include "gfx/rgba.hpp" // Reused from RGBGFX #include "gfx/rgba.hpp" // Reused from RGBGFX

2
test/link/ldh-bad.asm Normal file
View File

@@ -0,0 +1,2 @@
SECTION "bad", ROM0
ldh [$1234+@], a

2
test/link/ldh-bad.out Normal file
View File

@@ -0,0 +1,2 @@
error: ldh-bad.asm(2): Address $1234 for LDH is not in HRAM range
Linking failed with 1 error

View File

@@ -1,2 +1,2 @@
error: rst-bad.asm(2): Value 1 is not a RST vector error: rst-bad.asm(2): Value $1 is not a RST vector
Linking failed with 1 error Linking failed with 1 error

View File

@@ -1,3 +1,3 @@
error: script-syntax-error.link(2): syntax error, unexpected ORG, expecting newline or OPTIONAL error: script-syntax-error.link(2): syntax error, unexpected ORG, expecting end of line or OPTIONAL
error: script-syntax-error.link(5): syntax error, unexpected string, expecting newline or FLOATING or number error: script-syntax-error.link(5): syntax error, unexpected string, expecting end of line or FLOATING or number
Linking failed with 2 errors Linking failed with 2 errors

View File

@@ -1,17 +0,0 @@
diff --git a/makefile b/makefile
index ccfc9d1..ba14638 100644
--- a/makefile
+++ b/makefile
@@ -85,10 +85,10 @@ $(title).gb: $(objlisto)
$(RGBFIX) -jvsc -k "OK" -l 0x33 -m ROM -p 0xFF -t "LIBBET" -v $@
obj/gb/%.o: src/%.z80 src/hardware.inc src/global.inc
- ${RGBASM} -h -o $@ $<
+ ${RGBASM} -o $@ $<
obj/gb/%.o: obj/gb/%.z80
- ${RGBASM} -h -o $@ $<
+ ${RGBASM} -o $@ $<
# Files that will be included with incbin