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

View File

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

View File

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

View File

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

View File

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

1
.gitignore vendored
View File

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

View File

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

View File

@@ -1,6 +1,6 @@
FROM debian:11-slim
LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds
ARG version=0.9.0-rc1
ARG version=0.9.0-rc2
WORKDIR /rgbds
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
VERSION_STRING := `git --git-dir=.git describe --tags --dirty --always 2>/dev/null`
WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option \
-Wno-gnu-zero-variadic-macro-arguments
WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option -Wno-gnu-zero-variadic-macro-arguments
# Overridable CXXFLAGS
CXXFLAGS ?= -O3 -flto -DNDEBUG
# Non-overridable CXXFLAGS
REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++2a -I include \
-D_POSIX_C_SOURCE=200809L -fno-exceptions -fno-rtti
REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++2a -I include -fno-exceptions -fno-rtti
# Overridable LDFLAGS
LDFLAGS ?=
# Non-overridable LDFLAGS
REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} \
-DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} -DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
# Wrapper around bison that passes flags depending on what the version supports
BISON := src/bison.sh

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -797,11 +797,9 @@ static int peek() {
} else if (c == '{' && !lexerState->disableInterpolation) {
// If character is an open brace, do symbol interpolation
shiftChar();
if (auto str = readInterpolation(0); str) {
beginExpansion(str, *str);
}
return peek();
}
@@ -1201,9 +1199,9 @@ static std::shared_ptr<std::string> readInterpolation(size_t depth) {
if (c == '{') { // Nested interpolation
shiftChar();
auto str = readInterpolation(depth + 1);
if (auto str = readInterpolation(depth + 1); str) {
beginExpansion(str, *str);
}
continue; // Restart, reading from the new buffer
} else if (c == EOF || c == '\r' || c == '\n' || c == '"') {
error("Missing }\n");
@@ -1373,6 +1371,7 @@ static std::string readString(bool raw) {
// Line continuation
case ' ':
case '\t':
case '\r':
case '\n':
discardLineContinuation();
@@ -1505,6 +1504,7 @@ static void appendStringLiteral(std::string &str, bool raw) {
// Line continuation
case ' ':
case '\t':
case '\r':
case '\n':
discardLineContinuation();
@@ -1731,7 +1731,25 @@ static Token yylex_NORMAL() {
// Handle numbers
case '0': // Decimal or fixed-point number
case '0': // Decimal, fixed-point, or base-prefix number
switch (peek()) {
case 'x':
case 'X':
shiftChar();
return Token(T_(NUMBER), readHexNumber());
case 'o':
case 'O':
shiftChar();
return Token(T_(NUMBER), readNumber(8, 0));
case 'b':
case 'B':
shiftChar();
return Token(T_(NUMBER), readBinaryNumber());
}
[[fallthrough]];
// Decimal or fixed-point number
case '1':
case '2':
case '3':
@@ -1849,7 +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);
return token;
@@ -1970,6 +2000,7 @@ backslash:
break;
case ' ':
case '\t':
case '\r':
case '\n':
discardLineContinuation();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,9 +8,7 @@
#include "gfx/main.hpp"
namespace sorting {
void indexed(
void sortIndexed(
std::vector<Palette> &palettes,
int palSize,
png_color const *palRGB,
@@ -47,7 +45,7 @@ void indexed(
}
}
void grayscale(
void sortGrayscale(
std::vector<Palette> &palettes, std::array<std::optional<Rgba>, 0x8001> const &colors
) {
options.verbosePrint(Options::VERB_LOG_ACT, "Sorting grayscale-only palette...\n");
@@ -73,7 +71,7 @@ static unsigned int legacyLuminance(uint16_t color) {
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");
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.
static bool
readLine(std::filebuf &file, std::string &buffer) {
assume(buffer.empty());
// TODO: maybe this can be optimized to bulk reads?
for (;;) {
auto c = file.sbumpc();
if (c == std::filebuf::traits_type::eof()) {
return false;
return !buffer.empty();
}
if (c == '\n') {
// Discard a trailing CRLF

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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):
Cannot map an empty string
error: charmap-empty.asm(2):
syntax error, unexpected newline
syntax error, unexpected end of line
error: Assembly aborted (2 errors)!

View File

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

View File

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

View File

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

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

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

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

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

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

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

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

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

View File

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

View File

@@ -0,0 +1 @@
BRRAPQCS

View File

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

View File

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

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
warning: multiple-charmaps.asm(54) -> multiple-charmaps.asm::print_mapped(34): [-Wobsolete]
Treating multi-unit strings as numbers is deprecated
warning: multiple-charmaps.asm(64): [-Wcharmap-redef]
Overriding charmap mapping
warning: multiple-charmaps.asm(73) -> multiple-charmaps.asm::print_mapped(34): [-Wobsolete]
Treating multi-unit strings as numbers is deprecated
warning: multiple-charmaps.asm(95) -> multiple-charmaps.asm::print_mapped(34): [-Wobsolete]

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
; to induce a use-after-free.
opt Wno-charmap-redef
pushc
charmap "000000000000000000000000000000000",12
popc

View File

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

View File

@@ -1,2 +1,5 @@
FATAL: section-in-load.asm(3):
Cannot change the section within a `LOAD` block
warning: section-in-load.asm(3): [-Wunterminated-load]
`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
println "got {d:_NARG} args"
ENDM
mac
mac 42
notmac
mac
mac 42
mac::
mac ::
def x = 1 ; so far so good...
def n equ 2 + * / ^ 3 ; oops
def s equs "no closing quote, lol
section "test", rom0 ; good again
ld a, 42 ; keep going...
ld xor, ret ; oh no :(
label1: ; yes...
label2:: ; yes...
label3::: ; no!
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):
syntax error, unexpected newline, expecting : or ::
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
syntax error, unexpected xor
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):
'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)!

View File

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

View File

@@ -1,3 +1,3 @@
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)!

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
if "$nonfree"; then
action pret pokecrystal 2024-08-27 7b5986006f6b325e471fee25903c769ce67f5da9
action pret pokered 2024-09-08 1f6e2bf999401b9444f939bb40c1eb279bc51829
action pret pokecrystal 2024-10-16 961fad9e150df324afc9bccd1ce15c3a65d3c124
action pret pokered 2024-10-15 a891fc1168a7f998c570e1ea5f15014556df2d95
action zladx LADX-Disassembly 2024-09-16 008d01541f8cab3f4590cbc94a690af2b9a7979f
fi
action AntonioND ucity 2024-08-03 f3c6377f1fb1ea29644bcd90722abaaa5d478a74
action pinobatch libbet 2024-06-15 ee60f0e4712a938589edd3e5d258e519a475d754
action LIJI32 SameBoy 2024-09-13 1931c2830fc46cb648dde4fb0bed4f0345b67b2d
action pinobatch libbet 2024-10-20 71d04e850534cbe77d1c143153f664dac1960bc9
action LIJI32 SameBoy 2024-10-12 52d5169cc82356288f337d30aa01fb3fa1b37155

View File

@@ -29,7 +29,7 @@
#include <string>
#include <vector>
#include "defaultinitalloc.hpp"
#include "defaultinitvec.hpp" // Reused from RGBDS
#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

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(5): syntax error, unexpected string, expecting newline or FLOATING or number
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 end of line or FLOATING or number
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