mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-21 02:32:06 +00:00
Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b85875b67 | ||
|
|
7054d81650 | ||
|
|
5942117ac3 | ||
|
|
e7a3b9d90e | ||
|
|
b13d623ad4 | ||
|
|
37bf9fae01 | ||
|
|
612cf3b7dd | ||
|
|
089e366ddc | ||
|
|
fa9e29e4ce | ||
|
|
fa3d83a3d1 | ||
|
|
804db4e073 | ||
|
|
5d998ef483 | ||
|
|
126b1e5726 | ||
|
|
4f2400c15b | ||
|
|
063d284cbf | ||
|
|
205bf5a11d | ||
|
|
41c94aa448 | ||
|
|
d413870e6d | ||
|
|
e95ac6fb06 | ||
|
|
e1ae92709c | ||
|
|
1715f85d50 | ||
|
|
c2de0a991a | ||
|
|
2e6e1ccf06 | ||
|
|
081f48404c | ||
|
|
bdac0ce053 | ||
|
|
122d91509f | ||
|
|
7c6f778ae7 | ||
|
|
8cf6c5423a | ||
|
|
56f7222230 | ||
|
|
e45b9625ca | ||
|
|
e0a8eb8aff | ||
|
|
2a5b9b5f98 | ||
|
|
a72843748f | ||
|
|
762e2311d2 | ||
|
|
0b7cda9e0c | ||
|
|
df83bc31d2 | ||
|
|
bc8d99d915 | ||
|
|
c841672059 | ||
|
|
75b605797d | ||
|
|
00d0ae840d | ||
|
|
2cdbb145da | ||
|
|
d8192560b0 | ||
|
|
9b395f3bf1 | ||
|
|
0150eb4bf3 | ||
|
|
632342b254 | ||
|
|
c9060c7f2d | ||
|
|
993879a2ed | ||
|
|
62309d5c87 | ||
|
|
b2e865ee2a | ||
|
|
3feb75f84f | ||
|
|
ad4d9da4cf | ||
|
|
1489854932 | ||
|
|
2aef09c8d9 | ||
|
|
48412e9c56 | ||
|
|
177a3abfac | ||
|
|
4c916b8da8 | ||
|
|
d9d381cb62 | ||
|
|
fbde24ee17 | ||
|
|
91310c9eb6 |
1
.github/workflows/build-container.yml
vendored
1
.github/workflows/build-container.yml
vendored
@@ -38,6 +38,7 @@ jobs:
|
|||||||
sed -i "2i LABEL org.opencontainers.image.description=\"RGBDS container image for the release version $TAG_NAME\"" Dockerfile
|
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 build . --tag ghcr.io/gbdev/rgbds:$TAG_NAME
|
||||||
docker push ghcr.io/gbdev/rgbds:$TAG_NAME
|
docker push ghcr.io/gbdev/rgbds:$TAG_NAME
|
||||||
|
docker push ghcr.io/gbdev/rgbds:latest
|
||||||
|
|
||||||
- name: Delete untagged container images
|
- name: Delete untagged container images
|
||||||
if: github.repository_owner == 'gbdev'
|
if: github.repository_owner == 'gbdev'
|
||||||
|
|||||||
2
.github/workflows/checkdiff.yml
vendored
2
.github/workflows/checkdiff.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
cd rgbds
|
cd rgbds
|
||||||
git remote add upstream "${{ github.event.pull_request.base.repo.clone_url }}"
|
git remote add upstream "${{ github.event.pull_request.base.repo.clone_url }}"
|
||||||
git fetch upstream
|
git fetch upstream
|
||||||
- name: Checkdiff
|
- name: Check diff
|
||||||
working-directory: rgbds
|
working-directory: rgbds
|
||||||
run: |
|
run: |
|
||||||
make checkdiff "BASE_REF=${{ github.event.pull_request.base.sha }}" Q= | tee log
|
make checkdiff "BASE_REF=${{ github.event.pull_request.base.sha }}" Q= | tee log
|
||||||
|
|||||||
12
.github/workflows/checkformat.yml
vendored
Normal file
12
.github/workflows/checkformat.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
name: Code format checking
|
||||||
|
on: pull_request
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
checkformat:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Check format
|
||||||
|
run: |
|
||||||
|
./contrib/checkformat.bash
|
||||||
26
.github/workflows/create-release-artifacts.yml
vendored
26
.github/workflows/create-release-artifacts.yml
vendored
@@ -61,12 +61,12 @@ jobs:
|
|||||||
cmake --install build --verbose --prefix install_dir --strip
|
cmake --install build --verbose --prefix install_dir --strip
|
||||||
- name: Package binaries
|
- name: Package binaries
|
||||||
run: |
|
run: |
|
||||||
Compress-Archive -LiteralPath @("install_dir/bin/rgbasm.exe", "install_dir/bin/rgblink.exe", "install_dir/bin/rgbfix.exe", "install_dir/bin/rgbgfx.exe", "install_dir/bin/zlib1.dll", "install_dir/bin/libpng16.dll") "rgbds-${{ env.version }}-win${{ matrix.bits }}.zip"
|
Compress-Archive -LiteralPath @("install_dir/bin/rgbasm.exe", "install_dir/bin/rgblink.exe", "install_dir/bin/rgbfix.exe", "install_dir/bin/rgbgfx.exe", "install_dir/bin/zlib1.dll", "install_dir/bin/libpng16.dll") "rgbds-win${{ matrix.bits }}.zip"
|
||||||
- name: Upload Windows binaries
|
- name: Upload Windows binaries
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: win${{ matrix.bits }}
|
name: win${{ matrix.bits }}
|
||||||
path: rgbds-${{ env.version }}-win${{ matrix.bits }}.zip
|
path: rgbds-win${{ matrix.bits }}.zip
|
||||||
|
|
||||||
macos:
|
macos:
|
||||||
runs-on: macos-14
|
runs-on: macos-14
|
||||||
@@ -92,15 +92,15 @@ jobs:
|
|||||||
strip rgb{asm,link,fix,gfx}
|
strip rgb{asm,link,fix,gfx}
|
||||||
- name: Package binaries
|
- name: Package binaries
|
||||||
run: |
|
run: |
|
||||||
zip --junk-paths rgbds-${{ env.version }}-macos.zip rgb{asm,link,fix,gfx} man/* .github/scripts/install.sh
|
zip --junk-paths rgbds-macos.zip rgb{asm,link,fix,gfx} man/* .github/scripts/install.sh
|
||||||
- name: Upload macOS binaries
|
- name: Upload macOS binaries
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: macos
|
name: macos
|
||||||
path: rgbds-${{ env.version }}-macos.zip
|
path: rgbds-macos.zip
|
||||||
|
|
||||||
linux:
|
linux:
|
||||||
runs-on: ubuntu-20.04 # Oldest supported, for best glibc compatibility.
|
runs-on: ubuntu-22.04 # Oldest supported, for best glibc compatibility.
|
||||||
steps:
|
steps:
|
||||||
- name: Get version from tag
|
- name: Get version from tag
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -112,19 +112,19 @@ jobs:
|
|||||||
- name: Install deps
|
- name: Install deps
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
./.github/scripts/install_deps.sh ubuntu-20.04
|
./.github/scripts/install_deps.sh ubuntu-22.04
|
||||||
- name: Build binaries
|
- name: Build binaries
|
||||||
run: |
|
run: |
|
||||||
make -kj WARNFLAGS="-Wall -Wextra -pedantic -static" PKG_CONFIG="pkg-config --static" Q=
|
make -kj WARNFLAGS="-Wall -Wextra -pedantic -static" PKG_CONFIG="pkg-config --static" Q=
|
||||||
strip rgb{asm,link,fix,gfx}
|
strip rgb{asm,link,fix,gfx}
|
||||||
- name: Package binaries
|
- name: Package binaries
|
||||||
run: |
|
run: |
|
||||||
tar caf rgbds-${{ env.version }}-linux-x86_64.tar.xz --transform='s#.*/##' rgb{asm,link,fix,gfx} man/* .github/scripts/install.sh
|
tar caf rgbds-linux-x86_64.tar.xz --transform='s#.*/##' rgb{asm,link,fix,gfx} man/* .github/scripts/install.sh
|
||||||
- name: Upload Linux binaries
|
- name: Upload Linux binaries
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: linux
|
name: linux
|
||||||
path: rgbds-${{ env.version }}-linux-x86_64.tar.xz
|
path: rgbds-linux-x86_64.tar.xz
|
||||||
|
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -155,11 +155,11 @@ jobs:
|
|||||||
draft: true # Don't publish the release quite yet...
|
draft: true # Don't publish the release quite yet...
|
||||||
prerelease: ${{ contains(github.ref, '-rc') }}
|
prerelease: ${{ contains(github.ref, '-rc') }}
|
||||||
files: |
|
files: |
|
||||||
win32/rgbds-${{ env.version }}-win32.zip
|
win32/rgbds-win32.zip
|
||||||
win64/rgbds-${{ env.version }}-win64.zip
|
win64/rgbds-win64.zip
|
||||||
macos/rgbds-${{ env.version }}-macos.zip
|
macos/rgbds-macos.zip
|
||||||
linux/rgbds-${{ env.version }}-linux-x86_64.tar.xz
|
linux/rgbds-linux-x86_64.tar.xz
|
||||||
rgbds-${{ env.version }}.tar.gz
|
rgbds-source.tar.gz
|
||||||
fail_on_unmatched_files: true
|
fail_on_unmatched_files: true
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
2
.github/workflows/testing.yml
vendored
2
.github/workflows/testing.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
|||||||
unix:
|
unix:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-20.04, ubuntu-22.04, macos-14]
|
os: [ubuntu-22.04, macos-14]
|
||||||
cxx: [g++, clang++]
|
cxx: [g++, clang++]
|
||||||
buildsys: [make, cmake]
|
buildsys: [make, cmake]
|
||||||
exclude:
|
exclude:
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,4 +14,5 @@ CMakeCache.txt
|
|||||||
CMakeFiles/
|
CMakeFiles/
|
||||||
cmake_install.cmake
|
cmake_install.cmake
|
||||||
build/
|
build/
|
||||||
|
*.dSYM/
|
||||||
callgrind.out.*
|
callgrind.out.*
|
||||||
|
|||||||
@@ -51,16 +51,15 @@ else()
|
|||||||
add_link_options(${SAN_FLAGS})
|
add_link_options(${SAN_FLAGS})
|
||||||
add_definitions(-D_GLIBCXX_ASSERTIONS)
|
add_definitions(-D_GLIBCXX_ASSERTIONS)
|
||||||
# A non-zero optimization level is desired in debug mode, but allow overriding it nonetheless
|
# A non-zero optimization level is desired in debug mode, but allow overriding it nonetheless
|
||||||
# TODO: this overrides anything previously set... that's a bit sloppy!
|
set(CMAKE_CXX_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls ${CMAKE_CXX_FLAGS_DEBUG}"
|
||||||
set(CMAKE_CXX_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE STRING "" FORCE)
|
CACHE STRING "" FORCE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(MORE_WARNINGS)
|
if(MORE_WARNINGS)
|
||||||
add_compile_options(-Werror -Wextra
|
add_compile_options(-Werror -Wextra
|
||||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond
|
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond
|
||||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2
|
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2
|
||||||
-Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused
|
-Wstringop-overflow=4 -Wtrampolines -Wundef -Wuninitialized -Wunused -Wshadow
|
||||||
-Wshadow # TODO: -Wshadow=compatible-local?
|
|
||||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1
|
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1
|
||||||
-Wno-format-nonliteral -Wno-strict-overflow
|
-Wno-format-nonliteral -Wno-strict-overflow
|
||||||
-Wno-unused-but-set-variable # bison's `yynerrs_` is incremented but unused
|
-Wno-unused-but-set-variable # bison's `yynerrs_` is incremented but unused
|
||||||
|
|||||||
11
Dockerfile
11
Dockerfile
@@ -1,6 +1,6 @@
|
|||||||
FROM debian:12-slim
|
FROM debian:12-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.1
|
ARG version=0.9.3
|
||||||
WORKDIR /rgbds
|
WORKDIR /rgbds
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
@@ -8,7 +8,14 @@ COPY . .
|
|||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install sudo make cmake gcc build-essential -y
|
apt-get install sudo make cmake gcc build-essential -y
|
||||||
|
|
||||||
|
# Install dependencies and compile RGBDS
|
||||||
RUN ./.github/scripts/install_deps.sh ubuntu-22.04
|
RUN ./.github/scripts/install_deps.sh ubuntu-22.04
|
||||||
RUN make -j CXXFLAGS="-O3 -flto -DNDEBUG -static" PKG_CONFIG="pkg-config --static" Q=
|
RUN make -j CXXFLAGS="-O3 -flto -DNDEBUG -static" PKG_CONFIG="pkg-config --static" Q=
|
||||||
|
|
||||||
RUN tar caf rgbds-${version}-linux-x86_64.tar.xz --transform='s#.*/##' rgbasm rgblink rgbfix rgbgfx man/* .github/scripts/install.sh
|
# Create an archive with the compiled executables and all the necessary to install it,
|
||||||
|
# so it can be copied outside of the container and installed/used in another system
|
||||||
|
RUN tar caf rgbds-linux-x86_64.tar.xz --transform='s#.*/##' rgbasm rgblink rgbfix rgbgfx man/* .github/scripts/install.sh
|
||||||
|
|
||||||
|
# Install RGBDS on the container so all the executables will be available in the PATH
|
||||||
|
RUN cp man/* .
|
||||||
|
RUN ./.github/scripts/install.sh
|
||||||
|
|||||||
46
Makefile
46
Makefile
@@ -7,43 +7,43 @@
|
|||||||
|
|
||||||
# User-defined variables
|
# User-defined variables
|
||||||
|
|
||||||
Q := @
|
Q := @
|
||||||
PREFIX := /usr/local
|
PREFIX := /usr/local
|
||||||
bindir := ${PREFIX}/bin
|
bindir := ${PREFIX}/bin
|
||||||
mandir := ${PREFIX}/share/man
|
mandir := ${PREFIX}/share/man
|
||||||
SUFFIX :=
|
SUFFIX :=
|
||||||
STRIP := -s
|
STRIP := -s
|
||||||
BINMODE := 755
|
BINMODE := 755
|
||||||
MANMODE := 644
|
MANMODE := 644
|
||||||
|
|
||||||
# Other variables
|
# Other variables
|
||||||
|
|
||||||
PKG_CONFIG := pkg-config
|
PKG_CONFIG := pkg-config
|
||||||
PNGCFLAGS := `${PKG_CONFIG} --cflags libpng`
|
PNGCFLAGS := `${PKG_CONFIG} --cflags libpng`
|
||||||
PNGLDFLAGS := `${PKG_CONFIG} --libs-only-L libpng`
|
PNGLDFLAGS := `${PKG_CONFIG} --libs-only-L libpng`
|
||||||
PNGLDLIBS := `${PKG_CONFIG} --libs-only-l libpng`
|
PNGLDLIBS := `${PKG_CONFIG} --libs-only-l libpng`
|
||||||
|
|
||||||
# Note: if this comes up empty, `version.cpp` will automatically fall back to last release number
|
# Note: if this comes up empty, `version.cpp` will automatically fall back to last release number
|
||||||
VERSION_STRING := `git --git-dir=.git -c safe.directory='*' describe --tags --dirty --always 2>/dev/null`
|
VERSION_STRING := `git --git-dir=.git -c safe.directory='*' describe --tags --dirty --always 2>/dev/null`
|
||||||
|
|
||||||
WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option -Wno-gnu-zero-variadic-macro-arguments
|
WARNFLAGS := -Wall -pedantic -Wno-unknown-warning-option -Wno-gnu-zero-variadic-macro-arguments
|
||||||
|
|
||||||
# Overridable CXXFLAGS
|
# Overridable CXXFLAGS
|
||||||
CXXFLAGS ?= -O3 -flto -DNDEBUG
|
CXXFLAGS ?= -O3 -flto -DNDEBUG
|
||||||
# Non-overridable CXXFLAGS
|
# Non-overridable CXXFLAGS
|
||||||
REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++2a -I include -fno-exceptions -fno-rtti
|
REALCXXFLAGS := ${CXXFLAGS} ${WARNFLAGS} -std=c++2a -I include -fno-exceptions -fno-rtti
|
||||||
# Overridable LDFLAGS
|
# Overridable LDFLAGS
|
||||||
LDFLAGS ?=
|
LDFLAGS ?=
|
||||||
# Non-overridable 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
|
# Wrapper around bison that passes flags depending on what the version supports
|
||||||
BISON := src/bison.sh
|
BISON := src/bison.sh
|
||||||
|
|
||||||
RM := rm -rf
|
RM := rm -rf
|
||||||
|
|
||||||
# Used for checking pull requests
|
# Used for checking pull requests
|
||||||
BASE_REF := origin/master
|
BASE_REF := origin/master
|
||||||
|
|
||||||
# Rules to build the RGBDS binaries
|
# Rules to build the RGBDS binaries
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ develop:
|
|||||||
$Q${MAKE} WARNFLAGS="${WARNFLAGS} -Werror -Wextra \
|
$Q${MAKE} WARNFLAGS="${WARNFLAGS} -Werror -Wextra \
|
||||||
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond \
|
-Walloc-zero -Wcast-align -Wcast-qual -Wduplicated-branches -Wduplicated-cond \
|
||||||
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2 \
|
-Wfloat-equal -Wlogical-op -Wnull-dereference -Wold-style-cast -Wshift-overflow=2 \
|
||||||
-Wstringop-overflow=4 -Wundef -Wuninitialized -Wunused -Wshadow \
|
-Wstringop-overflow=4 -Wtrampolines -Wundef -Wuninitialized -Wunused -Wshadow \
|
||||||
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
|
-Wformat=2 -Wformat-overflow=2 -Wformat-truncation=1 \
|
||||||
-Wno-format-nonliteral -Wno-strict-overflow -Wno-unused-but-set-variable \
|
-Wno-format-nonliteral -Wno-strict-overflow -Wno-unused-but-set-variable \
|
||||||
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare -Wvla \
|
-Wno-type-limits -Wno-tautological-constant-out-of-range-compare -Wvla \
|
||||||
@@ -265,4 +265,4 @@ wine-shim:
|
|||||||
|
|
||||||
dist:
|
dist:
|
||||||
$Qgit ls-files | sed s~^~$${PWD##*/}/~ \
|
$Qgit ls-files | sed s~^~$${PWD##*/}/~ \
|
||||||
| tar -czf rgbds-`git -c safe.directory='*' describe --tags | cut -c 2-`.tar.gz -C .. -T -
|
| tar -czf rgbds-source.tar.gz -C .. -T -
|
||||||
|
|||||||
17
RELEASE.md
17
RELEASE.md
@@ -72,9 +72,14 @@ GitHub.
|
|||||||
|
|
||||||
8. Update the following related projects.
|
8. Update the following related projects.
|
||||||
|
|
||||||
- [rgbobj](https://github.com/gbdev/rgbobj) and [rgbds-obj](https://github.com/gbdev/rgbds-obj):
|
1. [rgbds-www](https://github.com/gbdev/rgbds-www): update
|
||||||
make sure that object files created by the latest RGBASM can be parsed and displayed.
|
[src/pages/versions.mdx](https://github.com/gbdev/rgbds-www/blob/master/src/pages/versions.mdx)
|
||||||
If the object file revision has been updated, rgbobj will need a corresponding release.
|
to list the new release.
|
||||||
- [rgbds-www](https://github.com/gbdev/rgbds-www): update
|
2. [rgbds-live](https://github.com/gbdev/rgbds-live): update the `rgbds` submodule (and
|
||||||
[src/pages/versions.mdx](https://github.com/gbdev/rgbds-www/blob/master/src/pages/versions.mdx)
|
[patches/rgbds.patch](https://github.com/gbdev/rgbds-live/blob/master/patches/rgbds.patch)
|
||||||
to list the new release.
|
if necessary) to use the new release, and
|
||||||
|
[index.html](https://github.com/gbdev/rgbds-live/blob/master/index.html)
|
||||||
|
to link to the new manual version.
|
||||||
|
3. [rgbobj](https://github.com/gbdev/rgbobj) and [rgbds-obj](https://github.com/gbdev/rgbds-obj):
|
||||||
|
make sure that object files created by the latest RGBASM can be parsed and displayed.
|
||||||
|
If the object file revision has been updated, `rgbobj` will need a corresponding release.
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ _rgbfix_completions() {
|
|||||||
[l]="old-licensee:unk"
|
[l]="old-licensee:unk"
|
||||||
[m]="mbc-type:mbc"
|
[m]="mbc-type:mbc"
|
||||||
[n]="rom-version:unk"
|
[n]="rom-version:unk"
|
||||||
|
[o]="output:glob-*.gb *.gbc *.sgb"
|
||||||
[p]="pad-value:unk"
|
[p]="pad-value:unk"
|
||||||
[r]="ram-size:unk"
|
[r]="ram-size:unk"
|
||||||
[t]="title:unk"
|
[t]="title:unk"
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ _rgbgfx_completions() {
|
|||||||
[Z]="columns:normal"
|
[Z]="columns:normal"
|
||||||
[a]="attr-map:glob-*.attrmap"
|
[a]="attr-map:glob-*.attrmap"
|
||||||
[A]="auto-attr-map:normal"
|
[A]="auto-attr-map:normal"
|
||||||
|
[B]="background-color:unk"
|
||||||
[b]="base-tiles:unk"
|
[b]="base-tiles:unk"
|
||||||
[c]="colors:unk"
|
[c]="colors:unk"
|
||||||
[d]="depth:unk"
|
[d]="depth:unk"
|
||||||
|
|||||||
15
contrib/checkformat.bash
Executable file
15
contrib/checkformat.bash
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
clang-format --version
|
||||||
|
|
||||||
|
find . -type f \( -iname '*.hpp' -o -iname '*.cpp' \) -exec clang-format -i {} +
|
||||||
|
|
||||||
|
if ! git diff-index --quiet HEAD --; then
|
||||||
|
echo 'Unformatted files:'
|
||||||
|
git diff-index --name-only HEAD --
|
||||||
|
echo
|
||||||
|
git diff HEAD --
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
@@ -32,14 +32,15 @@ diff <(xxd "$1") <(xxd "$2") | while read -r LINE; do
|
|||||||
# Ignore comment lines, only pick matching bank
|
# Ignore comment lines, only pick matching bank
|
||||||
# (The bank regex ignores comments already, make `cut` and `tr` process less lines)
|
# (The bank regex ignores comments already, make `cut` and `tr` process less lines)
|
||||||
grep -Ei "$(printf "^%02x:" $BANK)" "$SYMFILE" |
|
grep -Ei "$(printf "^%02x:" $BANK)" "$SYMFILE" |
|
||||||
|
sed "s/$(printf "^%02x:" $BANK)/0x/g" |
|
||||||
cut -d ';' -f 1 |
|
cut -d ';' -f 1 |
|
||||||
tr -d "\r" |
|
tr -d "\r" |
|
||||||
|
sort -g |
|
||||||
while read -r SYMADDR SYM; do
|
while read -r SYMADDR SYM; do
|
||||||
SYMADDR=$((0x${SYMADDR#*:}))
|
SYMADDR=$(($SYMADDR))
|
||||||
if [[ $SYMADDR -le $ADDR ]]; then
|
if [[ $SYMADDR -le $ADDR ]]; then
|
||||||
printf " (%s+0x%x)\n" "$SYM" $((ADDR - SYMADDR))
|
printf " (%s+0x%x)\n" "$SYM" $((ADDR - SYMADDR))
|
||||||
fi
|
fi
|
||||||
# TODO: assumes sorted sym files
|
|
||||||
done | tail -n 1
|
done | tail -n 1
|
||||||
fi)
|
fi)
|
||||||
printf "%02x:%04x %s\n" $BANK $ADDR "$EXTRA"
|
printf "%02x:%04x %s\n" $BANK $ADDR "$EXTRA"
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ local args=(
|
|||||||
'(-L --logo)'{-L,--logo}'+[Set custom logo]:1bpp image:'
|
'(-L --logo)'{-L,--logo}'+[Set custom logo]:1bpp image:'
|
||||||
'(-m --mbc-type)'{-m,--mbc-type}"+[Set MBC flags]:mbc name:_mbc_names"
|
'(-m --mbc-type)'{-m,--mbc-type}"+[Set MBC flags]:mbc name:_mbc_names"
|
||||||
'(-n --rom-version)'{-n,--rom-version}'+[Set ROM version]:rom version byte:'
|
'(-n --rom-version)'{-n,--rom-version}'+[Set ROM version]:rom version byte:'
|
||||||
|
'(-o --output)'{-o,--output}"+[Output file]:output file:_files -g '*.{gb,sgb,gbc}'"
|
||||||
'(-p --pad-value)'{-p,--pad-value}'+[Pad to next valid size using this byte as padding]:padding byte:'
|
'(-p --pad-value)'{-p,--pad-value}'+[Pad to next valid size using this byte as padding]:padding byte:'
|
||||||
'(-r --ram-size)'{-r,--ram-size}'+[Set RAM size]:ram size byte:'
|
'(-r --ram-size)'{-r,--ram-size}'+[Set RAM size]:ram size byte:'
|
||||||
'(-t --title)'{-t,--title}'+[Set title string]:11-char title string:'
|
'(-t --title)'{-t,--title}'+[Set title string]:11-char title string:'
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ local args=(
|
|||||||
'(-Z --columns)'{-Z,--columns}'[Read the image in column-major order]'
|
'(-Z --columns)'{-Z,--columns}'[Read the image in column-major order]'
|
||||||
|
|
||||||
'(-a --attr-map -A --auto-attr-map)'{-a,--attr-map}'+[Generate a map of tile attributes (mirroring)]:attrmap file:_files'
|
'(-a --attr-map -A --auto-attr-map)'{-a,--attr-map}'+[Generate a map of tile attributes (mirroring)]:attrmap file:_files'
|
||||||
|
'(-B --background-color)'{-B,--background-color}'+[Ignore tiles containing only specified color]:color:'
|
||||||
'(-b --base-tiles)'{-b,--base-tiles}'+[Base tile IDs for tile map output]:base tile IDs:'
|
'(-b --base-tiles)'{-b,--base-tiles}'+[Base tile IDs for tile map output]:base tile IDs:'
|
||||||
'(-c --colors)'{-c,--colors}'+[Specify color palettes]:palette spec:'
|
'(-c --colors)'{-c,--colors}'+[Specify color palettes]:palette spec:'
|
||||||
'(-d --depth)'{-d,--depth}'+[Set bit depth]:bit depth:_depths'
|
'(-d --depth)'{-d,--depth}'+[Set bit depth]:bit depth:_depths'
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#ifndef RGBDS_ASM_CHARMAP_HPP
|
#ifndef RGBDS_ASM_CHARMAP_HPP
|
||||||
#define RGBDS_ASM_CHARMAP_HPP
|
#define RGBDS_ASM_CHARMAP_HPP
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
@@ -20,8 +21,11 @@ void charmap_Push();
|
|||||||
void charmap_Pop();
|
void charmap_Pop();
|
||||||
void charmap_CheckStack();
|
void charmap_CheckStack();
|
||||||
void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value);
|
void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value);
|
||||||
bool charmap_HasChar(std::string const &input);
|
bool charmap_HasChar(std::string const &mapping);
|
||||||
|
size_t charmap_CharSize(std::string const &mapping);
|
||||||
|
std::optional<int32_t> charmap_CharValue(std::string const &mapping, size_t idx);
|
||||||
std::vector<int32_t> charmap_Convert(std::string const &input);
|
std::vector<int32_t> charmap_Convert(std::string const &input);
|
||||||
size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output);
|
size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output);
|
||||||
|
std::string charmap_Reverse(std::vector<int32_t> const &value, bool &unique);
|
||||||
|
|
||||||
#endif // RGBDS_ASM_CHARMAP_HPP
|
#endif // RGBDS_ASM_CHARMAP_HPP
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ void fstk_RunFor(
|
|||||||
int32_t reptLineNo,
|
int32_t reptLineNo,
|
||||||
ContentSpan const &span
|
ContentSpan const &span
|
||||||
);
|
);
|
||||||
void fstk_StopRept();
|
|
||||||
bool fstk_Break();
|
bool fstk_Break();
|
||||||
|
|
||||||
void fstk_NewRecursionDepth(size_t newDepth);
|
void fstk_NewRecursionDepth(size_t newDepth);
|
||||||
|
|||||||
@@ -118,17 +118,8 @@ struct LexerState {
|
|||||||
extern char binDigits[2];
|
extern char binDigits[2];
|
||||||
extern char gfxDigits[4];
|
extern char gfxDigits[4];
|
||||||
|
|
||||||
static inline void lexer_SetBinDigits(char const digits[2]) {
|
void lexer_SetBinDigits(char const digits[2]);
|
||||||
binDigits[0] = digits[0];
|
void lexer_SetGfxDigits(char const digits[4]);
|
||||||
binDigits[1] = digits[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void lexer_SetGfxDigits(char const digits[4]) {
|
|
||||||
gfxDigits[0] = digits[0];
|
|
||||||
gfxDigits[1] = digits[1];
|
|
||||||
gfxDigits[2] = digits[2];
|
|
||||||
gfxDigits[3] = digits[3];
|
|
||||||
}
|
|
||||||
|
|
||||||
bool lexer_AtTopLevel();
|
bool lexer_AtTopLevel();
|
||||||
void lexer_RestartRept(uint32_t lineNo);
|
void lexer_RestartRept(uint32_t lineNo);
|
||||||
|
|||||||
@@ -9,11 +9,11 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
struct MacroArgs {
|
struct MacroArgs {
|
||||||
unsigned int shift;
|
uint32_t shift;
|
||||||
std::vector<std::shared_ptr<std::string>> args;
|
std::vector<std::shared_ptr<std::string>> args;
|
||||||
|
|
||||||
uint32_t nbArgs() const { return args.size() - shift; }
|
uint32_t nbArgs() const { return args.size() - shift; }
|
||||||
std::shared_ptr<std::string> getArg(uint32_t i) const;
|
std::shared_ptr<std::string> getArg(int32_t i) const;
|
||||||
std::shared_ptr<std::string> getAllArgs() const;
|
std::shared_ptr<std::string> getAllArgs() const;
|
||||||
|
|
||||||
void appendArg(std::shared_ptr<std::string> arg);
|
void appendArg(std::shared_ptr<std::string> arg);
|
||||||
|
|||||||
@@ -22,15 +22,6 @@ struct Expression {
|
|||||||
std::vector<uint8_t> rpn{}; // Bytes serializing the RPN expression
|
std::vector<uint8_t> rpn{}; // Bytes serializing the RPN expression
|
||||||
uint32_t rpnPatchSize = 0; // Size the expression will take in the object file
|
uint32_t rpnPatchSize = 0; // Size the expression will take in the object file
|
||||||
|
|
||||||
Expression() = default;
|
|
||||||
Expression(Expression &&) = default;
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
// MSVC and WinFlexBison won't build without this...
|
|
||||||
Expression(Expression const &) = default;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Expression &operator=(Expression &&) = default;
|
|
||||||
|
|
||||||
bool isKnown() const { return data.holds<int32_t>(); }
|
bool isKnown() const { return data.holds<int32_t>(); }
|
||||||
int32_t value() const { return data.get<int32_t>(); }
|
int32_t value() const { return data.get<int32_t>(); }
|
||||||
|
|
||||||
@@ -51,6 +42,7 @@ struct Expression {
|
|||||||
|
|
||||||
bool makeCheckHRAM();
|
bool makeCheckHRAM();
|
||||||
void makeCheckRST();
|
void makeCheckRST();
|
||||||
|
void makeCheckBitIndex(uint8_t mask);
|
||||||
|
|
||||||
void checkNBit(uint8_t n) const;
|
void checkNBit(uint8_t n) const;
|
||||||
|
|
||||||
|
|||||||
@@ -91,11 +91,11 @@ void sect_ByteString(std::vector<int32_t> const &string);
|
|||||||
void sect_WordString(std::vector<int32_t> const &string);
|
void sect_WordString(std::vector<int32_t> const &string);
|
||||||
void sect_LongString(std::vector<int32_t> const &string);
|
void sect_LongString(std::vector<int32_t> const &string);
|
||||||
void sect_Skip(uint32_t skip, bool ds);
|
void sect_Skip(uint32_t skip, bool ds);
|
||||||
void sect_RelByte(Expression &expr, uint32_t pcShift);
|
void sect_RelByte(Expression const &expr, uint32_t pcShift);
|
||||||
void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs);
|
void sect_RelBytes(uint32_t n, std::vector<Expression> const &exprs);
|
||||||
void sect_RelWord(Expression &expr, uint32_t pcShift);
|
void sect_RelWord(Expression const &expr, uint32_t pcShift);
|
||||||
void sect_RelLong(Expression &expr, uint32_t pcShift);
|
void sect_RelLong(Expression const &expr, uint32_t pcShift);
|
||||||
void sect_PCRelByte(Expression &expr, uint32_t pcShift);
|
void sect_PCRelByte(Expression const &expr, uint32_t pcShift);
|
||||||
void sect_BinaryFile(std::string const &name, int32_t startPos);
|
void sect_BinaryFile(std::string const &name, int32_t startPos);
|
||||||
void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t length);
|
void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t length);
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,6 @@ Symbol *sym_RedefEqu(std::string const &symName, int32_t value);
|
|||||||
Symbol *sym_AddVar(std::string const &symName, int32_t value);
|
Symbol *sym_AddVar(std::string const &symName, int32_t value);
|
||||||
int32_t sym_GetRSValue();
|
int32_t sym_GetRSValue();
|
||||||
void sym_SetRSValue(int32_t value);
|
void sym_SetRSValue(int32_t value);
|
||||||
uint32_t sym_GetConstantValue(std::string const &symName);
|
|
||||||
// Find a symbol by exact name, bypassing expansion checks
|
// Find a symbol by exact name, bypassing expansion checks
|
||||||
Symbol *sym_FindExactSymbol(std::string const &symName);
|
Symbol *sym_FindExactSymbol(std::string const &symName);
|
||||||
// Find a symbol, possibly scoped, by name
|
// Find a symbol, possibly scoped, by name
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ public:
|
|||||||
} else if (other._tag == other._t2.tag_value) {
|
} else if (other._tag == other._t2.tag_value) {
|
||||||
*this = other._t2.value;
|
*this = other._t2.value;
|
||||||
} else {
|
} else {
|
||||||
_tag = nulltag;
|
_tag = nulltag; // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,10 @@
|
|||||||
#include "gfx/main.hpp"
|
#include "gfx/main.hpp"
|
||||||
|
|
||||||
class File {
|
class File {
|
||||||
// Construct a `std::streambuf *` by default, since it's probably lighter than a `filebuf`.
|
|
||||||
Either<std::streambuf *, std::filebuf> _file;
|
Either<std::streambuf *, std::filebuf> _file;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
File() {}
|
File() : _file(nullptr) {}
|
||||||
~File() { close(); }
|
|
||||||
|
|
||||||
// This should only be called once, and before doing any `->` operations.
|
// This should only be called once, and before doing any `->` operations.
|
||||||
// Returns `nullptr` on error, and a non-null pointer otherwise.
|
// Returns `nullptr` on error, and a non-null pointer otherwise.
|
||||||
@@ -61,20 +59,6 @@ public:
|
|||||||
return const_cast<File *>(this)->operator->();
|
return const_cast<File *>(this)->operator->();
|
||||||
}
|
}
|
||||||
|
|
||||||
File *close() {
|
|
||||||
if (_file.holds<std::filebuf>()) {
|
|
||||||
// This is called by the destructor, and an explicit `close` shouldn't close twice.
|
|
||||||
std::filebuf fileBuf = std::move(_file.get<std::filebuf>());
|
|
||||||
_file.emplace<std::streambuf *>(nullptr);
|
|
||||||
if (fileBuf.close() != nullptr) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
} else if (_file.get<std::streambuf *>() != nullptr) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
char const *c_str(std::string const &path) const {
|
char const *c_str(std::string const &path) const {
|
||||||
return _file.holds<std::filebuf>() ? path.c_str()
|
return _file.holds<std::filebuf>() ? path.c_str()
|
||||||
: _file.get<std::streambuf *>() == std::cin.rdbuf() ? "<stdin>"
|
: _file.get<std::streambuf *>() == std::cin.rdbuf() ? "<stdin>"
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "helpers.hpp"
|
||||||
|
|
||||||
#include "gfx/rgba.hpp"
|
#include "gfx/rgba.hpp"
|
||||||
|
|
||||||
struct Options {
|
struct Options {
|
||||||
@@ -21,13 +23,16 @@ struct Options {
|
|||||||
uint8_t verbosity = 0; // -v
|
uint8_t verbosity = 0; // -v
|
||||||
|
|
||||||
std::string attrmap{}; // -a, -A
|
std::string attrmap{}; // -a, -A
|
||||||
|
std::optional<Rgba> bgColor{}; // -B
|
||||||
std::array<uint8_t, 2> baseTileIDs{0, 0}; // -b
|
std::array<uint8_t, 2> baseTileIDs{0, 0}; // -b
|
||||||
enum {
|
enum {
|
||||||
NO_SPEC,
|
NO_SPEC,
|
||||||
EXPLICIT,
|
EXPLICIT,
|
||||||
EMBEDDED,
|
EMBEDDED,
|
||||||
|
DMG,
|
||||||
} palSpecType = NO_SPEC; // -c
|
} palSpecType = NO_SPEC; // -c
|
||||||
std::vector<std::array<std::optional<Rgba>, 4>> palSpec{};
|
std::vector<std::array<std::optional<Rgba>, 4>> palSpec{};
|
||||||
|
uint8_t palSpecDmg = 0;
|
||||||
uint8_t bitDepth = 2; // -d
|
uint8_t bitDepth = 2; // -d
|
||||||
std::string inputTileset{}; // -i
|
std::string inputTileset{}; // -i
|
||||||
struct {
|
struct {
|
||||||
@@ -62,6 +67,12 @@ struct Options {
|
|||||||
|
|
||||||
mutable bool hasTransparentPixels = false;
|
mutable bool hasTransparentPixels = false;
|
||||||
uint8_t maxOpaqueColors() const { return nbColorsPerPal - hasTransparentPixels; }
|
uint8_t maxOpaqueColors() const { return nbColorsPerPal - hasTransparentPixels; }
|
||||||
|
|
||||||
|
uint8_t dmgColors[4] = {};
|
||||||
|
uint8_t dmgValue(uint8_t i) const {
|
||||||
|
assume(i < 4);
|
||||||
|
return (palSpecDmg >> (2 * i)) & 0b11;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Options options;
|
extern Options options;
|
||||||
|
|||||||
@@ -3,7 +3,10 @@
|
|||||||
#ifndef RGBDS_GFX_PAL_SPEC_HPP
|
#ifndef RGBDS_GFX_PAL_SPEC_HPP
|
||||||
#define RGBDS_GFX_PAL_SPEC_HPP
|
#define RGBDS_GFX_PAL_SPEC_HPP
|
||||||
|
|
||||||
void parseInlinePalSpec(char const * const arg);
|
void parseInlinePalSpec(char const * const rawArg);
|
||||||
void parseExternalPalSpec(char const *arg);
|
void parseExternalPalSpec(char const *arg);
|
||||||
|
void parseDmgPalSpec(char const * const rawArg);
|
||||||
|
|
||||||
|
void parseBackgroundPalSpec(char const *arg);
|
||||||
|
|
||||||
#endif // RGBDS_GFX_PAL_SPEC_HPP
|
#endif // RGBDS_GFX_PAL_SPEC_HPP
|
||||||
|
|||||||
@@ -36,8 +36,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator==(Rgba const &rhs) const { return 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
|
// CGB colors are RGB555, so we use bit 15 to signify that the color is transparent instead
|
||||||
// Since the rest of the bits don't matter then, we return 0x8000 exactly.
|
// Since the rest of the bits don't matter then, we return 0x8000 exactly.
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ class EnumSeq {
|
|||||||
auto operator*() const { return _value; }
|
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; }
|
||||||
bool operator!=(Iterator const &rhs) const { return _value != rhs._value; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -59,10 +58,6 @@ public:
|
|||||||
bool operator==(ZipIterator const &rhs) const {
|
bool operator==(ZipIterator const &rhs) const {
|
||||||
return std::get<0>(_iters) == std::get<0>(rhs._iters);
|
return std::get<0>(_iters) == std::get<0>(rhs._iters);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator!=(ZipIterator const &rhs) const {
|
|
||||||
return std::get<0>(_iters) != std::get<0>(rhs._iters);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts>
|
template<typename... Ts>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
#include "helpers.hpp" // assume
|
#include "helpers.hpp" // assume
|
||||||
|
|
||||||
#define RGBDS_OBJECT_VERSION_STRING "RGB9"
|
#define RGBDS_OBJECT_VERSION_STRING "RGB9"
|
||||||
#define RGBDS_OBJECT_REV 11U
|
#define RGBDS_OBJECT_REV 12U
|
||||||
|
|
||||||
enum AssertionType { ASSERT_WARN, ASSERT_ERROR, ASSERT_FATAL };
|
enum AssertionType { ASSERT_WARN, ASSERT_ERROR, ASSERT_FATAL };
|
||||||
|
|
||||||
@@ -52,6 +52,7 @@ enum RPNCommand {
|
|||||||
|
|
||||||
RPN_HRAM = 0x60,
|
RPN_HRAM = 0x60,
|
||||||
RPN_RST = 0x61,
|
RPN_RST = 0x61,
|
||||||
|
RPN_BIT_INDEX = 0x62,
|
||||||
|
|
||||||
RPN_HIGH = 0x70,
|
RPN_HIGH = 0x70,
|
||||||
RPN_LOW = 0x71,
|
RPN_LOW = 0x71,
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
#ifndef RGBDS_UTIL_HPP
|
#ifndef RGBDS_UTIL_HPP
|
||||||
#define RGBDS_UTIL_HPP
|
#define RGBDS_UTIL_HPP
|
||||||
|
|
||||||
|
bool startsIdentifier(int c);
|
||||||
|
bool continuesIdentifier(int c);
|
||||||
|
|
||||||
char const *printChar(int c);
|
char const *printChar(int c);
|
||||||
|
|
||||||
#endif // RGBDS_UTIL_HPP
|
#endif // RGBDS_UTIL_HPP
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
#define PACKAGE_VERSION_MAJOR 0
|
#define PACKAGE_VERSION_MAJOR 0
|
||||||
#define PACKAGE_VERSION_MINOR 9
|
#define PACKAGE_VERSION_MINOR 9
|
||||||
#define PACKAGE_VERSION_PATCH 1
|
#define PACKAGE_VERSION_PATCH 3
|
||||||
|
|
||||||
char const *get_package_version_string();
|
char const *get_package_version_string();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd February 2, 2025
|
.Dd June 30, 2025
|
||||||
.Dt GBZ80 7
|
.Dt GBZ80 7
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd February 2, 2025
|
.Dd June 30, 2025
|
||||||
.Dt RGBASM-OLD 5
|
.Dt RGBASM-OLD 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
|||||||
35
man/rgbasm.1
35
man/rgbasm.1
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd February 2, 2025
|
.Dd June 30, 2025
|
||||||
.Dt RGBASM 1
|
.Dt RGBASM 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -51,8 +51,19 @@ is invalid because it could also be
|
|||||||
The arguments are as follows:
|
The arguments are as follows:
|
||||||
.Bl -tag -width Ds
|
.Bl -tag -width Ds
|
||||||
.It Fl b Ar chars , Fl \-binary-digits Ar chars
|
.It Fl b Ar chars , Fl \-binary-digits Ar chars
|
||||||
Change the two characters used for binary constants.
|
Allow two characters to be used for binary constants in addition to the default
|
||||||
The defaults are 01.
|
.Sq 0
|
||||||
|
and
|
||||||
|
.Sq 1 .
|
||||||
|
Valid characters are numbers other than
|
||||||
|
.Sq 0
|
||||||
|
and
|
||||||
|
.Sq 1 ,
|
||||||
|
letters,
|
||||||
|
.Sq \&. ,
|
||||||
|
.Sq # ,
|
||||||
|
or
|
||||||
|
.Sq @ .
|
||||||
.It Fl D Ar name Ns Oo = Ns Ar value Oc , Fl \-define Ar name Ns Oo = Ns Ar value Oc
|
.It Fl D Ar name Ns Oo = Ns Ar value Oc , Fl \-define Ar name Ns Oo = Ns Ar value Oc
|
||||||
Add a string symbol to the compiled source code.
|
Add a string symbol to the compiled source code.
|
||||||
This is equivalent to
|
This is equivalent to
|
||||||
@@ -65,7 +76,21 @@ is not specified.
|
|||||||
.It Fl E , Fl \-export-all
|
.It Fl E , Fl \-export-all
|
||||||
Export all labels, including unreferenced and local labels.
|
Export all labels, including unreferenced and local labels.
|
||||||
.It Fl g Ar chars , Fl \-gfx-chars Ar chars
|
.It Fl g Ar chars , Fl \-gfx-chars Ar chars
|
||||||
Change the four characters used for gfx constants.
|
Allow four characters to be used for graphics constants in addition to the default
|
||||||
|
.Sq 0 ,
|
||||||
|
.Sq 1 ,
|
||||||
|
.Sq 2 ,
|
||||||
|
and
|
||||||
|
.Sq 3 .
|
||||||
|
Valid characters are numbers other than
|
||||||
|
.Sq 0
|
||||||
|
to
|
||||||
|
.Sq 3 ,
|
||||||
|
letters,
|
||||||
|
.Sq \&. ,
|
||||||
|
.Sq # ,
|
||||||
|
or
|
||||||
|
.Sq @ .
|
||||||
The defaults are 0123.
|
The defaults are 0123.
|
||||||
.It Fl h , Fl \-help
|
.It Fl h , Fl \-help
|
||||||
Print help text for the program and exit.
|
Print help text for the program and exit.
|
||||||
@@ -275,7 +300,7 @@ This warning is enabled by
|
|||||||
.Fl Wall .
|
.Fl Wall .
|
||||||
.It Fl Wbuiltin-args
|
.It Fl Wbuiltin-args
|
||||||
Warn about incorrect arguments to built-in functions, such as
|
Warn about incorrect arguments to built-in functions, such as
|
||||||
.Fn STRSUB
|
.Fn STRSLICE
|
||||||
with indexes outside of the string's bounds.
|
with indexes outside of the string's bounds.
|
||||||
This warning is enabled by
|
This warning is enabled by
|
||||||
.Fl Wall .
|
.Fl Wall .
|
||||||
|
|||||||
79
man/rgbasm.5
79
man/rgbasm.5
@@ -2,7 +2,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd February 2, 2025
|
.Dd June 30, 2025
|
||||||
.Dt RGBASM 5
|
.Dt RGBASM 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -548,7 +548,7 @@ There are a number of escape sequences you can use within a string:
|
|||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
Multi-line strings are contained in triple quotes
|
Multi-line strings are contained in triple quotes
|
||||||
.Pq Ql \&"\&"\&"for instance\&"\&"\&" .
|
.Pq Ql \&"\&"\&"for instance""" .
|
||||||
Escape sequences work the same way in multi-line strings; however, literal newline characters will be included as-is, without needing to escape them with
|
Escape sequences work the same way in multi-line strings; however, literal newline characters will be included as-is, without needing to escape them with
|
||||||
.Ql \er
|
.Ql \er
|
||||||
or
|
or
|
||||||
@@ -560,26 +560,30 @@ Inside them, backslashes and braces are treated like regular characters, so they
|
|||||||
For example, the raw string
|
For example, the raw string
|
||||||
.Ql #"\et\e1{s}\e"
|
.Ql #"\et\e1{s}\e"
|
||||||
is equivalent to the regular string
|
is equivalent to the regular string
|
||||||
.Ql "\e\et\e\e1\e{s}\e\e" .
|
.Ql \&"\e\et\e\e1\e{s}\e\e" .
|
||||||
(Note that this prevents raw strings from including the double quote character.)
|
(Note that this prevents raw strings from including the double quote character.)
|
||||||
Raw strings also may be contained in triple quotes for them to be multi-line, so they can include literal newline or quote characters (although still not three quotes in a row).
|
Raw strings also may be contained in triple quotes for them to be multi-line, so they can include literal newline or quote characters (although still not three quotes in a row).
|
||||||
.Pp
|
.Pp
|
||||||
The following functions operate on string expressions.
|
You can use the
|
||||||
Most of them return a string, however some of these functions actually return an integer and can be used as part of an integer expression!
|
.Sq ++
|
||||||
.Bl -column "STRSUB(str, pos, len)"
|
operator to concatenate two strings.
|
||||||
|
.Ql \&"str" ++ \&"ing"
|
||||||
|
is equivalent to
|
||||||
|
.Ql \&"string" ,
|
||||||
|
or to
|
||||||
|
.Ql STRCAT("str", \&"ing") .
|
||||||
|
.Pp
|
||||||
|
The following functions operate on string expressions, and return strings themselves.
|
||||||
|
.Bl -column "STRSLICE(str, start, stop)"
|
||||||
.It Sy Name Ta Sy Operation
|
.It Sy Name Ta Sy Operation
|
||||||
.It Fn STRLEN str Ta Returns the number of characters in Ar str .
|
|
||||||
.It Fn STRCAT strs... Ta Concatenates Ar strs .
|
.It Fn STRCAT strs... Ta Concatenates Ar strs .
|
||||||
.It Fn STRCMP str1 str2 Ta Returns -1 if Ar str1 No is alphabetically lower than Ar str2 No , zero if they match, 1 if Ar str1 No is greater than Ar str2 .
|
|
||||||
.It Fn STRIN str1 str2 Ta Returns the first position of Ar str2 No in Ar str1 No or zero if it's not present Pq first character is position 1 .
|
|
||||||
.It Fn STRRIN str1 str2 Ta Returns the last position of Ar str2 No in Ar str1 No or zero if it's not present Pq first character is position 1 .
|
|
||||||
.It Fn STRSUB str pos len Ta Returns a substring from Ar str No starting at Ar pos No (first character is position 1, last is position -1) and Ar len No characters long. If Ar len No is not specified the substring continues to the end of Ar str .
|
|
||||||
.It Fn STRUPR str Ta Returns Ar str No with all ASCII letters
|
.It Fn STRUPR str Ta Returns Ar str No with all ASCII letters
|
||||||
.Pq Ql a-z
|
.Pq Ql a-z
|
||||||
in uppercase.
|
in uppercase.
|
||||||
.It Fn STRLWR str Ta Returns Ar str No with all ASCII letters
|
.It Fn STRLWR str Ta Returns Ar str No with all ASCII letters
|
||||||
.Pq Ql A-Z
|
.Pq Ql A-Z
|
||||||
in lowercase.
|
in lowercase.
|
||||||
|
.It Fn STRSLICE str start stop Ta Returns a substring of Ar str No starting at Ar start No and ending at Ar stop No (exclusive). If Ar stop No is not specified, the substring continues to the end of Ar str .
|
||||||
.It Fn STRRPL str old new Ta Returns Ar str No with each non-overlapping occurrence of the substring Ar old No replaced with Ar new .
|
.It Fn STRRPL str old new Ta Returns Ar str No with each non-overlapping occurrence of the substring Ar old No replaced with Ar new .
|
||||||
.It Fn STRFMT fmt args... Ta Returns the string Ar fmt No with each
|
.It Fn STRFMT fmt args... Ta Returns the string Ar fmt No with each
|
||||||
.Ql %spec
|
.Ql %spec
|
||||||
@@ -589,9 +593,42 @@ pattern replaced by interpolating the format
|
|||||||
with its corresponding argument in
|
with its corresponding argument in
|
||||||
.Ar args
|
.Ar args
|
||||||
.Pq So %% Sc is replaced by the So % Sc character .
|
.Pq So %% Sc is replaced by the So % Sc character .
|
||||||
.It Fn INCHARMAP str Ta Returns 1 if Ar str No has an entry in the current charmap, and 0 otherwise .
|
.It Fn STRCHAR str idx Ta Returns the substring of Ar str No for the charmap entry at Ar idx No with the current charmap . Pq Ar idx No counts charmap entries, not characters.
|
||||||
.It Fn CHARLEN str Ta Returns the number of charmap entries in Ar str No with the current charmap .
|
.It Fn REVCHAR vals... Ta Returns the string that is mapped to Ar vals No with the current charmap. If there is no unique charmap entry for Ar vals Ns , an error occurs.
|
||||||
.It Fn CHARSUB str pos Ta Returns the substring for the charmap entry at Ar pos No in Ar str No (first character is position 1, last is position -1) with the current charmap .
|
.El
|
||||||
|
.Pp
|
||||||
|
The following functions operate on string expressions, but return integers.
|
||||||
|
.Bl -column "STRRFIND(str, sub)"
|
||||||
|
.It Sy Name Ta Sy Operation
|
||||||
|
.It Fn STRLEN str Ta Returns the number of characters in Ar str .
|
||||||
|
.It Fn STRCMP str1 str2 Ta Compares Ar str1 No and Ar str2 No according to ASCII ordering of their characters. Returns -1 if Ar str1 No is lower than Ar str2 Ns , 1 if Ar str1 No is greater than Ar str2 Ns , or 0 if they match.
|
||||||
|
.It Fn STRFIND str sub Ta Returns the first index of Ar sub No in Ar str Ns , or -1 if it's not present.
|
||||||
|
.It Fn STRRFIND str sub Ta Returns the last index of Ar sub No in Ar str Ns , or -1 if it's not present.
|
||||||
|
.It Fn INCHARMAP str Ta Returns 1 if Ar str No has an entry in the current charmap, or 0 otherwise.
|
||||||
|
.It Fn CHARLEN str Ta Returns the number of charmap entries in Ar str No with the current charmap.
|
||||||
|
.It Fn CHARCMP str1 str2 Ta Compares Ar str1 No and Ar str2 No according to their charmap entry values with the current charmap. Returns -1 if Ar str1 No is lower than Ar str2 Ns , 1 if Ar str1 No is greater than Ar str2 Ns , or 0 if they match.
|
||||||
|
.It Fn CHARSIZE char Ta Returns how many values are in the charmap entry for Ar char No with the current charmap.
|
||||||
|
.It Fn CHARVAL char idx Ta Returns the value at Ar idx No of the charmap entry for Ar char .
|
||||||
|
.El
|
||||||
|
.Pp
|
||||||
|
Note that indexes count starting from 0 at the beginning, or from -1 at the end.
|
||||||
|
The characters of a string are counted by
|
||||||
|
.Ql STRLEN ;
|
||||||
|
the charmap entries of a string are counted by
|
||||||
|
.Ql CHARLEN ;
|
||||||
|
and the values of a charmap entry are counted by
|
||||||
|
.Ql CHARSIZE .
|
||||||
|
.Pp
|
||||||
|
The following legacy functions are similar to other functions that operate on string expressions, but for historical reasons, they count starting from
|
||||||
|
.Em position 1 ,
|
||||||
|
not from index 0!
|
||||||
|
(Position -1 still counts from the end.)
|
||||||
|
.Bl -column "STRSUB(str, pos, len)"
|
||||||
|
.It Sy Name Ta Sy Operation
|
||||||
|
.It Fn STRSUB str pos len Ta Returns a substring of Ar str No starting at Ar pos No and Ar len No characters long. If Ar len No is not specified, the substring continues to the end of Ar str No .
|
||||||
|
.It Fn STRIN str sub Ta Returns the first position of Ar sub No in Ar str Ns , or 0 if it's not present.
|
||||||
|
.It Fn STRRIN str sub Ta Returns the last position of Ar sub No in Ar str Ns , or 0 if it's not present.
|
||||||
|
.It Fn CHARSUB str pos Ta Returns the substring of Ar str No for the charmap entry at Ar pos No with the current charmap . Pq Ar pos No counts charmap entries, not characters.
|
||||||
.El
|
.El
|
||||||
.Ss Character maps
|
.Ss Character maps
|
||||||
When writing text strings that are meant to be displayed on the Game Boy, the character encoding in the ROM may need to be different than the source file encoding.
|
When writing text strings that are meant to be displayed on the Game Boy, the character encoding in the ROM may need to be different than the source file encoding.
|
||||||
@@ -1052,7 +1089,7 @@ and
|
|||||||
.Ic WRAMX
|
.Ic WRAMX
|
||||||
types are still considered different.
|
types are still considered different.
|
||||||
.It
|
.It
|
||||||
Different constraints (alignment, bank, etc.) can be specified for each unionized section declaration, but they must all be compatible.
|
Different constraints (alignment, bank, etc.) can be specified for each section fragment declaration, but they must all be compatible.
|
||||||
For example, alignment must be compatible with any fixed address, all specified banks must be the same, etc.
|
For example, alignment must be compatible with any fixed address, all specified banks must be the same, etc.
|
||||||
.It
|
.It
|
||||||
A section fragment may not be unionized; after all, that wouldn't make much sense.
|
A section fragment may not be unionized; after all, that wouldn't make much sense.
|
||||||
@@ -1104,7 +1141,9 @@ Additionally, label names can contain up to a single dot
|
|||||||
.Ql \&. ,
|
.Ql \&. ,
|
||||||
which may not be the first character.
|
which may not be the first character.
|
||||||
.Pp
|
.Pp
|
||||||
A symbol cannot have the same name as a reserved keyword, unless it is prefixed by a hash
|
A symbol cannot have the same name as a reserved keyword, unless its name is a
|
||||||
|
.Dq raw identifier
|
||||||
|
prefixed by a hash
|
||||||
.Sq # .
|
.Sq # .
|
||||||
For example,
|
For example,
|
||||||
.Ql #load
|
.Ql #load
|
||||||
@@ -1279,7 +1318,7 @@ it at the same time.
|
|||||||
below).
|
below).
|
||||||
.Ss Numeric constants
|
.Ss Numeric constants
|
||||||
.Ic EQU
|
.Ic EQU
|
||||||
is used to define immutable numeric symbols.
|
is used to define numeric constant symbols.
|
||||||
Unlike
|
Unlike
|
||||||
.Sq =
|
.Sq =
|
||||||
above, constants defined this way cannot be redefined.
|
above, constants defined this way cannot be redefined.
|
||||||
@@ -1387,6 +1426,8 @@ This expansion is disabled in a few contexts:
|
|||||||
and
|
and
|
||||||
.Ql MACRO name
|
.Ql MACRO name
|
||||||
will not expand string constants in their names.
|
will not expand string constants in their names.
|
||||||
|
Expansion is also disabled if the string constant's name is a raw identifier prefixed by a hash
|
||||||
|
.Sq # .
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
DEF COUNTREG EQUS "[hl+]"
|
DEF COUNTREG EQUS "[hl+]"
|
||||||
ld a, COUNTREG
|
ld a, COUNTREG
|
||||||
@@ -1852,9 +1893,11 @@ being the second, and so on. Since there are only nine digits, you can only use
|
|||||||
To use the rest, you put the argument number in angle brackets, like
|
To use the rest, you put the argument number in angle brackets, like
|
||||||
.Ic \e<10> .
|
.Ic \e<10> .
|
||||||
.Pp
|
.Pp
|
||||||
This bracketed syntax supports decimal numbers and numeric constant symbols.
|
This bracketed syntax supports decimal numbers and numeric symbols, where negative values count from the last argument.
|
||||||
For example,
|
For example,
|
||||||
.Ql \e<_NARG>
|
.Ql \e<_NARG>
|
||||||
|
or
|
||||||
|
.Ql \e<-1>
|
||||||
will get the last argument.
|
will get the last argument.
|
||||||
.Pp
|
.Pp
|
||||||
Other macro arguments and symbol interpolations will also be expanded inside the angle brackets.
|
Other macro arguments and symbol interpolations will also be expanded inside the angle brackets.
|
||||||
|
|||||||
15
man/rgbds.5
15
man/rgbds.5
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd February 2, 2025
|
.Dd June 30, 2025
|
||||||
.Dt RGBDS 5
|
.Dt RGBDS 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -388,10 +388,19 @@ The value is then ANDed with $00FF
|
|||||||
check.
|
check.
|
||||||
Checks if the value is a valid
|
Checks if the value is a valid
|
||||||
.Ql rst
|
.Ql rst
|
||||||
.Pq see Do RST vec Dc in Xr gbz80 7
|
vector
|
||||||
vector, that is one of $00, $08, $10, $18, $20, $28, $30, or $38.
|
.Pq see Do RST vec Dc in Xr gbz80 7 ,
|
||||||
|
that is, one of $00, $08, $10, $18, $20, $28, $30, or $38.
|
||||||
The value is then ORed with $C7
|
The value is then ORed with $C7
|
||||||
.Pq Ql \&| $C7 .
|
.Pq Ql \&| $C7 .
|
||||||
|
.It Li $62 Ta Ql bit/res/set
|
||||||
|
check; followed by the instruction's
|
||||||
|
.Cm BYTE
|
||||||
|
mask.
|
||||||
|
Checks if the value is a valid bit index
|
||||||
|
.Pq see e.g. Do BIT u3, r8 Dc in Xr gbz80 7 ,
|
||||||
|
that is, from 0 to 7.
|
||||||
|
The value is then ORed with the instruction's mask.
|
||||||
.It Li $80 Ta Integer literal; followed by the
|
.It Li $80 Ta Integer literal; followed by the
|
||||||
.Cm LONG
|
.Cm LONG
|
||||||
integer.
|
integer.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd February 2, 2025
|
.Dd June 30, 2025
|
||||||
.Dt RGBDS 7
|
.Dt RGBDS 7
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd February 2, 2025
|
.Dd June 30, 2025
|
||||||
.Dt RGBFIX 1
|
.Dt RGBFIX 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
.Op Fl l Ar licensee_id
|
.Op Fl l Ar licensee_id
|
||||||
.Op Fl m Ar mbc_type
|
.Op Fl m Ar mbc_type
|
||||||
.Op Fl n Ar rom_version
|
.Op Fl n Ar rom_version
|
||||||
|
.Op Fl o Ar out_file
|
||||||
.Op Fl p Ar pad_value
|
.Op Fl p Ar pad_value
|
||||||
.Op Fl r Ar ram_size
|
.Op Fl r Ar ram_size
|
||||||
.Op Fl t Ar title_str
|
.Op Fl t Ar title_str
|
||||||
@@ -134,6 +135,9 @@ Set the ROM version
|
|||||||
to a given value from 0 to 0xFF.
|
to a given value from 0 to 0xFF.
|
||||||
.It Fl O , Fl \-overwrite
|
.It Fl O , Fl \-overwrite
|
||||||
Allow overwriting different non-zero bytes in the header without a warning being emitted.
|
Allow overwriting different non-zero bytes in the header without a warning being emitted.
|
||||||
|
.It Fl o Ar out_file , Fl \-output Ar out_file
|
||||||
|
Write the modified ROM image to the given file, or '-' to write to standard output.
|
||||||
|
If not specified, the input files are modified in-place, or written to standard output if read from standard input.
|
||||||
.It Fl p Ar pad_value , Fl \-pad-value Ar pad_value
|
.It Fl p Ar pad_value , Fl \-pad-value Ar pad_value
|
||||||
Pad the ROM image to a valid size with a given pad value from 0 to 255 (0xFF).
|
Pad the ROM image to a valid size with a given pad value from 0 to 255 (0xFF).
|
||||||
.Nm
|
.Nm
|
||||||
|
|||||||
37
man/rgbgfx.1
37
man/rgbgfx.1
@@ -2,7 +2,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd February 2, 2025
|
.Dd June 30, 2025
|
||||||
.Dt RGBGFX 1
|
.Dt RGBGFX 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -103,6 +103,19 @@ and has the same size.
|
|||||||
Same as
|
Same as
|
||||||
.Fl a Ar base_path Ns .attrmap
|
.Fl a Ar base_path Ns .attrmap
|
||||||
.Pq see Sx Automatic output paths .
|
.Pq see Sx Automatic output paths .
|
||||||
|
.It Fl B Ar color , Fl \-background-color Ar color
|
||||||
|
Set a background color to be omitted from output.
|
||||||
|
Colors are accepted in
|
||||||
|
.Ql #rgb
|
||||||
|
or
|
||||||
|
.Ql #rrggbb
|
||||||
|
format, or as
|
||||||
|
.Ql transparent .
|
||||||
|
Input tiles which are entirely the specified background color are ignored and will not be output in tile data file.
|
||||||
|
The tilemap, atrribute map, or palette map files
|
||||||
|
.Em will
|
||||||
|
use placeholder values where background tiles were.
|
||||||
|
If a background color is specified, it cannot be used within tiles which are not ignored.
|
||||||
.It Fl b Ar base_ids , Fl \-base-tiles Ar base_ids
|
.It Fl b Ar base_ids , Fl \-base-tiles Ar base_ids
|
||||||
Set the base IDs for tile map output.
|
Set the base IDs for tile map output.
|
||||||
.Ar base_ids
|
.Ar base_ids
|
||||||
@@ -126,7 +139,7 @@ begins with a hash character
|
|||||||
.Ql # ,
|
.Ql # ,
|
||||||
it is treated as an inline palette specification.
|
it is treated as an inline palette specification.
|
||||||
It should contain a comma-separated list of hexadecimal colors, each beginning with a hash.
|
It should contain a comma-separated list of hexadecimal colors, each beginning with a hash.
|
||||||
Colors are accepted either as
|
Colors are accepted in
|
||||||
.Ql #rgb
|
.Ql #rgb
|
||||||
or
|
or
|
||||||
.Ql #rrggbb
|
.Ql #rrggbb
|
||||||
@@ -146,6 +159,24 @@ is the case-insensitive word
|
|||||||
then the first four colors of the input PNG's embedded palette are used.
|
then the first four colors of the input PNG's embedded palette are used.
|
||||||
It is an error if the PNG is not indexed, or if colors other than these 4 are used.
|
It is an error if the PNG is not indexed, or if colors other than these 4 are used.
|
||||||
.Pq This is different from the default behavior of indexed PNGs, as then unused entries in the embedded palette are ignored, whereas they are not with Fl c Cm embedded .
|
.Pq This is different from the default behavior of indexed PNGs, as then unused entries in the embedded palette are ignored, whereas they are not with Fl c Cm embedded .
|
||||||
|
.It Sy DMG palette spec
|
||||||
|
If
|
||||||
|
.Ar pal_spec
|
||||||
|
starts with case-insensitive
|
||||||
|
.Cm dmg= ,
|
||||||
|
then the following two-digit hexadecimal number specifies four grayscale DMG color indexes.
|
||||||
|
The number functions like the DMG's $FF47
|
||||||
|
.Sy BGP
|
||||||
|
register
|
||||||
|
(see
|
||||||
|
.Lk https://gbdev.io/pandocs/Palettes.html Pan Docs
|
||||||
|
for more information):
|
||||||
|
the low two bits 0-1 specify which gray shade goes in color index 0,
|
||||||
|
the next two bits 2-3 specify which gray shade goes in color index 1,
|
||||||
|
and so on.
|
||||||
|
Gray shade 0 is the lightest (white), 3 is the darkest (black).
|
||||||
|
The same gray shade cannot go in two color indexes.
|
||||||
|
To specify a DMG palette, the input PNG must have all its colors in shades of gray, without any transparent colors.
|
||||||
.It Sy external palette spec
|
.It Sy external palette spec
|
||||||
Otherwise,
|
Otherwise,
|
||||||
.Ar pal_spec
|
.Ar pal_spec
|
||||||
@@ -515,6 +546,8 @@ Otherwise, if the PNG only contains shades of gray, they will be categorized int
|
|||||||
.Dq bins
|
.Dq bins
|
||||||
as there are colors per palette, and the palette is set to these bins.
|
as there are colors per palette, and the palette is set to these bins.
|
||||||
The darkest gray will end up in bin #0, and so on; note that this is the opposite of the RGB method below.
|
The darkest gray will end up in bin #0, and so on; note that this is the opposite of the RGB method below.
|
||||||
|
This is equivalent to having specified a DMG palette of
|
||||||
|
.Fl c Cm dmg=E4 .
|
||||||
If two distinct grays end up in the same bin, the RGB method is used instead.
|
If two distinct grays end up in the same bin, the RGB method is used instead.
|
||||||
.Pp
|
.Pp
|
||||||
Be careful that
|
Be careful that
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd February 2, 2025
|
.Dd June 30, 2025
|
||||||
.Dt RGBLINK 1
|
.Dt RGBLINK 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -121,8 +121,8 @@ WRAMX sections that are fixed to a bank other than 1 become errors, other WRAMX
|
|||||||
Disables padding the end of the final file.
|
Disables padding the end of the final file.
|
||||||
This option automatically enables
|
This option automatically enables
|
||||||
.Fl t .
|
.Fl t .
|
||||||
You can use this when not not making a ROM.
|
You can use this to make binary files that are not a ROM.
|
||||||
When making a ROM, be careful that not using this is not a replacement for
|
When making a ROM, note that not using this is not a replacement for
|
||||||
.Xr rgbfix 1 Ap s Fl p
|
.Xr rgbfix 1 Ap s Fl p
|
||||||
option!
|
option!
|
||||||
.El
|
.El
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd February 2, 2025
|
.Dd June 30, 2025
|
||||||
.Dt RGBLINK 5
|
.Dt RGBLINK 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -120,7 +120,7 @@ causes all sections between it and the next
|
|||||||
.Ic ORG
|
.Ic ORG
|
||||||
or bank specification to be placed at addresses automatically determined by
|
or bank specification to be placed at addresses automatically determined by
|
||||||
.Nm .
|
.Nm .
|
||||||
.Pq It is, however, compatible with Ic ALIGN No below.
|
.Pq \&It is, however, compatible with Ic ALIGN No below.
|
||||||
.Pp
|
.Pp
|
||||||
.Ql Ic ALIGN Ar addr , Ar offset
|
.Ql Ic ALIGN Ar addr , Ar offset
|
||||||
increases the
|
increases the
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ set(common_src
|
|||||||
)
|
)
|
||||||
|
|
||||||
find_package(BISON 3.0.0 REQUIRED)
|
find_package(BISON 3.0.0 REQUIRED)
|
||||||
set(BISON_FLAGS "-Wall -Dparse.lac=full -Dlr.type=ielr")
|
set(BISON_FLAGS "-Wall -Dlr.type=ielr")
|
||||||
# Set some optimization flags on versions that support them
|
# Set some optimization flags on versions that support them
|
||||||
if(BISON_VERSION VERSION_GREATER_EQUAL "3.5")
|
if(BISON_VERSION VERSION_GREATER_EQUAL "3.5")
|
||||||
set(BISON_FLAGS "${BISON_FLAGS} -Dapi.token.raw=true")
|
set(BISON_FLAGS "${BISON_FLAGS} -Dparse.lac=full -Dapi.token.raw=true")
|
||||||
endif()
|
endif()
|
||||||
if(BISON_VERSION VERSION_GREATER_EQUAL "3.6")
|
if(BISON_VERSION VERSION_GREATER_EQUAL "3.6")
|
||||||
set(BISON_FLAGS "${BISON_FLAGS} -Dparse.error=detailed")
|
set(BISON_FLAGS "${BISON_FLAGS} -Dparse.error=detailed")
|
||||||
|
|||||||
@@ -31,6 +31,29 @@ struct CharmapNode {
|
|||||||
struct Charmap {
|
struct Charmap {
|
||||||
std::string name;
|
std::string name;
|
||||||
std::vector<CharmapNode> nodes; // first node is reserved for the root node
|
std::vector<CharmapNode> nodes; // first node is reserved for the root node
|
||||||
|
|
||||||
|
// Traverse the trie depth-first to derive the character mappings in definition order
|
||||||
|
template<typename F>
|
||||||
|
bool forEachChar(F callback) const {
|
||||||
|
// clang-format off: nested initializers
|
||||||
|
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}}); !prefixes.empty();) {
|
||||||
|
// clang-format on
|
||||||
|
auto [nodeIdx, mapping] = std::move(prefixes.top());
|
||||||
|
prefixes.pop();
|
||||||
|
CharmapNode const &node = nodes[nodeIdx];
|
||||||
|
if (node.isTerminal()) {
|
||||||
|
if (!callback(nodeIdx, mapping)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (unsigned c = 0; c < std::size(node.next); c++) {
|
||||||
|
if (size_t nextIdx = node.next[c]; nextIdx) {
|
||||||
|
prefixes.push({nextIdx, mapping + static_cast<char>(c)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::deque<Charmap> charmapList;
|
static std::deque<Charmap> charmapList;
|
||||||
@@ -44,24 +67,12 @@ bool charmap_ForEach(
|
|||||||
void (*charFunc)(std::string const &, std::vector<int32_t>)
|
void (*charFunc)(std::string const &, std::vector<int32_t>)
|
||||||
) {
|
) {
|
||||||
for (Charmap const &charmap : charmapList) {
|
for (Charmap const &charmap : charmapList) {
|
||||||
// Traverse the trie depth-first to derive the character mappings in definition order
|
|
||||||
std::map<size_t, std::string> mappings;
|
std::map<size_t, std::string> mappings;
|
||||||
// clang-format off: nested initializers
|
charmap.forEachChar([&mappings](size_t nodeIdx, std::string const &mapping) {
|
||||||
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}});
|
mappings[nodeIdx] = mapping;
|
||||||
!prefixes.empty();) {
|
return true;
|
||||||
// clang-format on
|
});
|
||||||
auto [nodeIdx, mapping] = std::move(prefixes.top());
|
|
||||||
prefixes.pop();
|
|
||||||
CharmapNode const &node = charmap.nodes[nodeIdx];
|
|
||||||
if (node.isTerminal()) {
|
|
||||||
mappings[nodeIdx] = mapping;
|
|
||||||
}
|
|
||||||
for (unsigned c = 0; c < 256; c++) {
|
|
||||||
if (size_t nextIdx = node.next[c]; nextIdx) {
|
|
||||||
prefixes.push({nextIdx, mapping + static_cast<char>(c)});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mapFunc(charmap.name);
|
mapFunc(charmap.name);
|
||||||
for (auto [nodeIdx, mapping] : mappings) {
|
for (auto [nodeIdx, mapping] : mappings) {
|
||||||
charFunc(mapping, charmap.nodes[nodeIdx].value);
|
charFunc(mapping, charmap.nodes[nodeIdx].value);
|
||||||
@@ -163,11 +174,11 @@ void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value) {
|
|||||||
std::swap(node.value, value);
|
std::swap(node.value, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool charmap_HasChar(std::string const &input) {
|
bool charmap_HasChar(std::string const &mapping) {
|
||||||
Charmap const &charmap = *currentCharmap;
|
Charmap const &charmap = *currentCharmap;
|
||||||
size_t nodeIdx = 0;
|
size_t nodeIdx = 0;
|
||||||
|
|
||||||
for (char c : input) {
|
for (char c : mapping) {
|
||||||
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(c)];
|
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(c)];
|
||||||
|
|
||||||
if (!nodeIdx) {
|
if (!nodeIdx) {
|
||||||
@@ -178,6 +189,34 @@ bool charmap_HasChar(std::string const &input) {
|
|||||||
return charmap.nodes[nodeIdx].isTerminal();
|
return charmap.nodes[nodeIdx].isTerminal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static CharmapNode const *charmapEntry(std::string const &mapping) {
|
||||||
|
Charmap const &charmap = *currentCharmap;
|
||||||
|
size_t nodeIdx = 0;
|
||||||
|
|
||||||
|
for (char c : mapping) {
|
||||||
|
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(c)];
|
||||||
|
|
||||||
|
if (!nodeIdx) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &charmap.nodes[nodeIdx];
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t charmap_CharSize(std::string const &mapping) {
|
||||||
|
CharmapNode const *node = charmapEntry(mapping);
|
||||||
|
return node && node->isTerminal() ? node->value.size() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int32_t> charmap_CharValue(std::string const &mapping, size_t idx) {
|
||||||
|
if (CharmapNode const *node = charmapEntry(mapping);
|
||||||
|
node && node->isTerminal() && idx < node->value.size()) {
|
||||||
|
return node->value[idx];
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<int32_t> charmap_Convert(std::string const &input) {
|
std::vector<int32_t> charmap_Convert(std::string const &input) {
|
||||||
std::vector<int32_t> output;
|
std::vector<int32_t> output;
|
||||||
for (std::string_view inputView = input; charmap_ConvertNext(inputView, &output);) {}
|
for (std::string_view inputView = input; charmap_ConvertNext(inputView, &output);) {}
|
||||||
@@ -263,3 +302,20 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output
|
|||||||
input = input.substr(inputIdx);
|
input = input.substr(inputIdx);
|
||||||
return matchLen;
|
return matchLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string charmap_Reverse(std::vector<int32_t> const &value, bool &unique) {
|
||||||
|
Charmap const &charmap = *currentCharmap;
|
||||||
|
std::string revMapping;
|
||||||
|
unique = charmap.forEachChar([&](size_t nodeIdx, std::string const &mapping) {
|
||||||
|
if (charmap.nodes[nodeIdx].value == value) {
|
||||||
|
if (revMapping.empty()) {
|
||||||
|
revMapping = mapping;
|
||||||
|
} else {
|
||||||
|
revMapping.clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
return revMapping;
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,29 +22,29 @@ void FormatSpec::useCharacter(int c) {
|
|||||||
case ' ':
|
case ' ':
|
||||||
case '+':
|
case '+':
|
||||||
if (state > FORMAT_SIGN) {
|
if (state > FORMAT_SIGN) {
|
||||||
goto invalid;
|
break;
|
||||||
}
|
}
|
||||||
state = FORMAT_EXACT;
|
state = FORMAT_EXACT;
|
||||||
sign = c;
|
sign = c;
|
||||||
break;
|
return;
|
||||||
|
|
||||||
// exact
|
// exact
|
||||||
case '#':
|
case '#':
|
||||||
if (state > FORMAT_EXACT) {
|
if (state > FORMAT_EXACT) {
|
||||||
goto invalid;
|
break;
|
||||||
}
|
}
|
||||||
state = FORMAT_ALIGN;
|
state = FORMAT_ALIGN;
|
||||||
exact = true;
|
exact = true;
|
||||||
break;
|
return;
|
||||||
|
|
||||||
// align
|
// align
|
||||||
case '-':
|
case '-':
|
||||||
if (state > FORMAT_ALIGN) {
|
if (state > FORMAT_ALIGN) {
|
||||||
goto invalid;
|
break;
|
||||||
}
|
}
|
||||||
state = FORMAT_WIDTH;
|
state = FORMAT_WIDTH;
|
||||||
alignLeft = true;
|
alignLeft = true;
|
||||||
break;
|
return;
|
||||||
|
|
||||||
// pad, width, and prec values
|
// pad, width, and prec values
|
||||||
case '0':
|
case '0':
|
||||||
@@ -71,27 +71,27 @@ void FormatSpec::useCharacter(int c) {
|
|||||||
} else if (state == FORMAT_PREC) {
|
} else if (state == FORMAT_PREC) {
|
||||||
precision = precision * 10 + (c - '0');
|
precision = precision * 10 + (c - '0');
|
||||||
} else {
|
} else {
|
||||||
goto invalid;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
return;
|
||||||
|
|
||||||
// width
|
// width
|
||||||
case '.':
|
case '.':
|
||||||
if (state > FORMAT_WIDTH) {
|
if (state > FORMAT_WIDTH) {
|
||||||
goto invalid;
|
break;
|
||||||
}
|
}
|
||||||
state = FORMAT_FRAC;
|
state = FORMAT_FRAC;
|
||||||
hasFrac = true;
|
hasFrac = true;
|
||||||
break;
|
return;
|
||||||
|
|
||||||
// prec
|
// prec
|
||||||
case 'q':
|
case 'q':
|
||||||
if (state > FORMAT_PREC) {
|
if (state > FORMAT_PREC) {
|
||||||
goto invalid;
|
break;
|
||||||
}
|
}
|
||||||
state = FORMAT_PREC;
|
state = FORMAT_PREC;
|
||||||
hasPrec = true;
|
hasPrec = true;
|
||||||
break;
|
return;
|
||||||
|
|
||||||
// type
|
// type
|
||||||
case 'd':
|
case 'd':
|
||||||
@@ -103,18 +103,19 @@ void FormatSpec::useCharacter(int c) {
|
|||||||
case 'f':
|
case 'f':
|
||||||
case 's':
|
case 's':
|
||||||
if (state >= FORMAT_DONE) {
|
if (state >= FORMAT_DONE) {
|
||||||
goto invalid;
|
break;
|
||||||
}
|
}
|
||||||
state = FORMAT_DONE;
|
state = FORMAT_DONE;
|
||||||
valid = true;
|
valid = true;
|
||||||
type = c;
|
type = c;
|
||||||
break;
|
return;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
invalid:
|
break;
|
||||||
state = FORMAT_INVALID;
|
|
||||||
valid = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state = FORMAT_INVALID;
|
||||||
|
valid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FormatSpec::finishCharacters() {
|
void FormatSpec::finishCharacters() {
|
||||||
|
|||||||
@@ -119,18 +119,11 @@ void fstk_SetPreIncludeFile(std::string const &path) {
|
|||||||
warnx("Overriding pre-included filename %s", preIncludeName.c_str());
|
warnx("Overriding pre-included filename %s", preIncludeName.c_str());
|
||||||
}
|
}
|
||||||
preIncludeName = path;
|
preIncludeName = path;
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
printf("Pre-included filename %s\n", preIncludeName.c_str());
|
printf("Pre-included filename %s\n", preIncludeName.c_str());
|
||||||
}
|
}
|
||||||
}
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
static void printDep(std::string const &path) {
|
|
||||||
if (dependFile) {
|
|
||||||
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), path.c_str());
|
|
||||||
if (generatePhonyDeps) {
|
|
||||||
fprintf(dependFile, "%s:\n", path.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isValidFilePath(std::string const &path) {
|
static bool isValidFilePath(std::string const &path) {
|
||||||
@@ -138,6 +131,15 @@ static bool isValidFilePath(std::string const &path) {
|
|||||||
return stat(path.c_str(), &statBuf) == 0 && !S_ISDIR(statBuf.st_mode); // Reject directories
|
return stat(path.c_str(), &statBuf) == 0 && !S_ISDIR(statBuf.st_mode); // Reject directories
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void printDep(std::string const &path) {
|
||||||
|
if (dependFile) {
|
||||||
|
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), path.c_str());
|
||||||
|
if (generatePhonyDeps && isValidFilePath(path)) {
|
||||||
|
fprintf(dependFile, "%s:\n", path.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<std::string> fstk_FindFile(std::string const &path) {
|
std::optional<std::string> fstk_FindFile(std::string const &path) {
|
||||||
for (std::string &incPath : includePaths) {
|
for (std::string &incPath : includePaths) {
|
||||||
if (std::string fullPath = incPath + path; isValidFilePath(fullPath)) {
|
if (std::string fullPath = incPath + path; isValidFilePath(fullPath)) {
|
||||||
@@ -308,9 +310,11 @@ void fstk_RunInclude(std::string const &path, bool preInclude) {
|
|||||||
|
|
||||||
if (!fullPath) {
|
if (!fullPath) {
|
||||||
if (generatedMissingIncludes && !preInclude) {
|
if (generatedMissingIncludes && !preInclude) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n", path.c_str(), strerror(errno));
|
printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n", path.c_str(), strerror(errno));
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
failedOnMissingInclude = true;
|
failedOnMissingInclude = true;
|
||||||
} else {
|
} else {
|
||||||
error("Unable to open included file '%s': %s\n", path.c_str(), strerror(errno));
|
error("Unable to open included file '%s': %s\n", path.c_str(), strerror(errno));
|
||||||
@@ -319,7 +323,7 @@ void fstk_RunInclude(std::string const &path, bool preInclude) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!newFileContext(*fullPath, false)) {
|
if (!newFileContext(*fullPath, false)) {
|
||||||
fatalerror("Failed to set up lexer for file include\n");
|
fatalerror("Failed to set up lexer for file include\n"); // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,17 +392,13 @@ void fstk_RunFor(
|
|||||||
context.forName = symName;
|
context.forName = symName;
|
||||||
}
|
}
|
||||||
|
|
||||||
void fstk_StopRept() {
|
|
||||||
contextStack.top().nbReptIters = 0; // Prevent more iterations
|
|
||||||
}
|
|
||||||
|
|
||||||
bool fstk_Break() {
|
bool fstk_Break() {
|
||||||
if (contextStack.top().fileInfo->type != NODE_REPT) {
|
if (contextStack.top().fileInfo->type != NODE_REPT) {
|
||||||
error("BREAK can only be used inside a REPT/FOR block\n");
|
error("BREAK can only be used inside a REPT/FOR block\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fstk_StopRept();
|
contextStack.top().nbReptIters = 0; // Prevent more iterations
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ struct FileUnmapDeleter {
|
|||||||
|
|
||||||
static char *mapFile(int fd, std::string const &path, size_t size) {
|
static char *mapFile(int fd, std::string const &path, size_t size) {
|
||||||
void *mappingAddr = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
void *mappingAddr = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (mappingAddr == MAP_FAILED && errno == ENOTSUP) {
|
if (mappingAddr == MAP_FAILED && errno == ENOTSUP) {
|
||||||
// The implementation may not support MAP_PRIVATE; try again with MAP_SHARED
|
// The implementation may not support MAP_PRIVATE; try again with MAP_SHARED
|
||||||
// instead, offering, I believe, weaker guarantees about external modifications to
|
// instead, offering, I believe, weaker guarantees about external modifications to
|
||||||
@@ -86,6 +87,7 @@ static char *mapFile(int fd, std::string const &path, size_t size) {
|
|||||||
}
|
}
|
||||||
mappingAddr = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0);
|
mappingAddr = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0);
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
return mappingAddr != MAP_FAILED ? static_cast<char *>(mappingAddr) : nullptr;
|
return mappingAddr != MAP_FAILED ? static_cast<char *>(mappingAddr) : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,11 +136,8 @@ struct CaseInsensitive {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Identifiers that are also keywords are listed here. This ONLY applies to ones
|
// This map lists all RGBASM keywords which `yylex_NORMAL` lexes as identifiers.
|
||||||
// that would normally be matched as identifiers! Check out `yylex_NORMAL` to
|
// All non-identifier tokens are lexed separately.
|
||||||
// see how this is used.
|
|
||||||
// Tokens / keywords not handled here are handled in `yylex_NORMAL`'s switch.
|
|
||||||
// This assumes that no two keywords have the same name.
|
|
||||||
static std::unordered_map<std::string, int, CaseInsensitive, CaseInsensitive> keywordDict = {
|
static std::unordered_map<std::string, int, CaseInsensitive, CaseInsensitive> keywordDict = {
|
||||||
{"ADC", T_(SM83_ADC) },
|
{"ADC", T_(SM83_ADC) },
|
||||||
{"ADD", T_(SM83_ADD) },
|
{"ADD", T_(SM83_ADD) },
|
||||||
@@ -241,20 +240,28 @@ static std::unordered_map<std::string, int, CaseInsensitive, CaseInsensitive> ke
|
|||||||
{"BITWIDTH", T_(OP_BITWIDTH) },
|
{"BITWIDTH", T_(OP_BITWIDTH) },
|
||||||
{"TZCOUNT", T_(OP_TZCOUNT) },
|
{"TZCOUNT", T_(OP_TZCOUNT) },
|
||||||
|
|
||||||
{"STRCMP", T_(OP_STRCMP) },
|
|
||||||
{"STRIN", T_(OP_STRIN) },
|
|
||||||
{"STRRIN", T_(OP_STRRIN) },
|
|
||||||
{"STRSUB", T_(OP_STRSUB) },
|
|
||||||
{"STRLEN", T_(OP_STRLEN) },
|
|
||||||
{"STRCAT", T_(OP_STRCAT) },
|
{"STRCAT", T_(OP_STRCAT) },
|
||||||
{"STRUPR", T_(OP_STRUPR) },
|
{"STRCHAR", T_(OP_STRCHAR) },
|
||||||
{"STRLWR", T_(OP_STRLWR) },
|
{"STRCMP", T_(OP_STRCMP) },
|
||||||
{"STRRPL", T_(OP_STRRPL) },
|
{"STRFIND", T_(OP_STRFIND) },
|
||||||
{"STRFMT", T_(OP_STRFMT) },
|
{"STRFMT", T_(OP_STRFMT) },
|
||||||
|
{"STRIN", T_(OP_STRIN) },
|
||||||
|
{"STRLEN", T_(OP_STRLEN) },
|
||||||
|
{"STRLWR", T_(OP_STRLWR) },
|
||||||
|
{"STRRFIND", T_(OP_STRRFIND) },
|
||||||
|
{"STRRIN", T_(OP_STRRIN) },
|
||||||
|
{"STRRPL", T_(OP_STRRPL) },
|
||||||
|
{"STRSLICE", T_(OP_STRSLICE) },
|
||||||
|
{"STRSUB", T_(OP_STRSUB) },
|
||||||
|
{"STRUPR", T_(OP_STRUPR) },
|
||||||
|
|
||||||
|
{"CHARCMP", T_(OP_CHARCMP) },
|
||||||
{"CHARLEN", T_(OP_CHARLEN) },
|
{"CHARLEN", T_(OP_CHARLEN) },
|
||||||
|
{"CHARSIZE", T_(OP_CHARSIZE) },
|
||||||
{"CHARSUB", T_(OP_CHARSUB) },
|
{"CHARSUB", T_(OP_CHARSUB) },
|
||||||
|
{"CHARVAL", T_(OP_CHARVAL) },
|
||||||
{"INCHARMAP", T_(OP_INCHARMAP) },
|
{"INCHARMAP", T_(OP_INCHARMAP) },
|
||||||
|
{"REVCHAR", T_(OP_REVCHAR) },
|
||||||
|
|
||||||
{"INCLUDE", T_(POP_INCLUDE) },
|
{"INCLUDE", T_(POP_INCLUDE) },
|
||||||
{"PRINT", T_(POP_PRINT) },
|
{"PRINT", T_(POP_PRINT) },
|
||||||
@@ -407,21 +414,27 @@ bool LexerState::setFileAsNextState(std::string const &filePath, bool updateStat
|
|||||||
if (filePath == "-") {
|
if (filePath == "-") {
|
||||||
path = "<stdin>";
|
path = "<stdin>";
|
||||||
content.emplace<BufferedContent>(STDIN_FILENO);
|
content.emplace<BufferedContent>(STDIN_FILENO);
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
printf("Opening stdin\n");
|
printf("Opening stdin\n");
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
} else {
|
} else {
|
||||||
struct stat statBuf;
|
struct stat statBuf;
|
||||||
if (stat(filePath.c_str(), &statBuf) != 0) {
|
if (stat(filePath.c_str(), &statBuf) != 0) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
error("Failed to stat file \"%s\": %s\n", filePath.c_str(), strerror(errno));
|
error("Failed to stat file \"%s\": %s\n", filePath.c_str(), strerror(errno));
|
||||||
return false;
|
return false;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
path = filePath;
|
path = filePath;
|
||||||
|
|
||||||
int fd = open(path.c_str(), O_RDONLY);
|
int fd = open(path.c_str(), O_RDONLY);
|
||||||
if (fd < 0) {
|
if (fd < 0) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
error("Failed to open file \"%s\": %s\n", path.c_str(), strerror(errno));
|
error("Failed to open file \"%s\": %s\n", path.c_str(), strerror(errno));
|
||||||
return false;
|
return false;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isMmapped = false;
|
bool isMmapped = false;
|
||||||
@@ -433,9 +446,11 @@ bool LexerState::setFileAsNextState(std::string const &filePath, bool updateStat
|
|||||||
content.emplace<ViewedContent>(
|
content.emplace<ViewedContent>(
|
||||||
std::shared_ptr<char[]>(mappingAddr, FileUnmapDeleter(size)), size
|
std::shared_ptr<char[]>(mappingAddr, FileUnmapDeleter(size)), size
|
||||||
);
|
);
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
printf("File \"%s\" is mmap()ped\n", path.c_str());
|
printf("File \"%s\" is mmap()ped\n", path.c_str());
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
isMmapped = true;
|
isMmapped = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -443,6 +458,7 @@ bool LexerState::setFileAsNextState(std::string const &filePath, bool updateStat
|
|||||||
if (!isMmapped) {
|
if (!isMmapped) {
|
||||||
// Sometimes mmap() fails or isn't available, so have a fallback
|
// Sometimes mmap() fails or isn't available, so have a fallback
|
||||||
content.emplace<BufferedContent>(fd);
|
content.emplace<BufferedContent>(fd);
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
if (statBuf.st_size == 0) {
|
if (statBuf.st_size == 0) {
|
||||||
printf("File \"%s\" is empty\n", path.c_str());
|
printf("File \"%s\" is empty\n", path.c_str());
|
||||||
@@ -452,6 +468,7 @@ bool LexerState::setFileAsNextState(std::string const &filePath, bool updateStat
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -547,7 +564,9 @@ size_t BufferedContent::readMore(size_t startIndex, size_t nbChars) {
|
|||||||
ssize_t nbReadChars = read(fd, &buf[startIndex], nbChars);
|
ssize_t nbReadChars = read(fd, &buf[startIndex], nbChars);
|
||||||
|
|
||||||
if (nbReadChars == -1) {
|
if (nbReadChars == -1) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
fatalerror("Error while reading \"%s\": %s\n", lexerState->path.c_str(), strerror(errno));
|
fatalerror("Error while reading \"%s\": %s\n", lexerState->path.c_str(), strerror(errno));
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
size += nbReadChars;
|
size += nbReadChars;
|
||||||
@@ -592,9 +611,7 @@ static bool isMacroChar(char c) {
|
|||||||
// forward declarations for readBracketedMacroArgNum
|
// forward declarations for readBracketedMacroArgNum
|
||||||
static int peek();
|
static int peek();
|
||||||
static void shiftChar();
|
static void shiftChar();
|
||||||
static uint32_t readNumber(int radix, uint32_t baseValue);
|
static uint32_t readDecimalNumber(int initial);
|
||||||
static bool startsIdentifier(int c);
|
|
||||||
static bool continuesIdentifier(int c);
|
|
||||||
|
|
||||||
static uint32_t readBracketedMacroArgNum() {
|
static uint32_t readBracketedMacroArgNum() {
|
||||||
bool disableMacroArgs = lexerState->disableMacroArgs;
|
bool disableMacroArgs = lexerState->disableMacroArgs;
|
||||||
@@ -606,13 +623,24 @@ static uint32_t readBracketedMacroArgNum() {
|
|||||||
lexerState->disableInterpolation = disableInterpolation;
|
lexerState->disableInterpolation = disableInterpolation;
|
||||||
}};
|
}};
|
||||||
|
|
||||||
uint32_t num = 0;
|
int32_t num = 0;
|
||||||
int c = peek();
|
int c = peek();
|
||||||
bool empty = false;
|
bool empty = false;
|
||||||
bool symbolError = false;
|
bool symbolError = false;
|
||||||
|
bool negative = c == '-';
|
||||||
|
|
||||||
|
if (negative) {
|
||||||
|
shiftChar();
|
||||||
|
c = peek();
|
||||||
|
}
|
||||||
|
|
||||||
if (c >= '0' && c <= '9') {
|
if (c >= '0' && c <= '9') {
|
||||||
num = readNumber(10, 0);
|
uint32_t n = readDecimalNumber(0);
|
||||||
|
if (n > INT32_MAX) {
|
||||||
|
error("Number in bracketed macro argument is too large\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
num = negative ? -n : static_cast<int32_t>(n);
|
||||||
} else if (startsIdentifier(c) || c == '#') {
|
} else if (startsIdentifier(c) || c == '#') {
|
||||||
if (c == '#') {
|
if (c == '#') {
|
||||||
shiftChar();
|
shiftChar();
|
||||||
@@ -645,7 +673,7 @@ static uint32_t readBracketedMacroArgNum() {
|
|||||||
num = 0;
|
num = 0;
|
||||||
symbolError = true;
|
symbolError = true;
|
||||||
} else {
|
} else {
|
||||||
num = sym->getConstantValue();
|
num = static_cast<int32_t>(sym->getConstantValue());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
empty = true;
|
empty = true;
|
||||||
@@ -685,7 +713,7 @@ static std::shared_ptr<std::string> readMacroArg(char name) {
|
|||||||
assume(str); // '\#' should always be defined (at least as an empty string)
|
assume(str); // '\#' should always be defined (at least as an empty string)
|
||||||
return str;
|
return str;
|
||||||
} else if (name == '<') {
|
} else if (name == '<') {
|
||||||
uint32_t num = readBracketedMacroArgNum();
|
int32_t num = readBracketedMacroArgNum();
|
||||||
if (num == 0) {
|
if (num == 0) {
|
||||||
// The error was already reported by `readBracketedMacroArgNum`.
|
// The error was already reported by `readBracketedMacroArgNum`.
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -699,7 +727,7 @@ static std::shared_ptr<std::string> readMacroArg(char name) {
|
|||||||
|
|
||||||
auto str = macroArgs->getArg(num);
|
auto str = macroArgs->getArg(num);
|
||||||
if (!str) {
|
if (!str) {
|
||||||
error("Macro argument '\\<%" PRIu32 ">' not defined\n", num);
|
error("Macro argument '\\<%" PRId32 ">' not defined\n", num);
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
} else {
|
} else {
|
||||||
@@ -756,7 +784,9 @@ int LexerState::peekCharAhead() {
|
|||||||
// and `.peekCharAhead()` will continue with its parent
|
// and `.peekCharAhead()` will continue with its parent
|
||||||
assume(exp.offset <= exp.size());
|
assume(exp.offset <= exp.size());
|
||||||
if (exp.offset + distance < exp.size()) {
|
if (exp.offset + distance < exp.size()) {
|
||||||
return static_cast<uint8_t>((*exp.contents)[exp.offset + distance]);
|
// Macro args can't be recursive, since `peek()` marks them as scanned, so
|
||||||
|
// this is a failsafe that (as far as I can tell) won't ever actually run.
|
||||||
|
return static_cast<uint8_t>((*exp.contents)[exp.offset + distance]); // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
distance -= exp.size() - exp.offset;
|
distance -= exp.size() - exp.offset;
|
||||||
}
|
}
|
||||||
@@ -841,22 +871,24 @@ static void shiftChar() {
|
|||||||
|
|
||||||
lexerState->macroArgScanDistance--;
|
lexerState->macroArgScanDistance--;
|
||||||
|
|
||||||
restart:
|
for (;;) {
|
||||||
if (!lexerState->expansions.empty()) {
|
if (!lexerState->expansions.empty()) {
|
||||||
// Advance within the current expansion
|
// Advance within the current expansion
|
||||||
if (Expansion &exp = lexerState->expansions.front(); exp.advance()) {
|
if (Expansion &exp = lexerState->expansions.front(); exp.advance()) {
|
||||||
// When advancing would go past an expansion's end,
|
// When advancing would go past an expansion's end,
|
||||||
// move up to its parent and try again to advance
|
// move up to its parent and try again to advance
|
||||||
lexerState->expansions.pop_front();
|
lexerState->expansions.pop_front();
|
||||||
goto restart;
|
continue;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Advance within the file contents
|
|
||||||
if (lexerState->content.holds<ViewedContent>()) {
|
|
||||||
lexerState->content.get<ViewedContent>().offset++;
|
|
||||||
} else {
|
} else {
|
||||||
lexerState->content.get<BufferedContent>().advance();
|
// Advance within the file contents
|
||||||
|
if (lexerState->content.holds<ViewedContent>()) {
|
||||||
|
lexerState->content.get<ViewedContent>().offset++;
|
||||||
|
} else {
|
||||||
|
lexerState->content.get<BufferedContent>().advance();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -987,26 +1019,6 @@ static std::string readAnonLabelRef(char c) {
|
|||||||
return sym_MakeAnonLabelName(n, c == '-');
|
return sym_MakeAnonLabelName(n, c == '-');
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t readNumber(int radix, uint32_t baseValue) {
|
|
||||||
uint32_t value = baseValue;
|
|
||||||
|
|
||||||
for (;; shiftChar()) {
|
|
||||||
int c = peek();
|
|
||||||
|
|
||||||
if (c == '_') {
|
|
||||||
continue;
|
|
||||||
} else if (c < '0' || c > '0' + radix - 1) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (value > (UINT32_MAX - (c - '0')) / radix) {
|
|
||||||
warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n");
|
|
||||||
}
|
|
||||||
value = value * radix + (c - '0');
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint32_t readFractionalPart(uint32_t integer) {
|
static uint32_t readFractionalPart(uint32_t integer) {
|
||||||
uint32_t value = 0, divisor = 1;
|
uint32_t value = 0, divisor = 1;
|
||||||
uint8_t precision = 0;
|
uint8_t precision = 0;
|
||||||
@@ -1072,21 +1084,64 @@ static uint32_t readFractionalPart(uint32_t integer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
char binDigits[2];
|
char binDigits[2];
|
||||||
|
char gfxDigits[4];
|
||||||
|
|
||||||
|
static bool isValidDigit(char c) {
|
||||||
|
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '.'
|
||||||
|
|| c == '#' || c == '@';
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool checkDigitErrors(char const *digits, size_t n, char const *type) {
|
||||||
|
for (size_t i = 0; i < n; i++) {
|
||||||
|
char c = digits[i];
|
||||||
|
|
||||||
|
if (!isValidDigit(c)) {
|
||||||
|
error("Invalid digit for %s constant %s\n", type, printChar(c));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c >= '0' && c < static_cast<char>(n + '0') && c != static_cast<char>(i + '0')) {
|
||||||
|
error("Changed digit for %s constant %s\n", type, printChar(c));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t j = i + 1; j < n; j++) {
|
||||||
|
if (c == digits[j]) {
|
||||||
|
error("Repeated digit for %s constant %s\n", type, printChar(c));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void lexer_SetBinDigits(char const digits[2]) {
|
||||||
|
if (size_t n = std::size(binDigits); checkDigitErrors(digits, n, "binary")) {
|
||||||
|
memcpy(binDigits, digits, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void lexer_SetGfxDigits(char const digits[4]) {
|
||||||
|
if (size_t n = std::size(gfxDigits); checkDigitErrors(digits, n, "graphics")) {
|
||||||
|
memcpy(gfxDigits, digits, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static uint32_t readBinaryNumber() {
|
static uint32_t readBinaryNumber() {
|
||||||
uint32_t value = 0;
|
uint32_t value = 0;
|
||||||
|
bool empty = true;
|
||||||
|
|
||||||
for (;; shiftChar()) {
|
for (;; shiftChar()) {
|
||||||
int c = peek();
|
int c = peek();
|
||||||
int bit;
|
int bit;
|
||||||
|
|
||||||
// Check for '_' after digits in case one of the digits is '_'
|
if (c == '_' && !empty) {
|
||||||
if (c == binDigits[0]) {
|
|
||||||
bit = 0;
|
|
||||||
} else if (c == binDigits[1]) {
|
|
||||||
bit = 1;
|
|
||||||
} else if (c == '_') {
|
|
||||||
continue;
|
continue;
|
||||||
|
} else if (c == '0' || c == binDigits[0]) {
|
||||||
|
bit = 0;
|
||||||
|
} else if (c == '1' || c == binDigits[1]) {
|
||||||
|
bit = 1;
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1094,6 +1149,72 @@ static uint32_t readBinaryNumber() {
|
|||||||
warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n");
|
warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n");
|
||||||
}
|
}
|
||||||
value = value * 2 + bit;
|
value = value * 2 + bit;
|
||||||
|
|
||||||
|
empty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty) {
|
||||||
|
error("Invalid integer constant, no digits after '%%'\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t readOctalNumber() {
|
||||||
|
uint32_t value = 0;
|
||||||
|
bool empty = true;
|
||||||
|
|
||||||
|
for (;; shiftChar()) {
|
||||||
|
int c = peek();
|
||||||
|
|
||||||
|
if (c == '_' && !empty) {
|
||||||
|
continue;
|
||||||
|
} else if (c >= '0' && c <= '7') {
|
||||||
|
c = c - '0';
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value > (UINT32_MAX - c) / 8) {
|
||||||
|
warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n");
|
||||||
|
}
|
||||||
|
value = value * 8 + c;
|
||||||
|
|
||||||
|
empty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty) {
|
||||||
|
error("Invalid integer constant, no digits after '&'\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t readDecimalNumber(int initial) {
|
||||||
|
uint32_t value = initial ? initial - '0' : 0;
|
||||||
|
bool empty = !initial;
|
||||||
|
|
||||||
|
for (;; shiftChar()) {
|
||||||
|
int c = peek();
|
||||||
|
|
||||||
|
if (c == '_' && !empty) {
|
||||||
|
continue;
|
||||||
|
} else if (c >= '0' && c <= '9') {
|
||||||
|
c = c - '0';
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value > (UINT32_MAX - c) / 10) {
|
||||||
|
warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n");
|
||||||
|
}
|
||||||
|
value = value * 10 + c;
|
||||||
|
|
||||||
|
empty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty) {
|
||||||
|
error("Invalid integer constant, no digits\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
@@ -1106,14 +1227,14 @@ static uint32_t readHexNumber() {
|
|||||||
for (;; shiftChar()) {
|
for (;; shiftChar()) {
|
||||||
int c = peek();
|
int c = peek();
|
||||||
|
|
||||||
if (c >= 'a' && c <= 'f') {
|
if (c == '_' && !empty) {
|
||||||
|
continue;
|
||||||
|
} else if (c >= 'a' && c <= 'f') {
|
||||||
c = c - 'a' + 10;
|
c = c - 'a' + 10;
|
||||||
} else if (c >= 'A' && c <= 'F') {
|
} else if (c >= 'A' && c <= 'F') {
|
||||||
c = c - 'A' + 10;
|
c = c - 'A' + 10;
|
||||||
} else if (c >= '0' && c <= '9') {
|
} else if (c >= '0' && c <= '9') {
|
||||||
c = c - '0';
|
c = c - '0';
|
||||||
} else if (c == '_' && !empty) {
|
|
||||||
continue;
|
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1133,8 +1254,6 @@ static uint32_t readHexNumber() {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
char gfxDigits[4];
|
|
||||||
|
|
||||||
static uint32_t readGfxConstant() {
|
static uint32_t readGfxConstant() {
|
||||||
uint32_t bitPlaneLower = 0, bitPlaneUpper = 0;
|
uint32_t bitPlaneLower = 0, bitPlaneUpper = 0;
|
||||||
uint8_t width = 0;
|
uint8_t width = 0;
|
||||||
@@ -1143,17 +1262,16 @@ static uint32_t readGfxConstant() {
|
|||||||
int c = peek();
|
int c = peek();
|
||||||
uint32_t pixel;
|
uint32_t pixel;
|
||||||
|
|
||||||
// Check for '_' after digits in case one of the digits is '_'
|
if (c == '_' && width > 0) {
|
||||||
if (c == gfxDigits[0]) {
|
|
||||||
pixel = 0;
|
|
||||||
} else if (c == gfxDigits[1]) {
|
|
||||||
pixel = 1;
|
|
||||||
} else if (c == gfxDigits[2]) {
|
|
||||||
pixel = 2;
|
|
||||||
} else if (c == gfxDigits[3]) {
|
|
||||||
pixel = 3;
|
|
||||||
} else if (c == '_' && width > 0) {
|
|
||||||
continue;
|
continue;
|
||||||
|
} else if (c == '0' || c == gfxDigits[0]) {
|
||||||
|
pixel = 0;
|
||||||
|
} else if (c == '1' || c == gfxDigits[1]) {
|
||||||
|
pixel = 1;
|
||||||
|
} else if (c == '2' || c == gfxDigits[2]) {
|
||||||
|
pixel = 2;
|
||||||
|
} else if (c == '3' || c == gfxDigits[3]) {
|
||||||
|
pixel = 3;
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1179,31 +1297,22 @@ static uint32_t readGfxConstant() {
|
|||||||
return bitPlaneUpper << 8 | bitPlaneLower;
|
return bitPlaneUpper << 8 | bitPlaneLower;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Functions to read identifiers & keywords
|
// Functions to read identifiers and keywords
|
||||||
|
|
||||||
static bool startsIdentifier(int c) {
|
|
||||||
// Anonymous labels internally start with '!'
|
|
||||||
return (c <= 'Z' && c >= 'A') || (c <= 'z' && c >= 'a') || c == '.' || c == '_';
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool continuesIdentifier(int c) {
|
|
||||||
return startsIdentifier(c) || (c <= '9' && c >= '0') || c == '#' || c == '$' || c == '@';
|
|
||||||
}
|
|
||||||
|
|
||||||
static Token readIdentifier(char firstChar, bool raw) {
|
static Token readIdentifier(char firstChar, bool raw) {
|
||||||
std::string identifier(1, firstChar);
|
std::string identifier(1, firstChar);
|
||||||
int tokenType = firstChar == '.' ? T_(LOCAL_ID) : T_(ID);
|
int tokenType = firstChar == '.' ? T_(LOCAL) : T_(SYMBOL);
|
||||||
|
|
||||||
// Continue reading while the char is in the symbol charset
|
// Continue reading while the char is in the identifier charset
|
||||||
for (int c = peek(); continuesIdentifier(c); c = peek()) {
|
for (int c = peek(); continuesIdentifier(c); c = peek()) {
|
||||||
shiftChar();
|
shiftChar();
|
||||||
|
|
||||||
// Write the char to the identifier's name
|
// Write the char to the identifier's name
|
||||||
identifier += c;
|
identifier += c;
|
||||||
|
|
||||||
// If the char was a dot, mark the identifier as local
|
// If the char was a dot, the identifier is a local label
|
||||||
if (c == '.') {
|
if (c == '.') {
|
||||||
tokenType = T_(LOCAL_ID);
|
tokenType = T_(LOCAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1219,7 +1328,7 @@ static Token readIdentifier(char firstChar, bool raw) {
|
|||||||
|
|
||||||
// Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot
|
// Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot
|
||||||
if (identifier.find_first_not_of('.') == identifier.npos) {
|
if (identifier.find_first_not_of('.') == identifier.npos) {
|
||||||
tokenType = T_(ID);
|
tokenType = T_(SYMBOL);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Token(tokenType, identifier);
|
return Token(tokenType, identifier);
|
||||||
@@ -1276,7 +1385,7 @@ static std::shared_ptr<std::string> readInterpolation(size_t depth) {
|
|||||||
lexerState->disableInterpolation = disableInterpolation;
|
lexerState->disableInterpolation = disableInterpolation;
|
||||||
|
|
||||||
if (fmtBuf.starts_with('#')) {
|
if (fmtBuf.starts_with('#')) {
|
||||||
// Skip a '#' raw identifier prefix, but after expanding any nested interpolations.
|
// Skip a '#' raw symbol prefix, but after expanding any nested interpolations.
|
||||||
fmtBuf.erase(0, 1);
|
fmtBuf.erase(0, 1);
|
||||||
} else if (keywordDict.find(fmtBuf) != keywordDict.end()) {
|
} else if (keywordDict.find(fmtBuf) != keywordDict.end()) {
|
||||||
// Don't allow symbols that alias keywords without a '#' prefix.
|
// Don't allow symbols that alias keywords without a '#' prefix.
|
||||||
@@ -1318,8 +1427,11 @@ static void appendEscapedString(std::string &str, std::string const &escape) {
|
|||||||
str += "\\n";
|
str += "\\n";
|
||||||
break;
|
break;
|
||||||
case '\r':
|
case '\r':
|
||||||
|
// A literal CR in a string may get treated as a LF, so '\r' is not tested.
|
||||||
|
// LCOV_EXCL_START
|
||||||
str += "\\r";
|
str += "\\r";
|
||||||
break;
|
break;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
case '\t':
|
case '\t':
|
||||||
str += "\\t";
|
str += "\\t";
|
||||||
break;
|
break;
|
||||||
@@ -1620,6 +1732,12 @@ static void appendStringLiteral(std::string &str, bool raw) {
|
|||||||
|
|
||||||
static Token yylex_SKIP_TO_ENDC(); // forward declaration for yylex_NORMAL
|
static Token yylex_SKIP_TO_ENDC(); // forward declaration for yylex_NORMAL
|
||||||
|
|
||||||
|
// Must stay in sync with the `switch` in `yylex_NORMAL`!
|
||||||
|
static bool isGarbageCharacter(int c) {
|
||||||
|
return c != EOF && !continuesIdentifier(c)
|
||||||
|
&& (c == '\0' || !strchr("; \t~[](),+-*/|^=!<>:&%`\"\r\n\\", c));
|
||||||
|
}
|
||||||
|
|
||||||
static Token yylex_NORMAL() {
|
static Token yylex_NORMAL() {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
int c = nextChar();
|
int c = nextChar();
|
||||||
@@ -1641,7 +1759,7 @@ static Token yylex_NORMAL() {
|
|||||||
|
|
||||||
case '@': {
|
case '@': {
|
||||||
std::string symName("@");
|
std::string symName("@");
|
||||||
return Token(T_(ID), symName);
|
return Token(T_(SYMBOL), symName);
|
||||||
}
|
}
|
||||||
|
|
||||||
case '[':
|
case '[':
|
||||||
@@ -1657,12 +1775,17 @@ static Token yylex_NORMAL() {
|
|||||||
|
|
||||||
// Handle ambiguous 1- or 2-char tokens
|
// Handle ambiguous 1- or 2-char tokens
|
||||||
|
|
||||||
case '+': // Either += or ADD
|
case '+': // Either +=, ADD, or CAT
|
||||||
if (peek() == '=') {
|
switch (peek()) {
|
||||||
|
case '=':
|
||||||
shiftChar();
|
shiftChar();
|
||||||
return Token(T_(POP_ADDEQ));
|
return Token(T_(POP_ADDEQ));
|
||||||
|
case '+':
|
||||||
|
shiftChar();
|
||||||
|
return Token(T_(OP_CAT));
|
||||||
|
default:
|
||||||
|
return Token(T_(OP_ADD));
|
||||||
}
|
}
|
||||||
return Token(T_(OP_ADD));
|
|
||||||
|
|
||||||
case '-': // Either -= or SUB
|
case '-': // Either -= or SUB
|
||||||
if (peek() == '=') {
|
if (peek() == '=') {
|
||||||
@@ -1795,7 +1918,7 @@ static Token yylex_NORMAL() {
|
|||||||
case 'o':
|
case 'o':
|
||||||
case 'O':
|
case 'O':
|
||||||
shiftChar();
|
shiftChar();
|
||||||
return Token(T_(NUMBER), readNumber(8, 0));
|
return Token(T_(NUMBER), readOctalNumber());
|
||||||
case 'b':
|
case 'b':
|
||||||
case 'B':
|
case 'B':
|
||||||
shiftChar();
|
shiftChar();
|
||||||
@@ -1814,7 +1937,7 @@ static Token yylex_NORMAL() {
|
|||||||
case '7':
|
case '7':
|
||||||
case '8':
|
case '8':
|
||||||
case '9': {
|
case '9': {
|
||||||
uint32_t n = readNumber(10, c - '0');
|
uint32_t n = readDecimalNumber(c);
|
||||||
|
|
||||||
if (peek() == '.') {
|
if (peek() == '.') {
|
||||||
shiftChar();
|
shiftChar();
|
||||||
@@ -1832,7 +1955,7 @@ static Token yylex_NORMAL() {
|
|||||||
shiftChar();
|
shiftChar();
|
||||||
return Token(T_(OP_LOGICAND));
|
return Token(T_(OP_LOGICAND));
|
||||||
} else if (c >= '0' && c <= '7') {
|
} else if (c >= '0' && c <= '7') {
|
||||||
return Token(T_(NUMBER), readNumber(8, 0));
|
return Token(T_(NUMBER), readOctalNumber());
|
||||||
}
|
}
|
||||||
return Token(T_(OP_AND));
|
return Token(T_(OP_AND));
|
||||||
|
|
||||||
@@ -1841,7 +1964,7 @@ static Token yylex_NORMAL() {
|
|||||||
if (c == '=') {
|
if (c == '=') {
|
||||||
shiftChar();
|
shiftChar();
|
||||||
return Token(T_(POP_MODEQ));
|
return Token(T_(POP_MODEQ));
|
||||||
} else if (c == binDigits[0] || c == binDigits[1]) {
|
} else if (c == '0' || c == '1' || c == binDigits[0] || c == binDigits[1]) {
|
||||||
return Token(T_(NUMBER), readBinaryNumber());
|
return Token(T_(NUMBER), readBinaryNumber());
|
||||||
}
|
}
|
||||||
return Token(T_(OP_MOD));
|
return Token(T_(OP_MOD));
|
||||||
@@ -1903,15 +2026,15 @@ static Token yylex_NORMAL() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If a keyword, don't try to expand
|
// If a keyword, don't try to expand
|
||||||
if (token.type != T_(ID) && token.type != T_(LOCAL_ID)) {
|
if (token.type != T_(SYMBOL) && token.type != T_(LOCAL)) {
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
// `token` is either an `ID` or a `LOCAL_ID`, and both have a `std::string` value.
|
// `token` is either a `SYMBOL` or a `LOCAL`, and both have a `std::string` value.
|
||||||
assume(token.value.holds<std::string>());
|
assume(token.value.holds<std::string>());
|
||||||
|
|
||||||
// Local symbols cannot be string expansions
|
// Raw symbols and local symbols cannot be string expansions
|
||||||
if (token.type == T_(ID) && lexerState->expandStrings) {
|
if (!raw && token.type == T_(SYMBOL) && lexerState->expandStrings) {
|
||||||
// Attempt string expansion
|
// Attempt string expansion
|
||||||
Symbol const *sym = sym_FindExactSymbol(token.value.get<std::string>());
|
Symbol const *sym = sym_FindExactSymbol(token.value.get<std::string>());
|
||||||
|
|
||||||
@@ -1925,18 +2048,18 @@ static Token yylex_NORMAL() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This is a "lexer hack"! We need it to distinguish between label definitions
|
// This is a "lexer hack"! We need it to distinguish between label definitions
|
||||||
// (which start with `LABEL`) and macro invocations (which start with `ID`).
|
// (which start with `LABEL`) and macro invocations (which start with `SYMBOL`).
|
||||||
//
|
//
|
||||||
// If we had one `IDENTIFIER` token, the parser would need to perform "lookahead"
|
// 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
|
// 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
|
// 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`.
|
// 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 ':'
|
// Instead, we have separate `SYMBOL` and `LABEL` tokens, lexing as a `LABEL` if a
|
||||||
// character *immediately* follows the identifier. Thus, at the beginning of a line,
|
// ':' character *immediately* follows the identifier. Thus, at the beginning of a
|
||||||
// "Label:" and "mac:" are treated as label definitions, but "Label :" and "mac :"
|
// line, "Label:" and "mac:" are treated as label definitions, but "Label :" and
|
||||||
// are treated as macro invocations.
|
// "mac :" are treated as macro invocations.
|
||||||
if (token.type == T_(ID) && peek() == ':') {
|
if (token.type == T_(SYMBOL) && peek() == ':') {
|
||||||
token.type = T_(LABEL);
|
token.type = T_(LABEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1945,8 +2068,19 @@ static Token yylex_NORMAL() {
|
|||||||
|
|
||||||
// Do not report weird characters when capturing, it'll be done later
|
// Do not report weird characters when capturing, it'll be done later
|
||||||
if (!lexerState->capturing) {
|
if (!lexerState->capturing) {
|
||||||
// TODO: try to group reportings
|
assume(isGarbageCharacter(c) || c == '#');
|
||||||
error("Unknown character %s\n", printChar(c));
|
if (isGarbageCharacter(peek())) {
|
||||||
|
// At least two characters are garbage; group them into one error report
|
||||||
|
std::string garbage = printChar(c);
|
||||||
|
while (isGarbageCharacter(peek())) {
|
||||||
|
c = nextChar();
|
||||||
|
garbage += ", ";
|
||||||
|
garbage += printChar(c);
|
||||||
|
}
|
||||||
|
error("Unknown characters %s\n", garbage.c_str());
|
||||||
|
} else {
|
||||||
|
error("Unknown character %s\n", printChar(c));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lexerState->atLineStart = false;
|
lexerState->atLineStart = false;
|
||||||
@@ -2090,7 +2224,7 @@ append:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
finish:
|
finish: // Can't `break` out of a nested `for`-`switch`
|
||||||
// Trim right whitespace
|
// Trim right whitespace
|
||||||
auto rightPos = std::find_if_not(str.rbegin(), str.rend(), isWhitespace);
|
auto rightPos = std::find_if_not(str.rbegin(), str.rend(), isWhitespace);
|
||||||
str.resize(rightPos.base() - str.begin());
|
str.resize(rightPos.base() - str.begin());
|
||||||
@@ -2158,7 +2292,8 @@ static Token skipIfBlock(bool toEndc) {
|
|||||||
|
|
||||||
case T_(POP_ELIF):
|
case T_(POP_ELIF):
|
||||||
if (lexer_ReachedELSEBlock()) {
|
if (lexer_ReachedELSEBlock()) {
|
||||||
fatalerror("Found ELIF after an ELSE block\n");
|
// This should be redundant, as the parser handles this error first.
|
||||||
|
fatalerror("Found ELIF after an ELSE block\n"); // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
if (!toEndc && lexer_GetIFDepth() == startingDepth) {
|
if (!toEndc && lexer_GetIFDepth() == startingDepth) {
|
||||||
return token;
|
return token;
|
||||||
@@ -2250,9 +2385,8 @@ static Token yylex_SKIP_TO_ENDR() {
|
|||||||
|
|
||||||
case T_(POP_ENDR):
|
case T_(POP_ENDR):
|
||||||
depth--;
|
depth--;
|
||||||
if (!depth) {
|
// `lexer_CaptureRept` has already guaranteed that the `ENDR`s are balanced
|
||||||
return Token(T_(YYEOF)); // yywrap() will finish the REPT/FOR loop
|
assume(depth > 0);
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case T_(POP_IF):
|
case T_(POP_IF):
|
||||||
@@ -2390,7 +2524,7 @@ Capture lexer_CaptureRept() {
|
|||||||
do { // Discard initial whitespace
|
do { // Discard initial whitespace
|
||||||
c = nextChar();
|
c = nextChar();
|
||||||
} while (isWhitespace(c));
|
} while (isWhitespace(c));
|
||||||
// Now, try to match `REPT`, `FOR` or `ENDR` as a **whole** identifier
|
// Now, try to match `REPT`, `FOR` or `ENDR` as a **whole** keyword
|
||||||
if (startsIdentifier(c)) {
|
if (startsIdentifier(c)) {
|
||||||
switch (readIdentifier(c, false).type) {
|
switch (readIdentifier(c, false).type) {
|
||||||
case T_(POP_REPT):
|
case T_(POP_REPT):
|
||||||
@@ -2443,7 +2577,7 @@ Capture lexer_CaptureMacro() {
|
|||||||
do { // Discard initial whitespace
|
do { // Discard initial whitespace
|
||||||
c = nextChar();
|
c = nextChar();
|
||||||
} while (isWhitespace(c));
|
} while (isWhitespace(c));
|
||||||
// Now, try to match `ENDM` as a **whole** identifier
|
// Now, try to match `ENDM` as a **whole** keyword
|
||||||
if (startsIdentifier(c)) {
|
if (startsIdentifier(c)) {
|
||||||
switch (readIdentifier(c, false).type) {
|
switch (readIdentifier(c, false).type) {
|
||||||
case T_(POP_ENDM):
|
case T_(POP_ENDM):
|
||||||
|
|||||||
@@ -8,10 +8,16 @@
|
|||||||
|
|
||||||
#include "asm/warning.hpp"
|
#include "asm/warning.hpp"
|
||||||
|
|
||||||
std::shared_ptr<std::string> MacroArgs::getArg(uint32_t i) const {
|
std::shared_ptr<std::string> MacroArgs::getArg(int32_t i) const {
|
||||||
uint32_t realIndex = i + shift - 1;
|
// Bracketed macro arguments adjust negative indexes such that -1 is the last argument.
|
||||||
|
if (i < 0) {
|
||||||
|
i += args.size() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
return realIndex >= args.size() ? nullptr : args[realIndex];
|
int32_t realIndex = i + shift - 1;
|
||||||
|
|
||||||
|
return realIndex < 0 || static_cast<uint32_t>(realIndex) >= args.size() ? nullptr
|
||||||
|
: args[realIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<std::string> MacroArgs::getAllArgs() const {
|
std::shared_ptr<std::string> MacroArgs::getAllArgs() const {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ static std::string make_escape(std::string &str) {
|
|||||||
size_t pos = 0;
|
size_t pos = 0;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
// All dollars needs to be doubled
|
// All dollars needs to be doubled
|
||||||
size_t nextPos = str.find("$", pos);
|
size_t nextPos = str.find('$', pos);
|
||||||
if (nextPos == std::string::npos) {
|
if (nextPos == std::string::npos) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -87,6 +87,7 @@ static option const longopts[] = {
|
|||||||
{nullptr, no_argument, nullptr, 0 }
|
{nullptr, no_argument, nullptr, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
static void printUsage() {
|
static void printUsage() {
|
||||||
fputs(
|
fputs(
|
||||||
"Usage: rgbasm [-EhVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n"
|
"Usage: rgbasm [-EhVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n"
|
||||||
@@ -107,6 +108,7 @@ static void printUsage() {
|
|||||||
stderr
|
stderr
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
time_t now = time(nullptr);
|
time_t now = time(nullptr);
|
||||||
@@ -176,8 +178,10 @@ int main(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'h':
|
case 'h':
|
||||||
|
// LCOV_EXCL_START
|
||||||
printUsage();
|
printUsage();
|
||||||
exit(0);
|
exit(0);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
case 'I':
|
case 'I':
|
||||||
fstk_AddIncludePath(musl_optarg);
|
fstk_AddIncludePath(musl_optarg);
|
||||||
@@ -195,7 +199,7 @@ int main(int argc, char *argv[]) {
|
|||||||
dependFileName = "<stdout>";
|
dependFileName = "<stdout>";
|
||||||
}
|
}
|
||||||
if (dependFile == nullptr) {
|
if (dependFile == nullptr) {
|
||||||
err("Failed to open dependfile \"%s\"", dependFileName);
|
err("Failed to open dependfile \"%s\"", dependFileName); // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -302,9 +306,11 @@ int main(int argc, char *argv[]) {
|
|||||||
if (stateFileSpecs.find(name) != stateFileSpecs.end()) {
|
if (stateFileSpecs.find(name) != stateFileSpecs.end()) {
|
||||||
warnx("Overriding state filename %s", name);
|
warnx("Overriding state filename %s", name);
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
printf("State filename %s\n", name);
|
printf("State filename %s\n", name);
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
stateFileSpecs.emplace(name, std::move(features));
|
stateFileSpecs.emplace(name, std::move(features));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -314,8 +320,10 @@ int main(int argc, char *argv[]) {
|
|||||||
exit(0);
|
exit(0);
|
||||||
|
|
||||||
case 'v':
|
case 'v':
|
||||||
|
// LCOV_EXCL_START
|
||||||
verbose = true;
|
verbose = true;
|
||||||
break;
|
break;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
case 'W':
|
case 'W':
|
||||||
opt_W(musl_optarg);
|
opt_W(musl_optarg);
|
||||||
@@ -367,8 +375,10 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
// Unrecognized options
|
// Unrecognized options
|
||||||
default:
|
default:
|
||||||
|
// LCOV_EXCL_START
|
||||||
printUsage();
|
printUsage();
|
||||||
exit(1);
|
exit(1);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,7 +401,7 @@ int main(int argc, char *argv[]) {
|
|||||||
std::string mainFileName = argv[musl_optind];
|
std::string mainFileName = argv[musl_optind];
|
||||||
|
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
printf("Assembling %s\n", mainFileName.c_str());
|
printf("Assembling %s\n", mainFileName.c_str()); // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dependFile) {
|
if (dependFile) {
|
||||||
|
|||||||
@@ -14,8 +14,8 @@
|
|||||||
#include "asm/warning.hpp"
|
#include "asm/warning.hpp"
|
||||||
|
|
||||||
struct OptStackEntry {
|
struct OptStackEntry {
|
||||||
char binary[2];
|
char binDigits[2];
|
||||||
char gbgfx[4];
|
char gfxDigits[4];
|
||||||
uint8_t fixPrecision;
|
uint8_t fixPrecision;
|
||||||
uint8_t fillByte;
|
uint8_t fillByte;
|
||||||
bool warningsAreErrors;
|
bool warningsAreErrors;
|
||||||
@@ -151,13 +151,8 @@ void opt_Push() {
|
|||||||
OptStackEntry entry;
|
OptStackEntry entry;
|
||||||
|
|
||||||
// Both of these are pulled from lexer.hpp
|
// Both of these are pulled from lexer.hpp
|
||||||
entry.binary[0] = binDigits[0];
|
memcpy(entry.binDigits, binDigits, std::size(binDigits));
|
||||||
entry.binary[1] = binDigits[1];
|
memcpy(entry.gfxDigits, gfxDigits, std::size(gfxDigits));
|
||||||
|
|
||||||
entry.gbgfx[0] = gfxDigits[0];
|
|
||||||
entry.gbgfx[1] = gfxDigits[1];
|
|
||||||
entry.gbgfx[2] = gfxDigits[2];
|
|
||||||
entry.gbgfx[3] = gfxDigits[3];
|
|
||||||
|
|
||||||
entry.fixPrecision = fixPrecision; // Pulled from fixpoint.hpp
|
entry.fixPrecision = fixPrecision; // Pulled from fixpoint.hpp
|
||||||
|
|
||||||
@@ -181,8 +176,8 @@ void opt_Pop() {
|
|||||||
OptStackEntry entry = stack.top();
|
OptStackEntry entry = stack.top();
|
||||||
stack.pop();
|
stack.pop();
|
||||||
|
|
||||||
opt_B(entry.binary);
|
opt_B(entry.binDigits);
|
||||||
opt_G(entry.gbgfx);
|
opt_G(entry.gfxDigits);
|
||||||
opt_P(entry.fillByte);
|
opt_P(entry.fillByte);
|
||||||
opt_Q(entry.fixPrecision);
|
opt_Q(entry.fixPrecision);
|
||||||
opt_R(entry.maxRecursionDepth);
|
opt_R(entry.maxRecursionDepth);
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ void out_RegisterNode(std::shared_ptr<FileStackNode> node) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a section's ID, or UINT32_MAX if the section is not in the list
|
// Return a section's ID, or UINT32_MAX if the section does not exist
|
||||||
static uint32_t getSectIDIfAny(Section *sect) {
|
static uint32_t getSectIDIfAny(Section *sect) {
|
||||||
if (!sect) {
|
if (!sect) {
|
||||||
return UINT32_MAX;
|
return UINT32_MAX;
|
||||||
@@ -71,7 +71,8 @@ static uint32_t getSectIDIfAny(Section *sect) {
|
|||||||
return static_cast<uint32_t>(search->second);
|
return static_cast<uint32_t>(search->second);
|
||||||
}
|
}
|
||||||
|
|
||||||
fatalerror("Unknown section '%s'\n", sect->name.c_str());
|
// Every section that exists should be in `sectionMap`
|
||||||
|
fatalerror("Unknown section '%s'\n", sect->name.c_str()); // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
|
|
||||||
static void writePatch(Patch const &patch, FILE *file) {
|
static void writePatch(Patch const &patch, FILE *file) {
|
||||||
@@ -175,7 +176,7 @@ static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &
|
|||||||
sym = sym_FindExactSymbol(symName);
|
sym = sym_FindExactSymbol(symName);
|
||||||
if (sym->isConstant()) {
|
if (sym->isConstant()) {
|
||||||
rpnexpr[rpnptr++] = RPN_CONST;
|
rpnexpr[rpnptr++] = RPN_CONST;
|
||||||
value = sym_GetConstantValue(symName);
|
value = sym->getConstantValue();
|
||||||
} else {
|
} else {
|
||||||
rpnexpr[rpnptr++] = RPN_SYM;
|
rpnexpr[rpnptr++] = RPN_SYM;
|
||||||
registerUnregisteredSymbol(*sym); // Ensure that `sym->ID` is set
|
registerUnregisteredSymbol(*sym); // Ensure that `sym->ID` is set
|
||||||
@@ -323,7 +324,7 @@ void out_WriteObject() {
|
|||||||
file = stdout;
|
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()); // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
Defer closeFile{[&] { fclose(file); }};
|
Defer closeFile{[&] { fclose(file); }};
|
||||||
|
|
||||||
@@ -343,14 +344,7 @@ void out_WriteObject() {
|
|||||||
writeFileStackNode(node, file);
|
writeFileStackNode(node, file);
|
||||||
|
|
||||||
// The list is supposed to have decrementing IDs
|
// The list is supposed to have decrementing IDs
|
||||||
if (it + 1 != fileStackNodes.end() && it[1]->ID != node.ID - 1) {
|
assume(it + 1 == fileStackNodes.end() || it[1]->ID == node.ID - 1);
|
||||||
fatalerror(
|
|
||||||
"Internal error: fstack node #%" PRIu32 " follows #%" PRIu32
|
|
||||||
". Please report this to the developers!\n",
|
|
||||||
it[1]->ID,
|
|
||||||
node.ID
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Symbol const *sym : objectSymbols) {
|
for (Symbol const *sym : objectSymbols) {
|
||||||
@@ -373,9 +367,11 @@ void out_SetFileName(std::string const &name) {
|
|||||||
warnx("Overriding output filename %s", objectFileName.c_str());
|
warnx("Overriding output filename %s", objectFileName.c_str());
|
||||||
}
|
}
|
||||||
objectFileName = name;
|
objectFileName = name;
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
printf("Output filename %s\n", objectFileName.c_str());
|
printf("Output filename %s\n", objectFileName.c_str());
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dumpString(std::string const &escape, FILE *file) {
|
static void dumpString(std::string const &escape, FILE *file) {
|
||||||
@@ -528,7 +524,7 @@ void out_WriteState(std::string name, std::vector<StateFeature> const &features)
|
|||||||
file = stdout;
|
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()); // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
Defer closeFile{[&] { fclose(file); }};
|
Defer closeFile{[&] { fclose(file); }};
|
||||||
|
|
||||||
|
|||||||
595
src/asm/parser.y
595
src/asm/parser.y
File diff suppressed because it is too large
Load Diff
100
src/asm/rpn.cpp
100
src/asm/rpn.cpp
@@ -75,6 +75,9 @@ void Expression::makeSymbol(std::string const &symName) {
|
|||||||
if (Symbol *sym = sym_FindScopedSymbol(symName); sym_IsPC(sym) && !sect_GetSymbolSection()) {
|
if (Symbol *sym = sym_FindScopedSymbol(symName); sym_IsPC(sym) && !sect_GetSymbolSection()) {
|
||||||
error("PC has no value outside of a section\n");
|
error("PC has no value outside of a section\n");
|
||||||
data = 0;
|
data = 0;
|
||||||
|
} else if (sym && !sym->isNumeric() && !sym->isLabel()) {
|
||||||
|
error("'%s' is not a numeric symbol\n", symName.c_str());
|
||||||
|
data = 0;
|
||||||
} else if (!sym || !sym->isConstant()) {
|
} else if (!sym || !sym->isConstant()) {
|
||||||
isSymbol = true;
|
isSymbol = true;
|
||||||
|
|
||||||
@@ -91,7 +94,7 @@ void Expression::makeSymbol(std::string const &symName) {
|
|||||||
*ptr++ = RPN_SYM;
|
*ptr++ = RPN_SYM;
|
||||||
memcpy(ptr, sym->name.c_str(), nameLen);
|
memcpy(ptr, sym->name.c_str(), nameLen);
|
||||||
} else {
|
} else {
|
||||||
data = static_cast<int32_t>(sym_GetConstantValue(symName));
|
data = static_cast<int32_t>(sym->getConstantValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,40 +325,12 @@ void Expression::makeUnaryOp(RPNCommand op, Expression &&src) {
|
|||||||
case RPN_TZCOUNT:
|
case RPN_TZCOUNT:
|
||||||
data = val != 0 ? ctz(uval) : 32;
|
data = val != 0 ? ctz(uval) : 32;
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
case RPN_LOGOR:
|
// `makeUnaryOp` should never be called with a non-unary operator!
|
||||||
case RPN_LOGAND:
|
// LCOV_EXCL_START
|
||||||
case RPN_LOGEQ:
|
unreachable_();
|
||||||
case RPN_LOGGT:
|
|
||||||
case RPN_LOGLT:
|
|
||||||
case RPN_LOGGE:
|
|
||||||
case RPN_LOGLE:
|
|
||||||
case RPN_LOGNE:
|
|
||||||
case RPN_ADD:
|
|
||||||
case RPN_SUB:
|
|
||||||
case RPN_XOR:
|
|
||||||
case RPN_OR:
|
|
||||||
case RPN_AND:
|
|
||||||
case RPN_SHL:
|
|
||||||
case RPN_SHR:
|
|
||||||
case RPN_USHR:
|
|
||||||
case RPN_MUL:
|
|
||||||
case RPN_DIV:
|
|
||||||
case RPN_MOD:
|
|
||||||
case RPN_EXP:
|
|
||||||
case RPN_BANK_SYM:
|
|
||||||
case RPN_BANK_SECT:
|
|
||||||
case RPN_BANK_SELF:
|
|
||||||
case RPN_SIZEOF_SECT:
|
|
||||||
case RPN_STARTOF_SECT:
|
|
||||||
case RPN_SIZEOF_SECTTYPE:
|
|
||||||
case RPN_STARTOF_SECTTYPE:
|
|
||||||
case RPN_HRAM:
|
|
||||||
case RPN_RST:
|
|
||||||
case RPN_CONST:
|
|
||||||
case RPN_SYM:
|
|
||||||
fatalerror("%d is not an unary operator\n", op);
|
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
} else if (op == RPN_LOGNOT && tryConstLogNot(src)) {
|
} else if (op == RPN_LOGNOT && tryConstLogNot(src)) {
|
||||||
data = 0;
|
data = 0;
|
||||||
} else if (int32_t constVal; op == RPN_LOW && (constVal = tryConstLow(src)) != -1) {
|
} else if (int32_t constVal; op == RPN_LOW && (constVal = tryConstLow(src)) != -1) {
|
||||||
@@ -498,27 +473,12 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
|||||||
|
|
||||||
data = op_exponent(lval, rval);
|
data = op_exponent(lval, rval);
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
case RPN_NEG:
|
// `makeBinaryOp` should never be called with a non-binary operator!
|
||||||
case RPN_NOT:
|
// LCOV_EXCL_START
|
||||||
case RPN_LOGNOT:
|
unreachable_();
|
||||||
case RPN_BANK_SYM:
|
|
||||||
case RPN_BANK_SECT:
|
|
||||||
case RPN_BANK_SELF:
|
|
||||||
case RPN_SIZEOF_SECT:
|
|
||||||
case RPN_STARTOF_SECT:
|
|
||||||
case RPN_SIZEOF_SECTTYPE:
|
|
||||||
case RPN_STARTOF_SECTTYPE:
|
|
||||||
case RPN_HRAM:
|
|
||||||
case RPN_RST:
|
|
||||||
case RPN_HIGH:
|
|
||||||
case RPN_LOW:
|
|
||||||
case RPN_BITWIDTH:
|
|
||||||
case RPN_TZCOUNT:
|
|
||||||
case RPN_CONST:
|
|
||||||
case RPN_SYM:
|
|
||||||
fatalerror("%d is not a binary operator\n", op);
|
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
} else if (op == RPN_SUB && src1.isDiffConstant(src2.symbolOf())) {
|
} else if (op == RPN_SUB && src1.isDiffConstant(src2.symbolOf())) {
|
||||||
data = src1.symbolOf()->getValue() - src2.symbolOf()->getValue();
|
data = src1.symbolOf()->getValue() - src2.symbolOf()->getValue();
|
||||||
} else if ((op == RPN_LOGAND || op == RPN_AND) && tryConstZero(src1, src2)) {
|
} else if ((op == RPN_LOGAND || op == RPN_AND) && tryConstZero(src1, src2)) {
|
||||||
@@ -605,10 +565,24 @@ void Expression::makeCheckRST() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Expression::makeCheckBitIndex(uint8_t mask) {
|
||||||
|
assume((mask & 0xC0) != 0x00); // The high two bits must correspond to BIT, RES, or SET
|
||||||
|
|
||||||
|
if (!isKnown()) {
|
||||||
|
uint8_t *ptr = reserveSpace(2);
|
||||||
|
*ptr++ = RPN_BIT_INDEX;
|
||||||
|
*ptr = mask;
|
||||||
|
} else if (int32_t val = value(); val & ~0x07) {
|
||||||
|
// A valid bit index must be masked with 0x07
|
||||||
|
static char const *instructions[4] = {"instruction", "BIT", "RES", "SET"};
|
||||||
|
error("Invalid bit index %" PRId32 " for %s\n", val, instructions[mask >> 6]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Checks that an RPN expression's value fits within N bits (signed or unsigned)
|
// Checks that an RPN expression's value fits within N bits (signed or unsigned)
|
||||||
void Expression::checkNBit(uint8_t n) const {
|
void Expression::checkNBit(uint8_t n) const {
|
||||||
if (isKnown()) {
|
if (isKnown()) {
|
||||||
::checkNBit(value(), n, "Expression");
|
::checkNBit(value(), n, nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,11 +591,23 @@ bool checkNBit(int32_t v, uint8_t n, char const *name) {
|
|||||||
assume(n < CHAR_BIT * sizeof(int)); // Otherwise `1 << n` is UB
|
assume(n < CHAR_BIT * sizeof(int)); // Otherwise `1 << n` is UB
|
||||||
|
|
||||||
if (v < -(1 << n) || v >= 1 << n) {
|
if (v < -(1 << n) || v >= 1 << n) {
|
||||||
warning(WARNING_TRUNCATION_1, "%s must be %u-bit\n", name, n);
|
warning(
|
||||||
|
WARNING_TRUNCATION_1,
|
||||||
|
n == 8 && !name ? "%s must be %u-bit; use LOW() to force 8-bit\n"
|
||||||
|
: "%s must be %u-bit\n",
|
||||||
|
name ? name : "Expression",
|
||||||
|
n
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (v < -(1 << (n - 1))) {
|
if (v < -(1 << (n - 1))) {
|
||||||
warning(WARNING_TRUNCATION_2, "%s must be %u-bit\n", name, n);
|
warning(
|
||||||
|
WARNING_TRUNCATION_2,
|
||||||
|
n == 8 && !name ? "%s must be %u-bit; use LOW() to force 8-bit\n"
|
||||||
|
: "%s must be %u-bit\n",
|
||||||
|
name ? name : "Expression",
|
||||||
|
n
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -768,7 +768,7 @@ void sect_Skip(uint32_t skip, bool ds) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Output a byte that can be relocatable or constant
|
// Output a byte that can be relocatable or constant
|
||||||
void sect_RelByte(Expression &expr, uint32_t pcShift) {
|
void sect_RelByte(Expression const &expr, uint32_t pcShift) {
|
||||||
if (!requireCodeSection()) {
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -782,15 +782,13 @@ void sect_RelByte(Expression &expr, uint32_t pcShift) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Output several bytes that can be relocatable or constant
|
// Output several bytes that can be relocatable or constant
|
||||||
void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs) {
|
void sect_RelBytes(uint32_t n, std::vector<Expression> const &exprs) {
|
||||||
if (!requireCodeSection()) {
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (uint32_t i = 0; i < n; i++) {
|
for (uint32_t i = 0; i < n; i++) {
|
||||||
Expression &expr = exprs[i % exprs.size()];
|
if (Expression const &expr = exprs[i % exprs.size()]; !expr.isKnown()) {
|
||||||
|
|
||||||
if (!expr.isKnown()) {
|
|
||||||
createPatch(PATCHTYPE_BYTE, expr, i);
|
createPatch(PATCHTYPE_BYTE, expr, i);
|
||||||
writeByte(0);
|
writeByte(0);
|
||||||
} else {
|
} else {
|
||||||
@@ -800,7 +798,7 @@ void sect_RelBytes(uint32_t n, std::vector<Expression> &exprs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Output a word that can be relocatable or constant
|
// Output a word that can be relocatable or constant
|
||||||
void sect_RelWord(Expression &expr, uint32_t pcShift) {
|
void sect_RelWord(Expression const &expr, uint32_t pcShift) {
|
||||||
if (!requireCodeSection()) {
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -814,7 +812,7 @@ void sect_RelWord(Expression &expr, uint32_t pcShift) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Output a long that can be relocatable or constant
|
// Output a long that can be relocatable or constant
|
||||||
void sect_RelLong(Expression &expr, uint32_t pcShift) {
|
void sect_RelLong(Expression const &expr, uint32_t pcShift) {
|
||||||
if (!requireCodeSection()) {
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -828,7 +826,7 @@ void sect_RelLong(Expression &expr, uint32_t pcShift) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Output a PC-relative byte that can be relocatable or constant
|
// Output a PC-relative byte that can be relocatable or constant
|
||||||
void sect_PCRelByte(Expression &expr, uint32_t pcShift) {
|
void sect_PCRelByte(Expression const &expr, uint32_t pcShift) {
|
||||||
if (!requireCodeSection()) {
|
if (!requireCodeSection()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -877,9 +875,11 @@ void sect_BinaryFile(std::string const &name, int32_t startPos) {
|
|||||||
}
|
}
|
||||||
if (!file) {
|
if (!file) {
|
||||||
if (generatedMissingIncludes) {
|
if (generatedMissingIncludes) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
|
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
failedOnMissingInclude = true;
|
failedOnMissingInclude = true;
|
||||||
} else {
|
} else {
|
||||||
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
||||||
@@ -944,9 +944,11 @@ void sect_BinaryFileSlice(std::string const &name, int32_t startPos, int32_t len
|
|||||||
}
|
}
|
||||||
if (!file) {
|
if (!file) {
|
||||||
if (generatedMissingIncludes) {
|
if (generatedMissingIncludes) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
|
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", name.c_str(), strerror(errno));
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
failedOnMissingInclude = true;
|
failedOnMissingInclude = true;
|
||||||
} else {
|
} else {
|
||||||
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
error("Error opening INCBIN file '%s': %s\n", name.c_str(), strerror(errno));
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "asm/symbol.hpp"
|
#include "asm/symbol.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@@ -9,6 +10,7 @@
|
|||||||
|
|
||||||
#include "error.hpp"
|
#include "error.hpp"
|
||||||
#include "helpers.hpp" // assume
|
#include "helpers.hpp" // assume
|
||||||
|
#include "util.hpp"
|
||||||
#include "version.hpp"
|
#include "version.hpp"
|
||||||
|
|
||||||
#include "asm/fstack.hpp"
|
#include "asm/fstack.hpp"
|
||||||
@@ -130,6 +132,11 @@ static void updateSymbolFilename(Symbol &sym) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool isValidIdentifier(std::string const &s) {
|
||||||
|
return !s.empty() && startsIdentifier(s[0])
|
||||||
|
&& std::all_of(s.begin() + 1, s.end(), [](char c) { return continuesIdentifier(c); });
|
||||||
|
}
|
||||||
|
|
||||||
static void alreadyDefinedError(Symbol const &sym, char const *asType) {
|
static void alreadyDefinedError(Symbol const &sym, char const *asType) {
|
||||||
if (sym.isBuiltin && !sym_FindScopedValidSymbol(sym.name)) {
|
if (sym.isBuiltin && !sym_FindScopedValidSymbol(sym.name)) {
|
||||||
// `DEF()` would return false, so we should not claim the symbol is already defined
|
// `DEF()` would return false, so we should not claim the symbol is already defined
|
||||||
@@ -141,6 +148,15 @@ static void alreadyDefinedError(Symbol const &sym, char const *asType) {
|
|||||||
}
|
}
|
||||||
fputs(" at ", stderr);
|
fputs(" at ", stderr);
|
||||||
dumpFilename(sym);
|
dumpFilename(sym);
|
||||||
|
if (sym.type == SYM_EQUS) {
|
||||||
|
if (std::string const &contents = *sym.getEqus(); isValidIdentifier(contents)) {
|
||||||
|
fprintf(
|
||||||
|
stderr,
|
||||||
|
" (should it be {interpolated} to define its contents \"%s\"?)\n",
|
||||||
|
contents.c_str()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,19 +341,6 @@ uint32_t Symbol::getConstantValue() const {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t sym_GetConstantValue(std::string const &symName) {
|
|
||||||
if (Symbol const *sym = sym_FindScopedSymbol(symName); sym) {
|
|
||||||
return sym->getConstantValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sym_IsPurgedScoped(symName)) {
|
|
||||||
error("'%s' not defined; it was purged\n", symName.c_str());
|
|
||||||
} else {
|
|
||||||
error("'%s' not defined\n", symName.c_str());
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair<Symbol const *, Symbol const *> sym_GetCurrentLabelScopes() {
|
std::pair<Symbol const *, Symbol const *> sym_GetCurrentLabelScopes() {
|
||||||
return {globalScope, localScope};
|
return {globalScope, localScope};
|
||||||
}
|
}
|
||||||
@@ -528,8 +531,10 @@ static uint32_t anonLabelID = 0;
|
|||||||
|
|
||||||
Symbol *sym_AddAnonLabel() {
|
Symbol *sym_AddAnonLabel() {
|
||||||
if (anonLabelID == UINT32_MAX) {
|
if (anonLabelID == UINT32_MAX) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
error("Only %" PRIu32 " anonymous labels can be created!", anonLabelID);
|
error("Only %" PRIu32 " anonymous labels can be created!", anonLabelID);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string anon = sym_MakeAnonLabelName(0, true); // The direction is important!
|
std::string anon = sym_MakeAnonLabelName(0, true); // The direction is important!
|
||||||
@@ -555,6 +560,7 @@ std::string sym_MakeAnonLabelName(uint32_t ofs, bool neg) {
|
|||||||
} else {
|
} else {
|
||||||
ofs--; // We're referencing symbols that haven't been created yet...
|
ofs--; // We're referencing symbols that haven't been created yet...
|
||||||
if (ofs > UINT32_MAX - anonLabelID) {
|
if (ofs > UINT32_MAX - anonLabelID) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
error(
|
error(
|
||||||
"Reference to anonymous label %" PRIu32 " after, when only %" PRIu32
|
"Reference to anonymous label %" PRIu32 " after, when only %" PRIu32
|
||||||
" may still be created\n",
|
" may still be created\n",
|
||||||
@@ -562,19 +568,21 @@ std::string sym_MakeAnonLabelName(uint32_t ofs, bool neg) {
|
|||||||
UINT32_MAX - anonLabelID
|
UINT32_MAX - anonLabelID
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
id = anonLabelID + ofs;
|
id = anonLabelID + ofs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string anon("!");
|
return "!"s + std::to_string(id);
|
||||||
anon += std::to_string(id);
|
|
||||||
return anon;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void sym_Export(std::string const &symName) {
|
void sym_Export(std::string const &symName) {
|
||||||
if (symName.starts_with('!')) {
|
if (symName.starts_with('!')) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
|
// The parser does not accept anonymous labels for an `EXPORT` directive
|
||||||
error("Anonymous labels cannot be exported\n");
|
error("Anonymous labels cannot be exported\n");
|
||||||
return;
|
return;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
Symbol *sym = sym_FindScopedSymbol(symName);
|
Symbol *sym = sym_FindScopedSymbol(symName);
|
||||||
@@ -656,11 +664,13 @@ void sym_Init(time_t now) {
|
|||||||
sym_AddEqu("__RGBDS_RC__"s, PACKAGE_VERSION_RC)->isBuiltin = true;
|
sym_AddEqu("__RGBDS_RC__"s, PACKAGE_VERSION_RC)->isBuiltin = true;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (now == static_cast<time_t>(-1)) {
|
if (now == static_cast<time_t>(-1)) {
|
||||||
warn("Failed to determine current time");
|
warn("Failed to determine current time");
|
||||||
// Fall back by pretending we are at the Epoch
|
// Fall back by pretending we are at the Epoch
|
||||||
now = 0;
|
now = 0;
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
tm const *time_local = localtime(&now);
|
tm const *time_local = localtime(&now);
|
||||||
|
|
||||||
|
|||||||
11
src/bison.sh
11
src/bison.sh
@@ -12,17 +12,20 @@ if [ "$BISON_MAJOR" -lt 3 ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
BISON_FLAGS="-Wall -Dparse.lac=full -Dlr.type=ielr"
|
BISON_FLAGS="-Wall -Dlr.type=ielr"
|
||||||
|
|
||||||
# Set some optimization flags on versions that support them
|
# Set some optimization flags on versions that support them
|
||||||
if [ "$BISON_MAJOR" -eq 4 ] || [ "$BISON_MAJOR" -eq 3 ] && [ "$BISON_MINOR" -ge 5 ]; then
|
if [ "$BISON_MAJOR" -ge 4 ] || [ "$BISON_MAJOR" -eq 3 ] && [ "$BISON_MINOR" -ge 5 ]; then
|
||||||
BISON_FLAGS="$BISON_FLAGS -Dapi.token.raw=true"
|
BISON_FLAGS="$BISON_FLAGS -Dparse.lac=full -Dapi.token.raw=true"
|
||||||
fi
|
fi
|
||||||
if [ "$BISON_MAJOR" -eq 4 ] || [ "$BISON_MAJOR" -eq 3 ] && [ "$BISON_MINOR" -ge 6 ]; then
|
if [ "$BISON_MAJOR" -ge 4 ] || [ "$BISON_MAJOR" -eq 3 ] && [ "$BISON_MINOR" -ge 6 ]; then
|
||||||
BISON_FLAGS="$BISON_FLAGS -Dparse.error=detailed"
|
BISON_FLAGS="$BISON_FLAGS -Dparse.error=detailed"
|
||||||
else
|
else
|
||||||
BISON_FLAGS="$BISON_FLAGS -Dparse.error=verbose"
|
BISON_FLAGS="$BISON_FLAGS -Dparse.error=verbose"
|
||||||
fi
|
fi
|
||||||
|
if [ "$BISON_MAJOR" -ge 4 ] || [ "$BISON_MAJOR" -eq 3 ] && [ "$BISON_MINOR" -ge 7 ]; then
|
||||||
|
BISON_FLAGS="$BISON_FLAGS -Wcounterexamples"
|
||||||
|
fi
|
||||||
|
|
||||||
# Replace the arguments to this script ($@) with the ones in $BISON_FLAGS
|
# Replace the arguments to this script ($@) with the ones in $BISON_FLAGS
|
||||||
eval "set -- $BISON_FLAGS"
|
eval "set -- $BISON_FLAGS"
|
||||||
|
|||||||
115
src/fix/main.cpp
115
src/fix/main.cpp
@@ -23,7 +23,7 @@ static_assert(UNSPECIFIED > 0xFF, "UNSPECIFIED should not be in byte range!");
|
|||||||
static constexpr off_t BANK_SIZE = 0x4000;
|
static constexpr off_t BANK_SIZE = 0x4000;
|
||||||
|
|
||||||
// Short options
|
// Short options
|
||||||
static char const *optstring = "Ccf:hi:jk:L:l:m:n:Op:r:st:Vv";
|
static char const *optstring = "Ccf:hi:jk:L:l:m:n:Oo:p:r:st:Vv";
|
||||||
|
|
||||||
// Equivalent long options
|
// Equivalent long options
|
||||||
// Please keep in the same order as short opts.
|
// Please keep in the same order as short opts.
|
||||||
@@ -45,6 +45,7 @@ static option const longopts[] = {
|
|||||||
{"mbc-type", required_argument, nullptr, 'm'},
|
{"mbc-type", required_argument, nullptr, 'm'},
|
||||||
{"rom-version", required_argument, nullptr, 'n'},
|
{"rom-version", required_argument, nullptr, 'n'},
|
||||||
{"overwrite", no_argument, nullptr, 'O'},
|
{"overwrite", no_argument, nullptr, 'O'},
|
||||||
|
{"output", required_argument, nullptr, 'o'},
|
||||||
{"pad-value", required_argument, nullptr, 'p'},
|
{"pad-value", required_argument, nullptr, 'p'},
|
||||||
{"ram-size", required_argument, nullptr, 'r'},
|
{"ram-size", required_argument, nullptr, 'r'},
|
||||||
{"sgb-compatible", no_argument, nullptr, 's'},
|
{"sgb-compatible", no_argument, nullptr, 's'},
|
||||||
@@ -54,6 +55,7 @@ static option const longopts[] = {
|
|||||||
{nullptr, no_argument, nullptr, 0 }
|
{nullptr, no_argument, nullptr, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
static void printUsage() {
|
static void printUsage() {
|
||||||
fputs(
|
fputs(
|
||||||
"Usage: rgbfix [-hjOsVv] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n"
|
"Usage: rgbfix [-hjOsVv] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n"
|
||||||
@@ -65,6 +67,7 @@ static void printUsage() {
|
|||||||
" to the man page for a list of values\n"
|
" to the man page for a list of values\n"
|
||||||
" -p, --pad-value <value> pad to the next valid size using this value\n"
|
" -p, --pad-value <value> pad to the next valid size using this value\n"
|
||||||
" -r, --ram-size <code> set the cart RAM size byte to this value\n"
|
" -r, --ram-size <code> set the cart RAM size byte to this value\n"
|
||||||
|
" -o, --output <path> set the output file\n"
|
||||||
" -V, --version print RGBFIX version and exit\n"
|
" -V, --version print RGBFIX version and exit\n"
|
||||||
" -v, --validate fix the header logo and both checksums (-f lhg)\n"
|
" -v, --validate fix the header logo and both checksums (-f lhg)\n"
|
||||||
"\n"
|
"\n"
|
||||||
@@ -72,6 +75,7 @@ static void printUsage() {
|
|||||||
stderr
|
stderr
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
static uint8_t nbErrors;
|
static uint8_t nbErrors;
|
||||||
|
|
||||||
@@ -696,10 +700,12 @@ static char const *mbcName(MbcType type) {
|
|||||||
case MBC_WRONG_FEATURES:
|
case MBC_WRONG_FEATURES:
|
||||||
case MBC_BAD_RANGE:
|
case MBC_BAD_RANGE:
|
||||||
case MBC_BAD_TPP1:
|
case MBC_BAD_TPP1:
|
||||||
|
// LCOV_EXCL_START
|
||||||
unreachable_();
|
unreachable_();
|
||||||
}
|
}
|
||||||
|
|
||||||
unreachable_();
|
unreachable_();
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool hasRAM(MbcType type) {
|
static bool hasRAM(MbcType type) {
|
||||||
@@ -713,8 +719,7 @@ static bool hasRAM(MbcType type) {
|
|||||||
case MBC3_TIMER_BATTERY:
|
case MBC3_TIMER_BATTERY:
|
||||||
case MBC5:
|
case MBC5:
|
||||||
case MBC5_RUMBLE:
|
case MBC5_RUMBLE:
|
||||||
case MBC6: // TODO: not sure
|
case BANDAI_TAMA5: // "Game de Hakken!! Tamagotchi - Osutchi to Mesutchi" has RAM size 0
|
||||||
case BANDAI_TAMA5: // TODO: not sure
|
|
||||||
case MBC_NONE:
|
case MBC_NONE:
|
||||||
case MBC_BAD:
|
case MBC_BAD:
|
||||||
case MBC_WRONG_FEATURES:
|
case MBC_WRONG_FEATURES:
|
||||||
@@ -735,6 +740,7 @@ static bool hasRAM(MbcType type) {
|
|||||||
case MBC5_RAM_BATTERY:
|
case MBC5_RAM_BATTERY:
|
||||||
case MBC5_RUMBLE_RAM:
|
case MBC5_RUMBLE_RAM:
|
||||||
case MBC5_RUMBLE_RAM_BATTERY:
|
case MBC5_RUMBLE_RAM_BATTERY:
|
||||||
|
case MBC6: // "Net de Get - Minigame @ 100" has RAM size 3 (32 KiB)
|
||||||
case MBC7_SENSOR_RUMBLE_RAM_BATTERY:
|
case MBC7_SENSOR_RUMBLE_RAM_BATTERY:
|
||||||
case POCKET_CAMERA:
|
case POCKET_CAMERA:
|
||||||
case HUC3:
|
case HUC3:
|
||||||
@@ -761,7 +767,7 @@ static bool hasRAM(MbcType type) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
unreachable_();
|
unreachable_(); // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t const nintendoLogo[] = {
|
static uint8_t const nintendoLogo[] = {
|
||||||
@@ -811,8 +817,9 @@ static ssize_t readBytes(int fd, uint8_t *buf, size_t len) {
|
|||||||
while (len) {
|
while (len) {
|
||||||
ssize_t ret = read(fd, buf, len);
|
ssize_t ret = read(fd, buf, len);
|
||||||
|
|
||||||
if (ret == -1 && errno != EINTR) { // Return errors, unless we only were interrupted
|
// Return errors, unless we only were interrupted
|
||||||
return -1;
|
if (ret == -1 && errno != EINTR) {
|
||||||
|
return -1; // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
// EOF reached
|
// EOF reached
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
@@ -838,8 +845,9 @@ static ssize_t writeBytes(int fd, uint8_t *buf, size_t len) {
|
|||||||
while (len) {
|
while (len) {
|
||||||
ssize_t ret = write(fd, buf, len);
|
ssize_t ret = write(fd, buf, len);
|
||||||
|
|
||||||
if (ret == -1 && errno != EINTR) { // Return errors, unless we only were interrupted
|
// Return errors, unless we only were interrupted
|
||||||
return -1;
|
if (ret == -1 && errno != EINTR) {
|
||||||
|
return -1; // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
// If anything was written, accumulate it, and continue
|
// If anything was written, accumulate it, and continue
|
||||||
if (ret != -1) {
|
if (ret != -1) {
|
||||||
@@ -879,9 +887,9 @@ static void overwriteBytes(
|
|||||||
memcpy(&rom0[startAddr], fixed, size);
|
memcpy(&rom0[startAddr], fixed, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void processFile(int input, int output, char const *name, off_t fileSize) {
|
static void
|
||||||
// Both of these should be true for seekable files, and neither otherwise
|
processFile(int input, int output, char const *name, off_t fileSize, bool expectFileSize) {
|
||||||
if (input == output) {
|
if (expectFileSize) {
|
||||||
assume(fileSize != 0);
|
assume(fileSize != 0);
|
||||||
} else {
|
} else {
|
||||||
assume(fileSize == 0);
|
assume(fileSize == 0);
|
||||||
@@ -893,8 +901,10 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
|||||||
ssize_t headerSize = (cartridgeType & 0xFF00) == TPP1 ? 0x154 : 0x150;
|
ssize_t headerSize = (cartridgeType & 0xFF00) == TPP1 ? 0x154 : 0x150;
|
||||||
|
|
||||||
if (rom0Len == -1) {
|
if (rom0Len == -1) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
report("FATAL: Failed to read \"%s\"'s header: %s\n", name, strerror(errno));
|
report("FATAL: Failed to read \"%s\"'s header: %s\n", name, strerror(errno));
|
||||||
return;
|
return;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
} else if (rom0Len < headerSize) {
|
} else if (rom0Len < headerSize) {
|
||||||
report(
|
report(
|
||||||
"FATAL: \"%s\" too short, expected at least %jd ($%jx) bytes, got only %jd\n",
|
"FATAL: \"%s\" too short, expected at least %jd ($%jx) bytes, got only %jd\n",
|
||||||
@@ -1132,8 +1142,10 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
|||||||
// write the header
|
// write the header
|
||||||
if (input == output) {
|
if (input == output) {
|
||||||
if (lseek(output, 0, SEEK_SET) == static_cast<off_t>(-1)) {
|
if (lseek(output, 0, SEEK_SET) == static_cast<off_t>(-1)) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
report("FATAL: Failed to rewind \"%s\": %s\n", name, strerror(errno));
|
report("FATAL: Failed to rewind \"%s\": %s\n", name, strerror(errno));
|
||||||
return;
|
return;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
// If modifying the file in-place, we only need to edit the header
|
// If modifying the file in-place, we only need to edit the header
|
||||||
// However, padding may have modified ROM0 (added padding), so don't in that case
|
// However, padding may have modified ROM0 (added padding), so don't in that case
|
||||||
@@ -1144,9 +1156,12 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
|||||||
writeLen = writeBytes(output, rom0, rom0Len);
|
writeLen = writeBytes(output, rom0, rom0Len);
|
||||||
|
|
||||||
if (writeLen == -1) {
|
if (writeLen == -1) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
report("FATAL: Failed to write \"%s\"'s ROM0: %s\n", name, strerror(errno));
|
report("FATAL: Failed to write \"%s\"'s ROM0: %s\n", name, strerror(errno));
|
||||||
return;
|
return;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
} else if (writeLen < rom0Len) {
|
} else if (writeLen < rom0Len) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
report(
|
report(
|
||||||
"FATAL: Could only write %jd of \"%s\"'s %jd ROM0 bytes\n",
|
"FATAL: Could only write %jd of \"%s\"'s %jd ROM0 bytes\n",
|
||||||
static_cast<intmax_t>(writeLen),
|
static_cast<intmax_t>(writeLen),
|
||||||
@@ -1154,6 +1169,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
|||||||
static_cast<intmax_t>(rom0Len)
|
static_cast<intmax_t>(rom0Len)
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output ROMX if it was buffered
|
// Output ROMX if it was buffered
|
||||||
@@ -1162,9 +1178,12 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
|||||||
// so it's fine to cast to `size_t`
|
// so it's fine to cast to `size_t`
|
||||||
writeLen = writeBytes(output, romx.data(), totalRomxLen);
|
writeLen = writeBytes(output, romx.data(), totalRomxLen);
|
||||||
if (writeLen == -1) {
|
if (writeLen == -1) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
report("FATAL: Failed to write \"%s\"'s ROMX: %s\n", name, strerror(errno));
|
report("FATAL: Failed to write \"%s\"'s ROMX: %s\n", name, strerror(errno));
|
||||||
return;
|
return;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
} else if (static_cast<size_t>(writeLen) < totalRomxLen) {
|
} else if (static_cast<size_t>(writeLen) < totalRomxLen) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
report(
|
report(
|
||||||
"FATAL: Could only write %jd of \"%s\"'s %zu ROMX bytes\n",
|
"FATAL: Could only write %jd of \"%s\"'s %zu ROMX bytes\n",
|
||||||
static_cast<intmax_t>(writeLen),
|
static_cast<intmax_t>(writeLen),
|
||||||
@@ -1172,6 +1191,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
|||||||
totalRomxLen
|
totalRomxLen
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1179,8 +1199,10 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
|||||||
if (padValue != UNSPECIFIED) {
|
if (padValue != UNSPECIFIED) {
|
||||||
if (input == output) {
|
if (input == output) {
|
||||||
if (lseek(output, 0, SEEK_END) == static_cast<off_t>(-1)) {
|
if (lseek(output, 0, SEEK_END) == static_cast<off_t>(-1)) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
report("FATAL: Failed to seek to end of \"%s\": %s\n", name, strerror(errno));
|
report("FATAL: Failed to seek to end of \"%s\": %s\n", name, strerror(errno));
|
||||||
return;
|
return;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
memset(bank, padValue, sizeof(bank));
|
memset(bank, padValue, sizeof(bank));
|
||||||
@@ -1194,40 +1216,73 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
|
|||||||
// The return value is either -1, or at most `thisLen`,
|
// The return value is either -1, or at most `thisLen`,
|
||||||
// so it's fine to cast to `size_t`
|
// so it's fine to cast to `size_t`
|
||||||
if (static_cast<size_t>(ret) != thisLen) {
|
if (static_cast<size_t>(ret) != thisLen) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
report("FATAL: Failed to write \"%s\"'s padding: %s\n", name, strerror(errno));
|
report("FATAL: Failed to write \"%s\"'s padding: %s\n", name, strerror(errno));
|
||||||
break;
|
break;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
len -= thisLen;
|
len -= thisLen;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool processFilename(char const *name) {
|
static bool processFilename(char const *name, char const *outputName) {
|
||||||
nbErrors = 0;
|
nbErrors = 0;
|
||||||
|
|
||||||
if (!strcmp(name, "-")) {
|
bool inputStdin = !strcmp(name, "-");
|
||||||
|
if (inputStdin && !outputName) {
|
||||||
|
outputName = "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
int output = -1;
|
||||||
|
bool openedOutput = false;
|
||||||
|
if (outputName) {
|
||||||
|
if (!strcmp(outputName, "-")) {
|
||||||
|
output = STDOUT_FILENO;
|
||||||
|
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||||
|
} else {
|
||||||
|
output = open(outputName, O_WRONLY | O_BINARY | O_CREAT, 0600);
|
||||||
|
if (output == -1) {
|
||||||
|
report(
|
||||||
|
"FATAL: Failed to open \"%s\" for writing: %s\n", outputName, strerror(errno)
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
openedOutput = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Defer closeOutput{[&] {
|
||||||
|
if (openedOutput) {
|
||||||
|
close(output);
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
|
||||||
|
if (inputStdin) {
|
||||||
name = "<stdin>";
|
name = "<stdin>";
|
||||||
(void)setmode(STDIN_FILENO, O_BINARY);
|
(void)setmode(STDIN_FILENO, O_BINARY);
|
||||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
processFile(STDIN_FILENO, output, name, 0, false);
|
||||||
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.
|
||||||
// However, this is necessary to avoid a TOCTTOU, if the file was changed between
|
// However, this is necessary to avoid a TOCTTOU, if the file was changed between
|
||||||
// `stat()` and `open(O_RDWR)`, which could trigger the UB anyway.
|
// `stat()` and `open(O_RDWR)`, which could trigger the UB anyway.
|
||||||
// Thus, we're going to hope that either the `open` fails, or it succeeds but IO
|
// Thus, we're going to hope that either the `open` fails, or it succeeds but IO
|
||||||
// operations may fail, all of which we handle.
|
// operations may fail, all of which we handle.
|
||||||
if (int input = open(name, O_RDWR | O_BINARY); input == -1) {
|
if (int input = open(name, (outputName ? O_RDONLY : O_RDWR) | O_BINARY); input == -1) {
|
||||||
report("FATAL: Failed to open \"%s\" for reading+writing: %s\n", name, strerror(errno));
|
report("FATAL: Failed to open \"%s\" for reading+writing: %s\n", name, strerror(errno));
|
||||||
} else {
|
} else {
|
||||||
Defer closeInput{[&] { close(input); }};
|
Defer closeInput{[&] { close(input); }};
|
||||||
struct stat stat;
|
struct stat stat;
|
||||||
if (fstat(input, &stat) == -1) {
|
if (fstat(input, &stat) == -1) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
report("FATAL: Failed to stat \"%s\": %s\n", name, strerror(errno));
|
report("FATAL: Failed to stat \"%s\": %s\n", name, strerror(errno));
|
||||||
} else if (!S_ISREG(stat.st_mode)) { // TODO: Do we want to support other types?
|
// LCOV_EXCL_STOP
|
||||||
|
} else if (!S_ISREG(stat.st_mode)) { // We do not support FIFOs or symlinks
|
||||||
|
// LCOV_EXCL_START
|
||||||
report(
|
report(
|
||||||
"FATAL: \"%s\" is not a regular file, and thus cannot be modified in-place\n",
|
"FATAL: \"%s\" is not a regular file, and thus cannot be modified in-place\n",
|
||||||
name
|
name
|
||||||
);
|
);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
} else if (stat.st_size < 0x150) {
|
} else if (stat.st_size < 0x150) {
|
||||||
// This check is in theory redundant with the one in `processFile`, but it
|
// This check is in theory redundant with the one in `processFile`, but it
|
||||||
// prevents passing a file size of 0, which usually indicates pipes
|
// prevents passing a file size of 0, which usually indicates pipes
|
||||||
@@ -1237,7 +1292,10 @@ static bool processFilename(char const *name) {
|
|||||||
static_cast<intmax_t>(stat.st_size)
|
static_cast<intmax_t>(stat.st_size)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
processFile(input, input, name, stat.st_size);
|
if (!outputName) {
|
||||||
|
output = input;
|
||||||
|
}
|
||||||
|
processFile(input, output, name, stat.st_size, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1281,6 +1339,7 @@ static void parseByte(uint16_t &output, char name) {
|
|||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
nbErrors = 0;
|
nbErrors = 0;
|
||||||
|
|
||||||
|
char const *outputFilename = nullptr;
|
||||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
size_t len;
|
size_t len;
|
||||||
@@ -1322,8 +1381,10 @@ int main(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'h':
|
case 'h':
|
||||||
|
// LCOV_EXCL_START
|
||||||
printUsage();
|
printUsage();
|
||||||
exit(0);
|
exit(0);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
case 'i':
|
case 'i':
|
||||||
gameID = musl_optarg;
|
gameID = musl_optarg;
|
||||||
@@ -1393,6 +1454,10 @@ int main(int argc, char *argv[]) {
|
|||||||
overwriteRom = true;
|
overwriteRom = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'o':
|
||||||
|
outputFilename = musl_optarg;
|
||||||
|
break;
|
||||||
|
|
||||||
case 'p':
|
case 'p':
|
||||||
parseByte(padValue, 'p');
|
parseByte(padValue, 'p');
|
||||||
break;
|
break;
|
||||||
@@ -1419,16 +1484,20 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'V':
|
case 'V':
|
||||||
|
// LCOV_EXCL_START
|
||||||
printf("rgbfix %s\n", get_package_version_string());
|
printf("rgbfix %s\n", get_package_version_string());
|
||||||
exit(0);
|
exit(0);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
case 'v':
|
case 'v':
|
||||||
fixSpec = FIX_LOGO | FIX_HEADER_SUM | FIX_GLOBAL_SUM;
|
fixSpec = FIX_LOGO | FIX_HEADER_SUM | FIX_GLOBAL_SUM;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
// LCOV_EXCL_START
|
||||||
printUsage();
|
printUsage();
|
||||||
exit(1);
|
exit(1);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1462,7 +1531,7 @@ int main(int argc, char *argv[]) {
|
|||||||
"warning: RAM size 1 (2 KiB) was specified for MBC \"%s\"\n",
|
"warning: RAM size 1 (2 KiB) was specified for MBC \"%s\"\n",
|
||||||
mbcName(cartridgeType)
|
mbcName(cartridgeType)
|
||||||
);
|
);
|
||||||
} // TODO: check possible values?
|
}
|
||||||
} else if (ramSize) {
|
} else if (ramSize) {
|
||||||
fprintf(
|
fprintf(
|
||||||
stderr,
|
stderr,
|
||||||
@@ -1543,8 +1612,14 @@ int main(int argc, char *argv[]) {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (outputFilename && argc != musl_optind + 1) {
|
||||||
|
fputs("FATAL: If `-o` is set then only a single input file may be specified\n", stderr);
|
||||||
|
printUsage();
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
failed |= processFilename(*argv);
|
failed |= processFilename(*argv, outputFilename);
|
||||||
} while (*++argv);
|
} while (*++argv);
|
||||||
|
|
||||||
return failed;
|
return failed;
|
||||||
|
|||||||
@@ -12,12 +12,10 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <type_traits>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "extern/getopt.hpp"
|
#include "extern/getopt.hpp"
|
||||||
#include "file.hpp"
|
#include "file.hpp"
|
||||||
#include "helpers.hpp" // assume
|
|
||||||
#include "platform.hpp"
|
#include "platform.hpp"
|
||||||
#include "version.hpp"
|
#include "version.hpp"
|
||||||
|
|
||||||
@@ -103,6 +101,7 @@ void fatal(char const *fmt, ...) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
|
void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (verbosity >= level) {
|
if (verbosity >= level) {
|
||||||
va_list ap;
|
va_list ap;
|
||||||
|
|
||||||
@@ -110,10 +109,11 @@ void Options::verbosePrint(uint8_t level, char const *fmt, ...) const {
|
|||||||
vfprintf(stderr, fmt, ap);
|
vfprintf(stderr, fmt, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
// Short options
|
// Short options
|
||||||
static char const *optstring = "-Aa:b:Cc:d:hi:L:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvXx:YZ";
|
static char const *optstring = "-Aa:B:b:Cc:d:hi:L:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvXx:YZ";
|
||||||
|
|
||||||
// Equivalent long options
|
// Equivalent long options
|
||||||
// Please keep in the same order as short opts.
|
// Please keep in the same order as short opts.
|
||||||
@@ -125,6 +125,7 @@ static char const *optstring = "-Aa:b:Cc:d:hi:L:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvXx:YZ"
|
|||||||
static option const longopts[] = {
|
static option const longopts[] = {
|
||||||
{"auto-attr-map", no_argument, nullptr, 'A'},
|
{"auto-attr-map", no_argument, nullptr, 'A'},
|
||||||
{"attr-map", required_argument, nullptr, 'a'},
|
{"attr-map", required_argument, nullptr, 'a'},
|
||||||
|
{"background-color", required_argument, nullptr, 'B'},
|
||||||
{"base-tiles", required_argument, nullptr, 'b'},
|
{"base-tiles", required_argument, nullptr, 'b'},
|
||||||
{"color-curve", no_argument, nullptr, 'C'},
|
{"color-curve", no_argument, nullptr, 'C'},
|
||||||
{"colors", required_argument, nullptr, 'c'},
|
{"colors", required_argument, nullptr, 'c'},
|
||||||
@@ -156,6 +157,7 @@ static option const longopts[] = {
|
|||||||
{nullptr, no_argument, nullptr, 0 }
|
{nullptr, no_argument, nullptr, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
static void printUsage() {
|
static void printUsage() {
|
||||||
fputs(
|
fputs(
|
||||||
"Usage: rgbgfx [-r stride] [-ChmOuVXYZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
|
"Usage: rgbgfx [-r stride] [-ChmOuVXYZ] [-v [-v ...]] [-a <attr_map> | -A]\n"
|
||||||
@@ -174,6 +176,7 @@ static void printUsage() {
|
|||||||
stderr
|
stderr
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
// Parses a number at the beginning of a string, moving the pointer to skip the parsed characters.
|
// Parses a number at the beginning of a string, moving the pointer to skip the parsed characters.
|
||||||
// Returns the provided errVal on error.
|
// Returns the provided errVal on error.
|
||||||
@@ -279,8 +282,8 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
|
|||||||
|
|
||||||
// We only filter out `EOF`, but calling `isblank()` on anything else is UB!
|
// We only filter out `EOF`, but calling `isblank()` on anything else is UB!
|
||||||
static_assert(
|
static_assert(
|
||||||
std::remove_reference_t<decltype(*file)>::traits_type::eof() == EOF,
|
std::streambuf::traits_type::eof() == EOF,
|
||||||
"isblank(char_traits<...>::eof()) is UB!"
|
"isblank(std::streambuf::traits_type::eof()) is UB!"
|
||||||
);
|
);
|
||||||
std::vector<size_t> argvOfs;
|
std::vector<size_t> argvOfs;
|
||||||
|
|
||||||
@@ -361,6 +364,9 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
options.attrmap = musl_optarg;
|
options.attrmap = musl_optarg;
|
||||||
break;
|
break;
|
||||||
|
case 'B':
|
||||||
|
parseBackgroundPalSpec(musl_optarg);
|
||||||
|
break;
|
||||||
case 'b':
|
case 'b':
|
||||||
number = parseNumber(arg, "Bank 0 base tile ID", 0);
|
number = parseNumber(arg, "Bank 0 base tile ID", 0);
|
||||||
if (number >= 256) {
|
if (number >= 256) {
|
||||||
@@ -400,18 +406,18 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
options.useColorCurve = true;
|
options.useColorCurve = true;
|
||||||
break;
|
break;
|
||||||
case 'c':
|
case 'c':
|
||||||
|
localOptions.externalPalSpec = nullptr; // Allow overriding a previous pal spec
|
||||||
if (musl_optarg[0] == '#') {
|
if (musl_optarg[0] == '#') {
|
||||||
options.palSpecType = Options::EXPLICIT;
|
options.palSpecType = Options::EXPLICIT;
|
||||||
parseInlinePalSpec(musl_optarg);
|
parseInlinePalSpec(musl_optarg);
|
||||||
} else if (strcasecmp(musl_optarg, "embedded") == 0) {
|
} else if (strcasecmp(musl_optarg, "embedded") == 0) {
|
||||||
// Use PLTE, error out if missing
|
// Use PLTE, error out if missing
|
||||||
options.palSpecType = Options::EMBEDDED;
|
options.palSpecType = Options::EMBEDDED;
|
||||||
|
} else if (strncasecmp(musl_optarg, "dmg=", literal_strlen("dmg=")) == 0) {
|
||||||
|
options.palSpecType = Options::DMG;
|
||||||
|
parseDmgPalSpec(&musl_optarg[literal_strlen("dmg=")]);
|
||||||
} else {
|
} else {
|
||||||
options.palSpecType = Options::EXPLICIT;
|
options.palSpecType = Options::EXPLICIT;
|
||||||
// Can't parse the file yet, as "flat" color collections need to know the palette
|
|
||||||
// size to be split; thus, we defer that
|
|
||||||
// TODO: this does not validate the `fmt` part of any external spec but the last
|
|
||||||
// one, but I guess that's okay
|
|
||||||
localOptions.externalPalSpec = musl_optarg;
|
localOptions.externalPalSpec = musl_optarg;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -425,8 +431,10 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'h':
|
case 'h':
|
||||||
|
// LCOV_EXCL_START
|
||||||
printUsage();
|
printUsage();
|
||||||
exit(0);
|
exit(0);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
case 'i':
|
case 'i':
|
||||||
if (!options.inputTileset.empty()) {
|
if (!options.inputTileset.empty()) {
|
||||||
warning("Overriding input tileset file %s", options.inputTileset.c_str());
|
warning("Overriding input tileset file %s", options.inputTileset.c_str());
|
||||||
@@ -582,13 +590,17 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
options.tilemap = musl_optarg;
|
options.tilemap = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'V':
|
case 'V':
|
||||||
|
// LCOV_EXCL_START
|
||||||
printf("rgbgfx %s\n", get_package_version_string());
|
printf("rgbgfx %s\n", get_package_version_string());
|
||||||
exit(0);
|
exit(0);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
case 'v':
|
case 'v':
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (options.verbosity < Options::VERB_VVVVVV) {
|
if (options.verbosity < Options::VERB_VVVVVV) {
|
||||||
++options.verbosity;
|
++options.verbosity;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
case 'x':
|
case 'x':
|
||||||
options.trim = parseNumber(arg, "Number of tiles to trim", 0);
|
options.trim = parseNumber(arg, "Number of tiles to trim", 0);
|
||||||
if (*arg != '\0') {
|
if (*arg != '\0') {
|
||||||
@@ -615,8 +627,10 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
// LCOV_EXCL_START
|
||||||
printUsage();
|
printUsage();
|
||||||
exit(1);
|
exit(1);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -744,15 +758,37 @@ int main(int argc, char *argv[]) {
|
|||||||
parseExternalPalSpec(localOptions.externalPalSpec);
|
parseExternalPalSpec(localOptions.externalPalSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (options.verbosity >= Options::VERB_CFG) {
|
if (options.verbosity >= Options::VERB_CFG) {
|
||||||
fprintf(stderr, "rgbgfx %s\n", get_package_version_string());
|
fprintf(stderr, "rgbgfx %s\n", get_package_version_string());
|
||||||
|
|
||||||
if (options.verbosity >= Options::VERB_VVVVVV) {
|
if (options.verbosity >= Options::VERB_VVVVVV) {
|
||||||
putc('\n', stderr);
|
putc('\n', stderr);
|
||||||
|
// clang-format off: vertically align values
|
||||||
static std::array<uint16_t, 21> gfx{
|
static std::array<uint16_t, 21> gfx{
|
||||||
0x1FE, 0x3FF, 0x399, 0x399, 0x3FF, 0x3FF, 0x381, 0x3C3, 0x1FE, 0x078, 0x1FE,
|
0b0111111110,
|
||||||
0x3FF, 0x3FF, 0x3FF, 0x37B, 0x37B, 0x0FC, 0x0CC, 0x1CE, 0x1CE, 0x1CE,
|
0b1111111111,
|
||||||
|
0b1110011001,
|
||||||
|
0b1110011001,
|
||||||
|
0b1111111111,
|
||||||
|
0b1111111111,
|
||||||
|
0b1110000001,
|
||||||
|
0b1111000011,
|
||||||
|
0b0111111110,
|
||||||
|
0b0001111000,
|
||||||
|
0b0111111110,
|
||||||
|
0b1111111111,
|
||||||
|
0b1111111111,
|
||||||
|
0b1111111111,
|
||||||
|
0b1101111011,
|
||||||
|
0b1101111011,
|
||||||
|
0b0011111100,
|
||||||
|
0b0011001100,
|
||||||
|
0b0111001110,
|
||||||
|
0b0111001110,
|
||||||
|
0b0111001110,
|
||||||
};
|
};
|
||||||
|
// clang-format on
|
||||||
static std::array<char const *, 3> textbox{
|
static std::array<char const *, 3> textbox{
|
||||||
" ,----------------------------------------.",
|
" ,----------------------------------------.",
|
||||||
" | Augh, dimensional interference again?! |",
|
" | Augh, dimensional interference again?! |",
|
||||||
@@ -805,6 +841,8 @@ int main(int argc, char *argv[]) {
|
|||||||
return "Explicit";
|
return "Explicit";
|
||||||
case Options::EMBEDDED:
|
case Options::EMBEDDED:
|
||||||
return "Embedded";
|
return "Embedded";
|
||||||
|
case Options::DMG:
|
||||||
|
return "DMG";
|
||||||
}
|
}
|
||||||
return "???";
|
return "???";
|
||||||
}());
|
}());
|
||||||
@@ -825,7 +863,7 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
fprintf(
|
fprintf(
|
||||||
stderr,
|
stderr,
|
||||||
"\tInput image slice: %" PRIu32 "x%" PRIu32 " pixels starting at (%" PRIi32 ", %" PRIi32
|
"\tInput image slice: %" PRIu32 "x%" PRIu32 " pixels starting at (%" PRId32 ", %" PRId32
|
||||||
")\n",
|
")\n",
|
||||||
options.inputSlice.width,
|
options.inputSlice.width,
|
||||||
options.inputSlice.height,
|
options.inputSlice.height,
|
||||||
@@ -856,6 +894,7 @@ int main(int argc, char *argv[]) {
|
|||||||
printPath("Output palettes", options.palettes);
|
printPath("Output palettes", options.palettes);
|
||||||
fputs("Ready.\n", stderr);
|
fputs("Ready.\n", stderr);
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
// Do not do anything if option parsing went wrong.
|
// Do not do anything if option parsing went wrong.
|
||||||
requireZeroErrors();
|
requireZeroErrors();
|
||||||
|
|||||||
@@ -86,8 +86,8 @@ private:
|
|||||||
public:
|
public:
|
||||||
Iter() = default;
|
Iter() = default;
|
||||||
|
|
||||||
bool operator==(Iter const &other) const { return _iter == other._iter; }
|
bool operator==(Iter const &rhs) const { return _iter == rhs._iter; }
|
||||||
bool operator!=(Iter const &other) const { return !(*this == other); }
|
|
||||||
Iter &operator++() {
|
Iter &operator++() {
|
||||||
++_iter;
|
++_iter;
|
||||||
skipEmpty();
|
skipEmpty();
|
||||||
@@ -98,6 +98,7 @@ private:
|
|||||||
++(*this);
|
++(*this);
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|
||||||
reference operator*() const {
|
reference operator*() const {
|
||||||
assume((*_iter).has_value());
|
assume((*_iter).has_value());
|
||||||
return **_iter;
|
return **_iter;
|
||||||
@@ -167,16 +168,6 @@ private:
|
|||||||
std::unordered_set<uint16_t> &uniqueColors() const {
|
std::unordered_set<uint16_t> &uniqueColors() const {
|
||||||
// We check for *distinct* colors by stuffing them into a `set`; this should be
|
// We check for *distinct* colors by stuffing them into a `set`; this should be
|
||||||
// faster than "back-checking" on every element (O(n²))
|
// faster than "back-checking" on every element (O(n²))
|
||||||
//
|
|
||||||
// TODO: calc84maniac suggested another approach; try implementing it, see if it
|
|
||||||
// performs better:
|
|
||||||
// > So basically you make a priority queue that takes iterators into each of your sets
|
|
||||||
// > (paired with end iterators so you'll know where to stop), and the comparator tests the
|
|
||||||
// > values pointed to by each iterator
|
|
||||||
// > Then each iteration you pop from the queue,
|
|
||||||
// > optionally add one to your count, increment the iterator and push it back into the
|
|
||||||
// > queue if it didn't reach the end
|
|
||||||
// > And you do this until the priority queue is empty
|
|
||||||
static std::unordered_set<uint16_t> colors;
|
static std::unordered_set<uint16_t> colors;
|
||||||
|
|
||||||
colors.clear();
|
colors.clear();
|
||||||
@@ -308,7 +299,7 @@ static void decant(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
auto attrs = from.begin();
|
auto attrs = from.begin();
|
||||||
std::advance(attrs, (iter - processed.begin()));
|
std::advance(attrs, iter - processed.begin());
|
||||||
|
|
||||||
// Build up the "component"...
|
// Build up the "component"...
|
||||||
colors.clear();
|
colors.clear();
|
||||||
@@ -476,7 +467,6 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
|||||||
);
|
);
|
||||||
|
|
||||||
// All efficiencies are identical iff min equals max
|
// All efficiencies are identical iff min equals max
|
||||||
// TODO: maybe not ideal to re-compute these two?
|
|
||||||
ProtoPalette const &minProtoPal = protoPalettes[minEfficiencyIter->protoPalIndex];
|
ProtoPalette const &minProtoPal = protoPalettes[minEfficiencyIter->protoPalIndex];
|
||||||
ProtoPalette const &maxProtoPal = protoPalettes[maxEfficiencyIter->protoPalIndex];
|
ProtoPalette const &maxProtoPal = protoPalettes[maxEfficiencyIter->protoPalIndex];
|
||||||
size_t minSize = minProtoPal.size();
|
size_t minSize = minProtoPal.size();
|
||||||
@@ -558,6 +548,7 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (options.verbosity >= Options::VERB_INTERM) {
|
if (options.verbosity >= Options::VERB_INTERM) {
|
||||||
for (auto &&assignment : assignments) {
|
for (auto &&assignment : assignments) {
|
||||||
fprintf(stderr, "{ ");
|
fprintf(stderr, "{ ");
|
||||||
@@ -570,11 +561,13 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
|||||||
fprintf(stderr, "} (volume = %zu)\n", assignment.volume());
|
fprintf(stderr, "} (volume = %zu)\n", assignment.volume());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
// "Decant" the result
|
// "Decant" the result
|
||||||
decant(assignments, protoPalettes);
|
decant(assignments, protoPalettes);
|
||||||
// Note that the result does not contain any empty palettes
|
// Note that the result does not contain any empty palettes
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (options.verbosity >= Options::VERB_INTERM) {
|
if (options.verbosity >= Options::VERB_INTERM) {
|
||||||
for (auto &&assignment : assignments) {
|
for (auto &&assignment : assignments) {
|
||||||
fprintf(stderr, "{ ");
|
fprintf(stderr, "{ ");
|
||||||
@@ -587,6 +580,7 @@ std::tuple<DefaultInitVec<size_t>, size_t>
|
|||||||
fprintf(stderr, "} (volume = %zu)\n", assignment.volume());
|
fprintf(stderr, "} (volume = %zu)\n", assignment.volume());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
DefaultInitVec<size_t> mappings(protoPalettes.size());
|
DefaultInitVec<size_t> mappings(protoPalettes.size());
|
||||||
for (size_t i = 0; i < assignments.size(); ++i) {
|
for (size_t i = 0; i < assignments.size(); ++i) {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ void sortIndexed(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unreachable_(); // This should not be possible
|
unreachable_(); // LCOV_EXCL_LINE
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,12 @@
|
|||||||
|
|
||||||
using namespace std::string_view_literals;
|
using namespace std::string_view_literals;
|
||||||
|
|
||||||
constexpr uint8_t nibble(char c) {
|
template<typename Str> // Should be std::string or std::string_view
|
||||||
|
static void skipWhitespace(Str const &str, size_t &pos) {
|
||||||
|
pos = std::min(str.find_first_not_of(" \t"sv, pos), str.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr uint8_t nibble(char c) {
|
||||||
if (c >= 'a') {
|
if (c >= 'a') {
|
||||||
assume(c <= 'f');
|
assume(c <= 'f');
|
||||||
return c - 'a' + 10;
|
return c - 'a' + 10;
|
||||||
@@ -36,27 +41,21 @@ constexpr uint8_t nibble(char c) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr uint8_t toHex(char c1, char c2) {
|
static constexpr uint8_t toHex(char c1, char c2) {
|
||||||
return nibble(c1) * 16 + nibble(c2);
|
return nibble(c1) * 16 + nibble(c2);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr uint8_t singleToHex(char c) {
|
static constexpr uint8_t singleToHex(char c) {
|
||||||
return toHex(c, c);
|
return toHex(c, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Str> // Should be std::string or std::string_view
|
|
||||||
static void skipWhitespace(Str const &str, typename Str::size_type &pos) {
|
|
||||||
pos = std::min(str.find_first_not_of(" \t"sv, pos), str.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
void parseInlinePalSpec(char const * const rawArg) {
|
void parseInlinePalSpec(char const * const rawArg) {
|
||||||
// List of #rrggbb/#rgb colors (or #none); comma-separated.
|
// List of #rrggbb/#rgb colors (or #none); comma-separated.
|
||||||
// Palettes are separated by colons.
|
// Palettes are separated by colons.
|
||||||
|
|
||||||
std::string_view arg(rawArg);
|
std::string_view arg(rawArg);
|
||||||
using size_type = decltype(arg)::size_type;
|
|
||||||
|
|
||||||
auto parseError = [&rawArg, &arg](size_type ofs, size_type len, char const *msg) {
|
auto parseError = [&rawArg, &arg](size_t ofs, size_t len, char const *msg) {
|
||||||
(void)arg; // With NDEBUG, `arg` is otherwise not used
|
(void)arg; // With NDEBUG, `arg` is otherwise not used
|
||||||
assume(ofs <= arg.length());
|
assume(ofs <= arg.length());
|
||||||
assume(len <= arg.length());
|
assume(len <= arg.length());
|
||||||
@@ -80,17 +79,16 @@ void parseInlinePalSpec(char const * const rawArg) {
|
|||||||
options.palSpec.clear();
|
options.palSpec.clear();
|
||||||
options.palSpec.emplace_back(); // Value-initialized, not default-init'd, so we get zeros
|
options.palSpec.emplace_back(); // Value-initialized, not default-init'd, so we get zeros
|
||||||
|
|
||||||
size_type n = 0; // Index into the argument
|
size_t n = 0; // Index into the argument
|
||||||
// TODO: store max `nbColors` ever reached, and compare against palette size later
|
|
||||||
size_t nbColors = 0; // Number of colors in the current palette
|
size_t nbColors = 0; // Number of colors in the current palette
|
||||||
for (;;) {
|
for (;;) {
|
||||||
++n; // Ignore the '#' (checked either by caller or previous loop iteration)
|
++n; // Ignore the '#' (checked either by caller or previous loop iteration)
|
||||||
|
|
||||||
std::optional<Rgba> &color = options.palSpec.back()[nbColors];
|
std::optional<Rgba> &color = options.palSpec.back()[nbColors];
|
||||||
// Check for #none first.
|
// Check for "#none" first.
|
||||||
if (arg.compare(n, 4, "none"sv) == 0 || arg.compare(n, 4, "NONE"sv) == 0) {
|
if (strncasecmp(&rawArg[n], "none", literal_strlen("none")) == 0) {
|
||||||
color = {};
|
color = {};
|
||||||
n += 4;
|
n += literal_strlen("none");
|
||||||
} else {
|
} else {
|
||||||
auto pos = std::min(arg.find_first_not_of("0123456789ABCDEFabcdef"sv, n), arg.length());
|
auto pos = std::min(arg.find_first_not_of("0123456789ABCDEFabcdef"sv, n), arg.length());
|
||||||
switch (pos - n) {
|
switch (pos - n) {
|
||||||
@@ -194,7 +192,6 @@ static T readLE(U const *bytes) {
|
|||||||
[[gnu::warn_unused_result]]
|
[[gnu::warn_unused_result]]
|
||||||
static bool readLine(std::filebuf &file, std::string &buffer) {
|
static bool readLine(std::filebuf &file, std::string &buffer) {
|
||||||
assume(buffer.empty());
|
assume(buffer.empty());
|
||||||
// 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()) {
|
||||||
@@ -222,7 +219,7 @@ static bool readLine(std::filebuf &file, std::string &buffer) {
|
|||||||
|
|
||||||
// Parses the initial part of a string_view, advancing the "read index" as it does
|
// Parses the initial part of a string_view, advancing the "read index" as it does
|
||||||
template<typename U> // Should be uint*_t
|
template<typename U> // Should be uint*_t
|
||||||
static std::optional<U> parseDec(std::string const &str, std::string::size_type &n) {
|
static std::optional<U> parseDec(std::string const &str, size_t &n) {
|
||||||
uintmax_t value = 0;
|
uintmax_t value = 0;
|
||||||
auto result = std::from_chars(str.data() + n, str.data() + str.size(), value);
|
auto result = std::from_chars(str.data() + n, str.data() + str.size(), value);
|
||||||
if (static_cast<bool>(result.ec)) {
|
if (static_cast<bool>(result.ec)) {
|
||||||
@@ -232,8 +229,7 @@ static std::optional<U> parseDec(std::string const &str, std::string::size_type
|
|||||||
return std::optional<U>{value};
|
return std::optional<U>{value};
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::optional<Rgba>
|
static std::optional<Rgba> parseColor(std::string const &str, size_t &n, uint16_t i) {
|
||||||
parseColor(std::string const &str, std::string::size_type &n, uint16_t i) {
|
|
||||||
std::optional<uint8_t> r = parseDec<uint8_t>(str, n);
|
std::optional<uint8_t> r = parseDec<uint8_t>(str, n);
|
||||||
if (!r) {
|
if (!r) {
|
||||||
error("Failed to parse color #%d (\"%s\"): invalid red component", i + 1, str.c_str());
|
error("Failed to parse color #%d (\"%s\"): invalid red component", i + 1, str.c_str());
|
||||||
@@ -281,7 +277,7 @@ static void parsePSPFile(std::filebuf &file) {
|
|||||||
|
|
||||||
line.clear();
|
line.clear();
|
||||||
requireLine("PSP", file, line);
|
requireLine("PSP", file, line);
|
||||||
std::string::size_type n = 0;
|
size_t n = 0;
|
||||||
std::optional<uint16_t> nbColors = parseDec<uint16_t>(line, n);
|
std::optional<uint16_t> nbColors = parseDec<uint16_t>(line, n);
|
||||||
if (!nbColors || n != line.length()) {
|
if (!nbColors || n != line.length()) {
|
||||||
error("Invalid \"number of colors\" line in PSP file (%s)", line.c_str());
|
error("Invalid \"number of colors\" line in PSP file (%s)", line.c_str());
|
||||||
@@ -347,7 +343,7 @@ static void parseGPLFile(std::filebuf &file) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string::size_type n = 0;
|
size_t n = 0;
|
||||||
skipWhitespace(line, n);
|
skipWhitespace(line, n);
|
||||||
// Skip empty lines, or lines that contain just a comment.
|
// Skip empty lines, or lines that contain just a comment.
|
||||||
if (line.length() == n || line[n] == '#') {
|
if (line.length() == n || line[n] == '#') {
|
||||||
@@ -438,7 +434,6 @@ static void parseACTFile(std::filebuf &file) {
|
|||||||
uint16_t nbColors = 256;
|
uint16_t nbColors = 256;
|
||||||
if (len == 772) {
|
if (len == 772) {
|
||||||
nbColors = readBE<uint16_t>(&buf[768]);
|
nbColors = readBE<uint16_t>(&buf[768]);
|
||||||
// TODO: apparently there is a "transparent color index"? What?
|
|
||||||
if (nbColors > 256 || nbColors == 0) {
|
if (nbColors > 256 || nbColors == 0) {
|
||||||
error("Invalid number of colors in ACT file (%" PRIu16 ")", nbColors);
|
error("Invalid number of colors in ACT file (%" PRIu16 ")", nbColors);
|
||||||
return;
|
return;
|
||||||
@@ -549,9 +544,6 @@ static void parseACOFile(std::filebuf &file) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: maybe scan the v2 data instead (if present)
|
|
||||||
// `codecvt` can be used to convert from UTF-16 to UTF-8
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void parseGBCFile(std::filebuf &file) {
|
static void parseGBCFile(std::filebuf &file) {
|
||||||
@@ -603,10 +595,9 @@ void parseExternalPalSpec(char const *arg) {
|
|||||||
std::tuple{"GBC", &parseGBCFile, std::ios::binary},
|
std::tuple{"GBC", &parseGBCFile, std::ios::binary},
|
||||||
};
|
};
|
||||||
|
|
||||||
auto iter =
|
auto iter = std::find_if(RANGE(parsers), [&arg, &ptr](auto const &parser) {
|
||||||
std::find_if(RANGE(parsers), [&arg, &ptr](decltype(parsers)::value_type const &parser) {
|
return strncasecmp(arg, std::get<0>(parser), ptr - arg) == 0;
|
||||||
return strncasecmp(arg, std::get<0>(parser), ptr - arg) == 0;
|
});
|
||||||
});
|
|
||||||
if (iter == parsers.end()) {
|
if (iter == parsers.end()) {
|
||||||
error(
|
error(
|
||||||
"Unknown external palette format \"%.*s\"",
|
"Unknown external palette format \"%.*s\"",
|
||||||
@@ -625,3 +616,61 @@ void parseExternalPalSpec(char const *arg) {
|
|||||||
|
|
||||||
std::get<1> (*iter)(file);
|
std::get<1> (*iter)(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void parseDmgPalSpec(char const * const rawArg) {
|
||||||
|
// Two hex digit DMG palette spec
|
||||||
|
|
||||||
|
std::string_view arg(rawArg);
|
||||||
|
|
||||||
|
if (arg.length() != 2
|
||||||
|
|| arg.find_first_not_of("0123456789ABCDEFabcdef"sv) != std::string_view::npos) {
|
||||||
|
error("Unknown DMG palette specification \"%s\"", rawArg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
options.palSpecDmg = toHex(arg[0], arg[1]);
|
||||||
|
|
||||||
|
// Map gray shades to their DMG color indexes for fast lookup by `Rgba::grayIndex`
|
||||||
|
for (uint8_t i = 0; i < 4; ++i) {
|
||||||
|
options.dmgColors[options.dmgValue(i)] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate that DMG palette spec does not have conflicting colors
|
||||||
|
for (uint8_t i = 0; i < 3; ++i) {
|
||||||
|
for (uint8_t j = i + 1; j < 4; ++j) {
|
||||||
|
if (options.dmgValue(i) == options.dmgValue(j)) {
|
||||||
|
error("DMG palette specification maps two gray shades to the same color index");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void parseBackgroundPalSpec(char const *arg) {
|
||||||
|
if (strcasecmp(arg, "transparent") == 0) {
|
||||||
|
options.bgColor = Rgba(0x00, 0x00, 0x00, 0x00);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg[0] != '#') {
|
||||||
|
error("Background color specification must be `#rgb`, `#rrggbb`, or `transparent`");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size = strspn(&arg[1], "0123456789ABCDEFabcdef");
|
||||||
|
switch (size) {
|
||||||
|
case 3:
|
||||||
|
options.bgColor = Rgba(singleToHex(arg[1]), singleToHex(arg[2]), singleToHex(arg[3]), 0xFF);
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
options.bgColor =
|
||||||
|
Rgba(toHex(arg[1], arg[2]), toHex(arg[3], arg[4]), toHex(arg[5], arg[6]), 0xFF);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
error("Unknown background color specification \"%s\"", arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg[size + 1] != '\0') {
|
||||||
|
error("Unexpected text \"%s\" after background color specification", &arg[size + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,6 +25,10 @@
|
|||||||
#include "gfx/pal_sorting.hpp"
|
#include "gfx/pal_sorting.hpp"
|
||||||
#include "gfx/proto_palette.hpp"
|
#include "gfx/proto_palette.hpp"
|
||||||
|
|
||||||
|
static bool isBgColorTransparent() {
|
||||||
|
return options.bgColor.has_value() && options.bgColor->isTransparent();
|
||||||
|
}
|
||||||
|
|
||||||
class ImagePalette {
|
class ImagePalette {
|
||||||
std::array<std::optional<Rgba>, NB_COLOR_SLOTS> _colors;
|
std::array<std::optional<Rgba>, NB_COLOR_SLOTS> _colors;
|
||||||
|
|
||||||
@@ -36,9 +40,9 @@ public:
|
|||||||
// color), then the other color is returned. Otherwise, `nullptr` is returned.
|
// color), then the other color is returned. Otherwise, `nullptr` is returned.
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
Rgba const *registerColor(Rgba const &rgba) {
|
Rgba const *registerColor(Rgba const &rgba) {
|
||||||
decltype(_colors)::value_type &slot = _colors[rgba.cgbColor()];
|
std::optional<Rgba> &slot = _colors[rgba.cgbColor()];
|
||||||
|
|
||||||
if (rgba.cgbColor() == Rgba::transparent) {
|
if (rgba.cgbColor() == Rgba::transparent && !isBgColorTransparent()) {
|
||||||
options.hasTransparentPixels = true;
|
options.hasTransparentPixels = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +56,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t size() const {
|
size_t size() const {
|
||||||
return std::count_if(RANGE(_colors), [](decltype(_colors)::value_type const &slot) {
|
return std::count_if(RANGE(_colors), [](std::optional<Rgba> const &slot) {
|
||||||
return slot.has_value() && !slot->isTransparent();
|
return slot.has_value() && !slot->isTransparent();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -193,23 +197,20 @@ public:
|
|||||||
PNG_LIBPNG_VER_STRING, static_cast<png_voidp>(this), handleError, handleWarning
|
PNG_LIBPNG_VER_STRING, static_cast<png_voidp>(this), handleError, handleWarning
|
||||||
);
|
);
|
||||||
if (!png) {
|
if (!png) {
|
||||||
fatal("Failed to allocate PNG structure: %s", strerror(errno));
|
fatal("Failed to create PNG read structure: %s", strerror(errno)); // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
|
|
||||||
info = png_create_info_struct(png);
|
info = png_create_info_struct(png);
|
||||||
if (!info) {
|
if (!info) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
png_destroy_read_struct(&png, nullptr, nullptr);
|
png_destroy_read_struct(&png, nullptr, nullptr);
|
||||||
fatal("Failed to allocate PNG info structure: %s", strerror(errno));
|
fatal("Failed to create PNG info structure: %s", strerror(errno));
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
png_set_read_fn(png, this, readData);
|
png_set_read_fn(png, this, readData);
|
||||||
png_set_sig_bytes(png, pngHeader.size());
|
png_set_sig_bytes(png, pngHeader.size());
|
||||||
|
|
||||||
// TODO: png_set_crc_action(png, PNG_CRC_ERROR_QUIT, PNG_CRC_WARN_DISCARD);
|
|
||||||
|
|
||||||
// Skipping chunks we don't use should improve performance
|
|
||||||
// TODO: png_set_keep_unknown_chunks(png, ...);
|
|
||||||
|
|
||||||
// Process all chunks up to but not including the image data
|
// Process all chunks up to but not including the image data
|
||||||
png_read_info(png, info);
|
png_read_info(png, info);
|
||||||
|
|
||||||
@@ -288,9 +289,7 @@ public:
|
|||||||
options.verbosePrint(Options::VERB_INTERM, "No embedded palette\n");
|
options.verbosePrint(Options::VERB_INTERM, "No embedded palette\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up transformations; to turn everything into RGBA888
|
// Set up transformations to turn everything into RGBA888 for simplicity of handling
|
||||||
// TODO: it's not necessary to uniformize the pixel data (in theory), and not doing
|
|
||||||
// so *might* improve performance, and should reduce memory usage.
|
|
||||||
|
|
||||||
// Convert grayscale to RGB
|
// Convert grayscale to RGB
|
||||||
switch (colorType & ~PNG_COLOR_MASK_ALPHA) {
|
switch (colorType & ~PNG_COLOR_MASK_ALPHA) {
|
||||||
@@ -466,7 +465,6 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool operator==(Iterator const &rhs) const { return coords() == rhs.coords(); }
|
bool operator==(Iterator const &rhs) const { return coords() == rhs.coords(); }
|
||||||
bool operator!=(Iterator const &rhs) const { return coords() != rhs.coords(); }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -518,10 +516,12 @@ struct AttrmapEntry {
|
|||||||
bool yFlip;
|
bool yFlip;
|
||||||
bool xFlip;
|
bool xFlip;
|
||||||
|
|
||||||
static constexpr decltype(protoPaletteID) transparent = SIZE_MAX;
|
static constexpr size_t transparent = static_cast<size_t>(-1);
|
||||||
|
static constexpr size_t background = static_cast<size_t>(-2);
|
||||||
|
|
||||||
|
bool isBackgroundTile() const { return protoPaletteID == background; }
|
||||||
size_t getPalID(DefaultInitVec<size_t> const &mappings) const {
|
size_t getPalID(DefaultInitVec<size_t> const &mappings) const {
|
||||||
return protoPaletteID == transparent ? 0 : mappings[protoPaletteID];
|
return mappings[isBackgroundTile() || protoPaletteID == transparent ? 0 : protoPaletteID];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -552,10 +552,10 @@ static void generatePalSpec(Png const &png) {
|
|||||||
static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
|
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?
|
|
||||||
auto [mappings, nbPalettes] = overloadAndRemove(protoPalettes);
|
auto [mappings, nbPalettes] = overloadAndRemove(protoPalettes);
|
||||||
assume(mappings.size() == protoPalettes.size());
|
assume(mappings.size() == protoPalettes.size());
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (options.verbosity >= Options::VERB_INTERM) {
|
if (options.verbosity >= Options::VERB_INTERM) {
|
||||||
fprintf(
|
fprintf(
|
||||||
stderr,
|
stderr,
|
||||||
@@ -567,6 +567,7 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
|
|||||||
fprintf(stderr, "%zu -> %zu\n", i, mappings[i]);
|
fprintf(stderr, "%zu -> %zu\n", i, mappings[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
std::vector<Palette> palettes(nbPalettes);
|
std::vector<Palette> palettes(nbPalettes);
|
||||||
// If the image contains at least one transparent pixel, force transparency in the first slot of
|
// If the image contains at least one transparent pixel, force transparency in the first slot of
|
||||||
@@ -585,8 +586,10 @@ 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();
|
if (options.palSpecType == Options::DMG) {
|
||||||
if (embPalRGB != nullptr) {
|
sortGrayscale(palettes, png.getColors().raw());
|
||||||
|
} else if (auto [embPalSize, embPalRGB, embPalAlphaSize, embPalAlpha] = png.getEmbeddedPal();
|
||||||
|
embPalRGB != nullptr) {
|
||||||
sortIndexed(palettes, embPalSize, embPalRGB, embPalAlphaSize, embPalAlpha);
|
sortIndexed(palettes, embPalSize, embPalRGB, embPalAlphaSize, embPalAlpha);
|
||||||
} else if (png.isSuitableForGrayscale()) {
|
} else if (png.isSuitableForGrayscale()) {
|
||||||
sortGrayscale(palettes, png.getColors().raw());
|
sortGrayscale(palettes, png.getColors().raw());
|
||||||
@@ -601,9 +604,9 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
|
|||||||
// Convert the palette spec to actual palettes
|
// Convert the palette spec to actual palettes
|
||||||
std::vector<Palette> palettes(options.palSpec.size());
|
std::vector<Palette> palettes(options.palSpec.size());
|
||||||
for (auto [spec, pal] : zip(options.palSpec, palettes)) {
|
for (auto [spec, pal] : zip(options.palSpec, palettes)) {
|
||||||
for (size_t i = 0; i < options.nbColorsPerPal && (!spec[i] || spec[i]->isOpaque()); ++i) {
|
for (size_t i = 0; i < options.nbColorsPerPal; ++i) {
|
||||||
// If the spec has a gap, there's no need to copy anything.
|
// If the spec has a gap, there's no need to copy anything.
|
||||||
if (spec[i]) {
|
if (spec[i].has_value() && !spec[i]->isTransparent()) {
|
||||||
pal[i] = spec[i]->cgbColor();
|
pal[i] = spec[i]->cgbColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -654,6 +657,7 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void outputPalettes(std::vector<Palette> const &palettes) {
|
static void outputPalettes(std::vector<Palette> const &palettes) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (options.verbosity >= Options::VERB_INTERM) {
|
if (options.verbosity >= Options::VERB_INTERM) {
|
||||||
for (auto &&palette : palettes) {
|
for (auto &&palette : palettes) {
|
||||||
fputs("{ ", stderr);
|
fputs("{ ", stderr);
|
||||||
@@ -663,6 +667,7 @@ static void outputPalettes(std::vector<Palette> const &palettes) {
|
|||||||
fputs("}\n", stderr);
|
fputs("}\n", stderr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
if (palettes.size() > options.nbPalettes) {
|
if (palettes.size() > options.nbPalettes) {
|
||||||
// If the palette generation is wrong, other (dependee) operations are likely to be
|
// If the palette generation is wrong, other (dependee) operations are likely to be
|
||||||
@@ -677,7 +682,9 @@ static void outputPalettes(std::vector<Palette> const &palettes) {
|
|||||||
if (!options.palettes.empty()) {
|
if (!options.palettes.empty()) {
|
||||||
File output;
|
File output;
|
||||||
if (!output.open(options.palettes, std::ios_base::out | std::ios_base::binary)) {
|
if (!output.open(options.palettes, std::ios_base::out | std::ios_base::binary)) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
fatal("Failed to create \"%s\": %s", output.c_str(options.palettes), strerror(errno));
|
fatal("Failed to create \"%s\": %s", output.c_str(options.palettes), strerror(errno));
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Palette const &palette : palettes) {
|
for (Palette const &palette : palettes) {
|
||||||
@@ -830,7 +837,9 @@ static void outputUnoptimizedTileData(
|
|||||||
) {
|
) {
|
||||||
File output;
|
File output;
|
||||||
if (!output.open(options.output, std::ios_base::out | std::ios_base::binary)) {
|
if (!output.open(options.output, std::ios_base::out | std::ios_base::binary)) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
fatal("Failed to create \"%s\": %s", output.c_str(options.output), strerror(errno));
|
fatal("Failed to create \"%s\": %s", output.c_str(options.output), strerror(errno));
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t widthTiles = options.inputSlice.width ? options.inputSlice.width : png.getWidth() / 8;
|
uint16_t widthTiles = options.inputSlice.width ? options.inputSlice.width : png.getWidth() / 8;
|
||||||
@@ -843,13 +852,16 @@ static void outputUnoptimizedTileData(
|
|||||||
remainingTiles -= options.trim;
|
remainingTiles -= options.trim;
|
||||||
|
|
||||||
for (auto [tile, attr] : zip(png.visitAsTiles(), attrmap)) {
|
for (auto [tile, attr] : zip(png.visitAsTiles(), attrmap)) {
|
||||||
// If the tile is fully transparent, default to palette 0
|
// Do not emit fully-background tiles.
|
||||||
Palette const &palette = palettes[attr.getPalID(mappings)];
|
if (!attr.isBackgroundTile()) {
|
||||||
for (uint32_t y = 0; y < 8; ++y) {
|
// If the tile is fully transparent, this defaults to palette 0.
|
||||||
uint16_t bitplanes = TileData::rowBitplanes(tile, palette, y);
|
Palette const &palette = palettes[attr.getPalID(mappings)];
|
||||||
output->sputc(bitplanes & 0xFF);
|
for (uint32_t y = 0; y < 8; ++y) {
|
||||||
if (options.bitDepth == 2) {
|
uint16_t bitplanes = TileData::rowBitplanes(tile, palette, y);
|
||||||
output->sputc(bitplanes >> 8);
|
output->sputc(bitplanes & 0xFF);
|
||||||
|
if (options.bitDepth == 2) {
|
||||||
|
output->sputc(bitplanes >> 8);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -869,7 +881,9 @@ static void outputUnoptimizedMaps(
|
|||||||
if (!path.empty()) {
|
if (!path.empty()) {
|
||||||
file.emplace();
|
file.emplace();
|
||||||
if (!file->open(path, std::ios_base::out | std::ios_base::binary)) {
|
if (!file->open(path, std::ios_base::out | std::ios_base::binary)) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
fatal("Failed to create \"%s\": %s", file->c_str(options.tilemap), strerror(errno));
|
fatal("Failed to create \"%s\": %s", file->c_str(options.tilemap), strerror(errno));
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -887,16 +901,21 @@ static void outputUnoptimizedMaps(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (tilemapOutput.has_value()) {
|
if (tilemapOutput.has_value()) {
|
||||||
(*tilemapOutput)->sputc(tileID + options.baseTileIDs[bank]);
|
(*tilemapOutput)
|
||||||
|
->sputc((attr.isBackgroundTile() ? 0 : tileID) + options.baseTileIDs[bank]);
|
||||||
}
|
}
|
||||||
|
uint8_t palID = attr.getPalID(mappings);
|
||||||
if (attrmapOutput.has_value()) {
|
if (attrmapOutput.has_value()) {
|
||||||
uint8_t palID = attr.getPalID(mappings) & 7;
|
(*attrmapOutput)->sputc((palID & 7) | bank << 3); // The other flags are all 0
|
||||||
(*attrmapOutput)->sputc(palID | bank << 3); // The other flags are all 0
|
|
||||||
}
|
}
|
||||||
if (palmapOutput.has_value()) {
|
if (palmapOutput.has_value()) {
|
||||||
(*palmapOutput)->sputc(attr.getPalID(mappings));
|
(*palmapOutput)->sputc(palID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Background tiles are skipped in the tile data, so they should be skipped in the maps too.
|
||||||
|
if (!attr.isBackgroundTile()) {
|
||||||
|
++tileID;
|
||||||
}
|
}
|
||||||
++tileID;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -989,22 +1008,30 @@ static UniqueTiles dedupTiles(
|
|||||||
|
|
||||||
bool inputWithoutOutput = !options.inputTileset.empty() && options.output.empty();
|
bool inputWithoutOutput = !options.inputTileset.empty() && options.output.empty();
|
||||||
for (auto [tile, attr] : zip(png.visitAsTiles(), attrmap)) {
|
for (auto [tile, attr] : zip(png.visitAsTiles(), attrmap)) {
|
||||||
auto [tileID, matchType] = tiles.addTile({tile, palettes[mappings[attr.protoPaletteID]]});
|
if (attr.isBackgroundTile()) {
|
||||||
|
attr.xFlip = false;
|
||||||
|
attr.yFlip = false;
|
||||||
|
attr.bank = 0;
|
||||||
|
attr.tileID = 0;
|
||||||
|
} else {
|
||||||
|
auto [tileID, matchType] =
|
||||||
|
tiles.addTile({tile, palettes[mappings[attr.protoPaletteID]]});
|
||||||
|
|
||||||
if (inputWithoutOutput && matchType == TileData::NOPE) {
|
if (inputWithoutOutput && matchType == TileData::NOPE) {
|
||||||
error(
|
error(
|
||||||
"Tile at (%" PRIu32 ", %" PRIu32
|
"Tile at (%" PRIu32 ", %" PRIu32
|
||||||
") is not within the input tileset, and `-o` was not given!",
|
") is not within the input tileset, and `-o` was not given!",
|
||||||
tile.x,
|
tile.x,
|
||||||
tile.y
|
tile.y
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
attr.xFlip = matchType == TileData::HFLIP || matchType == TileData::VHFLIP;
|
||||||
|
attr.yFlip = matchType == TileData::VFLIP || matchType == TileData::VHFLIP;
|
||||||
|
attr.bank = tileID >= options.maxNbTiles[0];
|
||||||
|
attr.tileID = (attr.bank ? tileID - options.maxNbTiles[0] : tileID)
|
||||||
|
+ options.baseTileIDs[attr.bank];
|
||||||
}
|
}
|
||||||
|
|
||||||
attr.xFlip = matchType == TileData::HFLIP || matchType == TileData::VHFLIP;
|
|
||||||
attr.yFlip = matchType == TileData::VFLIP || matchType == TileData::VHFLIP;
|
|
||||||
attr.bank = tileID >= options.maxNbTiles[0];
|
|
||||||
attr.tileID =
|
|
||||||
(attr.bank ? tileID - options.maxNbTiles[0] : tileID) + options.baseTileIDs[attr.bank];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy elision should prevent the contained `unordered_set` from being re-constructed
|
// Copy elision should prevent the contained `unordered_set` from being re-constructed
|
||||||
@@ -1014,7 +1041,9 @@ static UniqueTiles dedupTiles(
|
|||||||
static void outputTileData(UniqueTiles const &tiles) {
|
static void outputTileData(UniqueTiles const &tiles) {
|
||||||
File output;
|
File output;
|
||||||
if (!output.open(options.output, std::ios_base::out | std::ios_base::binary)) {
|
if (!output.open(options.output, std::ios_base::out | std::ios_base::binary)) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
fatal("Failed to create \"%s\": %s", output.c_str(options.output), strerror(errno));
|
fatal("Failed to create \"%s\": %s", output.c_str(options.output), strerror(errno));
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t tileID = 0;
|
uint16_t tileID = 0;
|
||||||
@@ -1036,7 +1065,9 @@ static void outputTileData(UniqueTiles const &tiles) {
|
|||||||
static void outputTilemap(DefaultInitVec<AttrmapEntry> const &attrmap) {
|
static void outputTilemap(DefaultInitVec<AttrmapEntry> const &attrmap) {
|
||||||
File output;
|
File output;
|
||||||
if (!output.open(options.tilemap, std::ios_base::out | std::ios_base::binary)) {
|
if (!output.open(options.tilemap, std::ios_base::out | std::ios_base::binary)) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
fatal("Failed to create \"%s\": %s", output.c_str(options.tilemap), strerror(errno));
|
fatal("Failed to create \"%s\": %s", output.c_str(options.tilemap), strerror(errno));
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
for (AttrmapEntry const &entry : attrmap) {
|
for (AttrmapEntry const &entry : attrmap) {
|
||||||
@@ -1049,7 +1080,9 @@ static void outputAttrmap(
|
|||||||
) {
|
) {
|
||||||
File output;
|
File output;
|
||||||
if (!output.open(options.attrmap, std::ios_base::out | std::ios_base::binary)) {
|
if (!output.open(options.attrmap, std::ios_base::out | std::ios_base::binary)) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
fatal("Failed to create \"%s\": %s", output.c_str(options.attrmap), strerror(errno));
|
fatal("Failed to create \"%s\": %s", output.c_str(options.attrmap), strerror(errno));
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
for (AttrmapEntry const &entry : attrmap) {
|
for (AttrmapEntry const &entry : attrmap) {
|
||||||
@@ -1065,7 +1098,9 @@ static void outputPalmap(
|
|||||||
) {
|
) {
|
||||||
File output;
|
File output;
|
||||||
if (!output.open(options.palmap, std::ios_base::out | std::ios_base::binary)) {
|
if (!output.open(options.palmap, std::ios_base::out | std::ios_base::binary)) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
fatal("Failed to create \"%s\": %s", output.c_str(options.palmap), strerror(errno));
|
fatal("Failed to create \"%s\": %s", output.c_str(options.palmap), strerror(errno));
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
for (AttrmapEntry const &entry : attrmap) {
|
for (AttrmapEntry const &entry : attrmap) {
|
||||||
@@ -1093,6 +1128,7 @@ void process() {
|
|||||||
// Now, we have all the image's colors in `colors`
|
// Now, we have all the image's colors in `colors`
|
||||||
// The next step is to order the palette
|
// The next step is to order the palette
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (options.verbosity >= Options::VERB_INTERM) {
|
if (options.verbosity >= Options::VERB_INTERM) {
|
||||||
fputs("Image colors: [ ", stderr);
|
fputs("Image colors: [ ", stderr);
|
||||||
for (auto const &slot : colors) {
|
for (auto const &slot : colors) {
|
||||||
@@ -1103,6 +1139,19 @@ void process() {
|
|||||||
}
|
}
|
||||||
fputs("]\n", stderr);
|
fputs("]\n", stderr);
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
|
if (options.palSpecType == Options::DMG) {
|
||||||
|
if (options.hasTransparentPixels) {
|
||||||
|
fatal(
|
||||||
|
"Image contains transparent pixels, not compatible with a DMG palette specification"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!png.isSuitableForGrayscale()) {
|
||||||
|
fatal("Image contains too many or non-gray colors, not compatible with a DMG palette "
|
||||||
|
"specification");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Now, iterate through the tiles, generating proto-palettes as we go
|
// Now, iterate through the tiles, generating proto-palettes as we go
|
||||||
// We do this unconditionally because this performs the image validation (which we want to
|
// We do this unconditionally because this performs the image validation (which we want to
|
||||||
@@ -1118,7 +1167,8 @@ void process() {
|
|||||||
std::unordered_set<uint16_t> tileColors;
|
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) {
|
||||||
if (Rgba color = tile.pixel(x, y); !color.isTransparent()) {
|
if (Rgba color = tile.pixel(x, y);
|
||||||
|
!color.isTransparent() || !options.hasTransparentPixels) {
|
||||||
tileColors.insert(color.cgbColor());
|
tileColors.insert(color.cgbColor());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1136,6 +1186,7 @@ void process() {
|
|||||||
|
|
||||||
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
|
||||||
|
assume(!isBgColorTransparent());
|
||||||
attrs.protoPaletteID = AttrmapEntry::transparent;
|
attrs.protoPaletteID = AttrmapEntry::transparent;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1145,6 +1196,21 @@ void process() {
|
|||||||
protoPalette.add(cgbColor);
|
protoPalette.add(cgbColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.bgColor.has_value()
|
||||||
|
&& std::find(RANGE(tileColors), options.bgColor->cgbColor()) != tileColors.end()) {
|
||||||
|
if (tileColors.size() == 1) {
|
||||||
|
// The tile contains just the background color, skip it.
|
||||||
|
attrs.protoPaletteID = AttrmapEntry::background;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
fatal(
|
||||||
|
"Tile (%" PRIu32 ", %" PRIu32 ") contains the background color (#%08x)!",
|
||||||
|
tile.x,
|
||||||
|
tile.y,
|
||||||
|
options.bgColor->toCSS()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 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 (protoPalette.compare(protoPalettes[n])) {
|
switch (protoPalette.compare(protoPalettes[n])) {
|
||||||
@@ -1152,17 +1218,6 @@ void process() {
|
|||||||
protoPalettes[n] = protoPalette; // 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))
|
||||||
//
|
|
||||||
// The following code does its job, except that references to the removed
|
|
||||||
// proto-palettes are not updated, causing issues.
|
|
||||||
// TODO: overlap might not be detrimental to the packing algorithm.
|
|
||||||
// Investigation is necessary, especially if pathological cases are found.
|
|
||||||
//
|
|
||||||
// for (size_t i = protoPalettes.size(); --i != n;) {
|
|
||||||
// if (protoPalette.compare(protoPalettes[i]) == ProtoPalette::WE_BIGGER) {
|
|
||||||
// protoPalettes.erase(protoPalettes.begin() + i);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
|
|
||||||
case ProtoPalette::THEY_BIGGER:
|
case ProtoPalette::THEY_BIGGER:
|
||||||
@@ -1176,7 +1231,7 @@ void process() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
attrs.protoPaletteID = protoPalettes.size();
|
attrs.protoPaletteID = protoPalettes.size();
|
||||||
if (protoPalettes.size() == AttrmapEntry::transparent) { // Check for overflow
|
if (protoPalettes.size() == AttrmapEntry::background) { // Check for overflow
|
||||||
fatal(
|
fatal(
|
||||||
"Reached %zu proto-palettes... sorry, this image is too much for me to handle :(",
|
"Reached %zu proto-palettes... sorry, this image is too much for me to handle :(",
|
||||||
AttrmapEntry::transparent
|
AttrmapEntry::transparent
|
||||||
@@ -1192,6 +1247,7 @@ continue_visiting_tiles:;
|
|||||||
protoPalettes.size(),
|
protoPalettes.size(),
|
||||||
protoPalettes.size() != 1 ? "s" : ""
|
protoPalettes.size() != 1 ? "s" : ""
|
||||||
);
|
);
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (options.verbosity >= Options::VERB_INTERM) {
|
if (options.verbosity >= Options::VERB_INTERM) {
|
||||||
for (auto const &protoPal : protoPalettes) {
|
for (auto const &protoPal : protoPalettes) {
|
||||||
fputs("[ ", stderr);
|
fputs("[ ", stderr);
|
||||||
@@ -1201,13 +1257,15 @@ continue_visiting_tiles:;
|
|||||||
fputs("]\n", stderr);
|
fputs("]\n", stderr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
if (options.palSpecType == Options::EMBEDDED) {
|
if (options.palSpecType == Options::EMBEDDED) {
|
||||||
generatePalSpec(png);
|
generatePalSpec(png);
|
||||||
}
|
}
|
||||||
auto [mappings, palettes] = options.palSpecType == Options::NO_SPEC
|
auto [mappings, palettes] =
|
||||||
? generatePalettes(protoPalettes, png)
|
options.palSpecType == Options::NO_SPEC || options.palSpecType == Options::DMG
|
||||||
: makePalsAsSpecified(protoPalettes);
|
? generatePalettes(protoPalettes, png)
|
||||||
|
: makePalsAsSpecified(protoPalettes);
|
||||||
outputPalettes(palettes);
|
outputPalettes(palettes);
|
||||||
|
|
||||||
// If deduplication is not happening, we just need to output the tile data and/or maps as-is
|
// If deduplication is not happening, we just need to output the tile data and/or maps as-is
|
||||||
|
|||||||
@@ -195,12 +195,13 @@ void reverse() {
|
|||||||
Options::VERB_INTERM, "Reversed image dimensions: %zux%zu tiles\n", width, height
|
Options::VERB_INTERM, "Reversed image dimensions: %zux%zu tiles\n", width, height
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: `-U` to configure tile size beyond 8x8px ("deduplication units")
|
Rgba const grayColors[4] = {
|
||||||
|
Rgba(0xFFFFFFFF), Rgba(0xAAAAAAFF), Rgba(0x555555FF), Rgba(0x000000FF)
|
||||||
std::vector<std::array<std::optional<Rgba>, 4>> palettes{
|
|
||||||
{Rgba(0xFFFFFFFF), Rgba(0xAAAAAAFF), Rgba(0x555555FF), Rgba(0x000000FF)}
|
|
||||||
};
|
};
|
||||||
// If a palette file is used as input, it overrides the default colors.
|
std::vector<std::array<std::optional<Rgba>, 4>> palettes{
|
||||||
|
{grayColors[0], grayColors[1], grayColors[2], grayColors[3]}
|
||||||
|
};
|
||||||
|
// If a palette file or palette spec is used as input, it overrides the default colors.
|
||||||
if (!options.palettes.empty()) {
|
if (!options.palettes.empty()) {
|
||||||
File file;
|
File file;
|
||||||
if (!file.open(options.palettes, std::ios::in | std::ios::binary)) {
|
if (!file.open(options.palettes, std::ios::in | std::ios::binary)) {
|
||||||
@@ -257,6 +258,10 @@ void reverse() {
|
|||||||
putc('\n', stderr);
|
putc('\n', stderr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (options.palSpecType == Options::DMG) {
|
||||||
|
for (size_t i = 0; i < palettes[0].size(); ++i) {
|
||||||
|
palettes[0][i] = grayColors[options.dmgValue(i)];
|
||||||
|
}
|
||||||
} else if (options.palSpecType == Options::EMBEDDED) {
|
} else if (options.palSpecType == Options::EMBEDDED) {
|
||||||
warning("An embedded palette was requested, but no palette file was specified; ignoring "
|
warning("An embedded palette was requested, but no palette file was specified; ignoring "
|
||||||
"request.");
|
"request.");
|
||||||
@@ -401,7 +406,9 @@ void reverse() {
|
|||||||
options.verbosePrint(Options::VERB_LOG_ACT, "Writing image...\n");
|
options.verbosePrint(Options::VERB_LOG_ACT, "Writing image...\n");
|
||||||
File pngFile;
|
File pngFile;
|
||||||
if (!pngFile.open(options.input, std::ios::out | std::ios::binary)) {
|
if (!pngFile.open(options.input, std::ios::out | std::ios::binary)) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
fatal("Failed to create \"%s\": %s", pngFile.c_str(options.input), strerror(errno));
|
fatal("Failed to create \"%s\": %s", pngFile.c_str(options.input), strerror(errno));
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
png_structp png = png_create_write_struct(
|
png_structp png = png_create_write_struct(
|
||||||
PNG_LIBPNG_VER_STRING,
|
PNG_LIBPNG_VER_STRING,
|
||||||
@@ -410,46 +417,85 @@ void reverse() {
|
|||||||
pngWarning
|
pngWarning
|
||||||
);
|
);
|
||||||
if (!png) {
|
if (!png) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
fatal("Failed to create PNG write struct: %s", strerror(errno));
|
fatal("Failed to create PNG write struct: %s", strerror(errno));
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
png_infop pngInfo = png_create_info_struct(png);
|
png_infop pngInfo = png_create_info_struct(png);
|
||||||
if (!pngInfo) {
|
if (!pngInfo) {
|
||||||
fatal("Failed to create PNG info struct: %s", strerror(errno));
|
// LCOV_EXCL_START
|
||||||
|
fatal("Failed to create PNG info structure: %s", strerror(errno));
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
png_set_write_fn(png, &pngFile, writePng, flushPng);
|
png_set_write_fn(png, &pngFile, writePng, flushPng);
|
||||||
|
|
||||||
|
int pngColorType = options.palettes.empty() ? PNG_COLOR_TYPE_GRAY
|
||||||
|
: palettes.size() == 1 ? PNG_COLOR_TYPE_PALETTE
|
||||||
|
: PNG_COLOR_TYPE_RGB_ALPHA;
|
||||||
|
int pngDepth = options.palettes.empty() ? options.bitDepth : 8;
|
||||||
|
|
||||||
png_set_IHDR(
|
png_set_IHDR(
|
||||||
png,
|
png,
|
||||||
pngInfo,
|
pngInfo,
|
||||||
width * 8,
|
width * 8,
|
||||||
height * 8,
|
height * 8,
|
||||||
8,
|
pngDepth,
|
||||||
PNG_COLOR_TYPE_RGB_ALPHA,
|
pngColorType,
|
||||||
PNG_INTERLACE_NONE,
|
PNG_INTERLACE_NONE,
|
||||||
PNG_COMPRESSION_TYPE_DEFAULT,
|
PNG_COMPRESSION_TYPE_DEFAULT,
|
||||||
PNG_FILTER_TYPE_DEFAULT
|
PNG_FILTER_TYPE_DEFAULT
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (pngColorType != PNG_COLOR_TYPE_GRAY) {
|
||||||
|
png_color_8 sbitChunk;
|
||||||
|
sbitChunk.red = 5;
|
||||||
|
sbitChunk.green = 5;
|
||||||
|
sbitChunk.blue = 5;
|
||||||
|
if (pngColorType == PNG_COLOR_TYPE_RGB_ALPHA) {
|
||||||
|
sbitChunk.alpha = 1;
|
||||||
|
}
|
||||||
|
png_set_sBIT(png, pngInfo, &sbitChunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pngColorType == PNG_COLOR_TYPE_PALETTE) {
|
||||||
|
assume(palettes.size() == 1);
|
||||||
|
png_color pngPalette[4] = {};
|
||||||
|
png_byte pngTrans[4] = {};
|
||||||
|
int nbPngColors = 0, nbPngTrans = 0;
|
||||||
|
for (auto const &color : palettes[0]) {
|
||||||
|
if (!color.has_value()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
pngPalette[nbPngColors].red = color->red;
|
||||||
|
pngPalette[nbPngColors].green = color->green;
|
||||||
|
pngPalette[nbPngColors].blue = color->blue;
|
||||||
|
pngTrans[nbPngColors] = color->alpha;
|
||||||
|
++nbPngColors;
|
||||||
|
if (color->alpha < 255) {
|
||||||
|
nbPngTrans = nbPngColors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
png_set_PLTE(png, pngInfo, pngPalette, nbPngColors);
|
||||||
|
if (nbPngTrans > 0) {
|
||||||
|
png_set_tRNS(png, pngInfo, pngTrans, nbPngTrans, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
png_write_info(png, pngInfo);
|
png_write_info(png, pngInfo);
|
||||||
|
|
||||||
png_color_8 sbitChunk;
|
// N bits/pixel * 8 pixels/tile row / 8 bits/byte = N bytes/tile row
|
||||||
sbitChunk.red = 5;
|
uint8_t const bytesPerTileRow = pngColorType == PNG_COLOR_TYPE_RGB_ALPHA ? 32 : pngDepth;
|
||||||
sbitChunk.green = 5;
|
size_t const bytesPerRow = width * bytesPerTileRow;
|
||||||
sbitChunk.blue = 5;
|
std::vector<uint8_t> tileRow(8 * bytesPerRow, 0xFF); // Data for 8 rows of pixels
|
||||||
sbitChunk.alpha = 1;
|
|
||||||
png_set_sBIT(png, pngInfo, &sbitChunk);
|
|
||||||
|
|
||||||
constexpr uint8_t SIZEOF_TILE = 4 * 8; // 4 bytes/pixel (RGBA @ 8 bits/channel) * 8 pixels/tile
|
|
||||||
size_t const SIZEOF_ROW = width * SIZEOF_TILE;
|
|
||||||
std::vector<uint8_t> tileRow(8 * SIZEOF_ROW, 0xFF); // Data for 8 rows of pixels
|
|
||||||
uint8_t * const rowPtrs[8] = {
|
uint8_t * const rowPtrs[8] = {
|
||||||
&tileRow.data()[0 * SIZEOF_ROW],
|
&tileRow.data()[0 * bytesPerRow],
|
||||||
&tileRow.data()[1 * SIZEOF_ROW],
|
&tileRow.data()[1 * bytesPerRow],
|
||||||
&tileRow.data()[2 * SIZEOF_ROW],
|
&tileRow.data()[2 * bytesPerRow],
|
||||||
&tileRow.data()[3 * SIZEOF_ROW],
|
&tileRow.data()[3 * bytesPerRow],
|
||||||
&tileRow.data()[4 * SIZEOF_ROW],
|
&tileRow.data()[4 * bytesPerRow],
|
||||||
&tileRow.data()[5 * SIZEOF_ROW],
|
&tileRow.data()[5 * bytesPerRow],
|
||||||
&tileRow.data()[6 * SIZEOF_ROW],
|
&tileRow.data()[6 * bytesPerRow],
|
||||||
&tileRow.data()[7 * SIZEOF_ROW],
|
&tileRow.data()[7 * bytesPerRow],
|
||||||
};
|
};
|
||||||
|
|
||||||
for (size_t ty = 0; ty < height; ++ty) {
|
for (size_t ty = 0; ty < height; ++ty) {
|
||||||
@@ -482,19 +528,36 @@ void reverse() {
|
|||||||
bitplane0 = flipTable[bitplane0];
|
bitplane0 = flipTable[bitplane0];
|
||||||
bitplane1 = flipTable[bitplane1];
|
bitplane1 = flipTable[bitplane1];
|
||||||
}
|
}
|
||||||
uint8_t *ptr = &rowPtrs[y][tx * SIZEOF_TILE];
|
|
||||||
|
uint8_t *ptr = &rowPtrs[y][tx * bytesPerTileRow];
|
||||||
|
uint16_t gray = 0;
|
||||||
for (uint8_t x = 0; x < 8; ++x) {
|
for (uint8_t x = 0; x < 8; ++x) {
|
||||||
uint8_t bit0 = bitplane0 & 0x80, bit1 = bitplane1 & 0x80;
|
uint8_t bit0 = bitplane0 & 0x80, bit1 = bitplane1 & 0x80;
|
||||||
Rgba const &pixel = *palette[bit0 >> 7 | bit1 >> 6];
|
uint8_t colorID = bit0 >> 7 | bit1 >> 6;
|
||||||
*ptr++ = pixel.red;
|
Rgba const &pixel = *palette[colorID];
|
||||||
*ptr++ = pixel.green;
|
|
||||||
*ptr++ = pixel.blue;
|
if (pngColorType == PNG_COLOR_TYPE_GRAY) {
|
||||||
*ptr++ = pixel.alpha;
|
gray = gray << pngDepth | (pixel.red & ((1 << pngDepth) - 1));
|
||||||
|
} else if (pngColorType == PNG_COLOR_TYPE_PALETTE) {
|
||||||
|
*ptr++ = palID * 4 + colorID;
|
||||||
|
} else {
|
||||||
|
*ptr++ = pixel.red;
|
||||||
|
*ptr++ = pixel.green;
|
||||||
|
*ptr++ = pixel.blue;
|
||||||
|
*ptr++ = pixel.alpha;
|
||||||
|
}
|
||||||
|
|
||||||
// Shift the pixel out
|
// Shift the pixel out
|
||||||
bitplane0 <<= 1;
|
bitplane0 <<= 1;
|
||||||
bitplane1 <<= 1;
|
bitplane1 <<= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pngDepth == 1) {
|
||||||
|
*ptr = gray;
|
||||||
|
} else if (pngDepth == 2) {
|
||||||
|
*ptr++ = gray >> 8;
|
||||||
|
*ptr = gray & 0xff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// We never modify the pointers, and neither should libpng, despite the overly lax function
|
// We never modify the pointers, and neither should libpng, despite the overly lax function
|
||||||
|
|||||||
@@ -58,6 +58,15 @@ uint16_t Rgba::cgbColor() const {
|
|||||||
|
|
||||||
uint8_t Rgba::grayIndex() const {
|
uint8_t Rgba::grayIndex() const {
|
||||||
assume(isGray());
|
assume(isGray());
|
||||||
// Convert from [0; 256[ to [0; maxOpaqueColors[
|
// 2bpp shades are inverted from RGB PNG; %00 = white, %11 = black
|
||||||
return static_cast<uint16_t>(255 - red) * options.maxOpaqueColors() / 256;
|
uint8_t gray = 255 - red;
|
||||||
|
if (options.palSpecType == Options::DMG) {
|
||||||
|
assume(!options.hasTransparentPixels);
|
||||||
|
// Reduce gray shade from 0..<256 to 0..<4, then map to color index,
|
||||||
|
// then reduce to 0..<nbColorsPerPal
|
||||||
|
return options.dmgColors[gray * 4 / 256] * options.nbColorsPerPal / 4;
|
||||||
|
}
|
||||||
|
// Reduce gray shade from 0..<256 to hasTransparentPixels..<nbColorsPerPal
|
||||||
|
// Note that `maxOpaqueColors()` already takes `hasTransparentPixels` into account
|
||||||
|
return gray * options.maxOpaqueColors() / 256 + options.hasTransparentPixels;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -424,5 +424,5 @@ max_out:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unreachable_();
|
unreachable_(); // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ static option const longopts[] = {
|
|||||||
{nullptr, no_argument, nullptr, 0 }
|
{nullptr, no_argument, nullptr, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
static void printUsage() {
|
static void printUsage() {
|
||||||
fputs(
|
fputs(
|
||||||
"Usage: rgblink [-dhMtVvwx] [-l script] [-m map_file] [-n sym_file]\n"
|
"Usage: rgblink [-dhMtVvwx] [-l script] [-m map_file] [-n sym_file]\n"
|
||||||
@@ -181,6 +182,7 @@ static void printUsage() {
|
|||||||
stderr
|
stderr
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
enum ScrambledRegion {
|
enum ScrambledRegion {
|
||||||
SCRAMBLE_ROMX,
|
SCRAMBLE_ROMX,
|
||||||
@@ -335,8 +337,10 @@ int main(int argc, char *argv[]) {
|
|||||||
isWRAM0Mode = true;
|
isWRAM0Mode = true;
|
||||||
break;
|
break;
|
||||||
case 'h':
|
case 'h':
|
||||||
|
// LCOV_EXCL_START
|
||||||
printUsage();
|
printUsage();
|
||||||
exit(0);
|
exit(0);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
case 'l':
|
case 'l':
|
||||||
if (linkerScriptName) {
|
if (linkerScriptName) {
|
||||||
warnx("Overriding linker script %s", linkerScriptName);
|
warnx("Overriding linker script %s", linkerScriptName);
|
||||||
@@ -393,11 +397,15 @@ int main(int argc, char *argv[]) {
|
|||||||
is32kMode = true;
|
is32kMode = true;
|
||||||
break;
|
break;
|
||||||
case 'V':
|
case 'V':
|
||||||
|
// LCOV_EXCL_START
|
||||||
printf("rgblink %s\n", get_package_version_string());
|
printf("rgblink %s\n", get_package_version_string());
|
||||||
exit(0);
|
exit(0);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
case 'v':
|
case 'v':
|
||||||
|
// LCOV_EXCL_START
|
||||||
beVerbose = true;
|
beVerbose = true;
|
||||||
break;
|
break;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
case 'w':
|
case 'w':
|
||||||
isWRAM0Mode = true;
|
isWRAM0Mode = true;
|
||||||
break;
|
break;
|
||||||
@@ -407,8 +415,10 @@ int main(int argc, char *argv[]) {
|
|||||||
is32kMode = true;
|
is32kMode = true;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
// LCOV_EXCL_START
|
||||||
printUsage();
|
printUsage();
|
||||||
exit(1);
|
exit(1);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#include "linkdefs.hpp"
|
#include "linkdefs.hpp"
|
||||||
#include "platform.hpp"
|
#include "platform.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
#include "link/main.hpp"
|
#include "link/main.hpp"
|
||||||
#include "link/section.hpp"
|
#include "link/section.hpp"
|
||||||
@@ -30,6 +31,7 @@ FILE *mapFile;
|
|||||||
struct SortedSymbol {
|
struct SortedSymbol {
|
||||||
Symbol const *sym;
|
Symbol const *sym;
|
||||||
uint16_t addr;
|
uint16_t addr;
|
||||||
|
uint16_t parentAddr;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SortedSections {
|
struct SortedSections {
|
||||||
@@ -259,24 +261,13 @@ static void writeROM() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks whether this character is legal as the first character of a symbol's name in a sym file
|
|
||||||
static bool canStartSymName(char c) {
|
|
||||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks whether this character is legal in a symbol's name in a sym file
|
|
||||||
static bool isLegalForSymName(char c) {
|
|
||||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_'
|
|
||||||
|| c == '@' || c == '#' || c == '$' || c == '.';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prints a symbol's name to a file, assuming that the first character is legal.
|
// Prints a symbol's name to a file, assuming that the first character is legal.
|
||||||
// Illegal characters are UTF-8-decoded (errors are replaced by U+FFFD) and emitted as `\u`/`\U`.
|
// Illegal characters are UTF-8-decoded (errors are replaced by U+FFFD) and emitted as '\u'/'\U'.
|
||||||
static void printSymName(std::string const &name, FILE *file) {
|
static void printSymName(std::string const &name, FILE *file) {
|
||||||
for (char const *ptr = name.c_str(); *ptr != '\0';) {
|
for (char const *ptr = name.c_str(); *ptr != '\0';) {
|
||||||
char c = *ptr;
|
char c = *ptr;
|
||||||
|
|
||||||
if (isLegalForSymName(c)) {
|
if (continuesIdentifier(c)) {
|
||||||
// Output legal ASCII characters as-is
|
// Output legal ASCII characters as-is
|
||||||
putc(c, file);
|
putc(c, file);
|
||||||
++ptr;
|
++ptr;
|
||||||
@@ -306,33 +297,28 @@ static void printSymName(std::string const &name, FILE *file) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Comparator function for `std::stable_sort` to sort symbols
|
// Comparator function for `std::stable_sort` to sort symbols
|
||||||
// Symbols are ordered by address, then by parentage
|
|
||||||
static bool compareSymbols(SortedSymbol const &sym1, SortedSymbol const &sym2) {
|
static bool compareSymbols(SortedSymbol const &sym1, SortedSymbol const &sym2) {
|
||||||
|
// First, sort by address
|
||||||
if (sym1.addr != sym2.addr) {
|
if (sym1.addr != sym2.addr) {
|
||||||
return sym1.addr < sym2.addr;
|
return sym1.addr < sym2.addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Second, sort by locality (global before local)
|
||||||
std::string const &sym1_name = sym1.sym->name;
|
std::string const &sym1_name = sym1.sym->name;
|
||||||
std::string const &sym2_name = sym2.sym->name;
|
std::string const &sym2_name = sym2.sym->name;
|
||||||
bool sym1_local = sym1_name.find(".") != std::string::npos;
|
bool sym1_local = sym1_name.find('.') != std::string::npos;
|
||||||
bool sym2_local = sym2_name.find(".") != std::string::npos;
|
bool sym2_local = sym2_name.find('.') != std::string::npos;
|
||||||
|
|
||||||
if (sym1_local != sym2_local) {
|
if (sym1_local != sym2_local) {
|
||||||
size_t sym1_len = sym1_name.length();
|
return sym1_local < sym2_local;
|
||||||
size_t sym2_len = sym2_name.length();
|
|
||||||
|
|
||||||
// Sort parent labels before their child local labels
|
|
||||||
if (sym2_name.starts_with(sym1_name) && sym2_name[sym1_len] == '.') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (sym1_name.starts_with(sym2_name) && sym1_name[sym2_len] == '.') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Sort local labels before unrelated global labels
|
|
||||||
return sym1_local;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
// Third, sort by parent address
|
||||||
|
if (sym1.parentAddr != sym2.parentAddr) {
|
||||||
|
return sym1.parentAddr < sym2.parentAddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fourth, sort by name
|
||||||
|
return sym1_name < sym2_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write a bank's contents to the sym file
|
// Write a bank's contents to the sym file
|
||||||
@@ -368,11 +354,20 @@ static void writeSymBank(SortedSections const &bankSections, SectionType type, u
|
|||||||
forEachSortedSection(sect, {
|
forEachSortedSection(sect, {
|
||||||
for (Symbol const *sym : sect->symbols) {
|
for (Symbol const *sym : sect->symbols) {
|
||||||
// Don't output symbols that begin with an illegal character
|
// Don't output symbols that begin with an illegal character
|
||||||
if (!sym->name.empty() && canStartSymName(sym->name[0])) {
|
if (!sym->name.empty() && startsIdentifier(sym->name[0])) {
|
||||||
symList.push_back({
|
uint16_t addr = static_cast<uint16_t>(sym->label().offset + sect->org);
|
||||||
.sym = sym,
|
uint16_t parentAddr = addr;
|
||||||
.addr = static_cast<uint16_t>(sym->label().offset + sect->org),
|
if (auto pos = sym->name.find('.'); pos != std::string::npos) {
|
||||||
});
|
std::string parentName = sym->name.substr(0, pos);
|
||||||
|
if (Symbol const *parentSym = sym_GetSymbol(parentName);
|
||||||
|
parentSym && parentSym->data.holds<Label>()) {
|
||||||
|
auto const &parentLabel = parentSym->label();
|
||||||
|
assume(parentLabel.section != nullptr);
|
||||||
|
parentAddr =
|
||||||
|
static_cast<uint16_t>(parentLabel.offset + parentLabel.section->org);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
symList.push_back({.sym = sym, .addr = addr, .parentAddr = parentAddr});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -471,10 +466,13 @@ static void writeMapBank(SortedSections const §List, SectionType type, uint3
|
|||||||
// Also print symbols in the following "pieces"
|
// Also print symbols in the following "pieces"
|
||||||
for (uint16_t org = sect->org; sect; sect = sect->nextu.get()) {
|
for (uint16_t org = sect->org; sect; sect = sect->nextu.get()) {
|
||||||
for (Symbol *sym : sect->symbols) {
|
for (Symbol *sym : sect->symbols) {
|
||||||
// Space matches "\tSECTION: $xxxx ..."
|
// Don't output symbols that begin with an illegal character
|
||||||
fprintf(mapFile, "\t $%04" PRIx32 " = ", sym->label().offset + org);
|
if (!sym->name.empty() && startsIdentifier(sym->name[0])) {
|
||||||
printSymName(sym->name, mapFile);
|
// Space matches "\tSECTION: $xxxx ..."
|
||||||
putc('\n', mapFile);
|
fprintf(mapFile, "\t $%04" PRIx32 " = ", sym->label().offset + org);
|
||||||
|
printSymName(sym->name, mapFile);
|
||||||
|
putc('\n', mapFile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sect->nextu) {
|
if (sect->nextu) {
|
||||||
|
|||||||
@@ -363,7 +363,6 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
|||||||
case RPN_RST:
|
case RPN_RST:
|
||||||
value = popRPN(patch);
|
value = popRPN(patch);
|
||||||
// Acceptable values are 0x00, 0x08, 0x10, ..., 0x38
|
// Acceptable values are 0x00, 0x08, 0x10, ..., 0x38
|
||||||
// They can be easily checked with a bitmask
|
|
||||||
if (value & ~0x38) {
|
if (value & ~0x38) {
|
||||||
if (!isError) {
|
if (!isError) {
|
||||||
error(patch.src, patch.lineNo, "Value $%" PRIx32 " is not a RST vector", value);
|
error(patch.src, patch.lineNo, "Value $%" PRIx32 " is not a RST vector", value);
|
||||||
@@ -374,6 +373,21 @@ static int32_t computeRPNExpr(Patch const &patch, std::vector<Symbol> const &fil
|
|||||||
value |= 0xC7;
|
value |= 0xC7;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case RPN_BIT_INDEX: {
|
||||||
|
value = popRPN(patch);
|
||||||
|
int32_t mask = getRPNByte(expression, size, patch);
|
||||||
|
// Acceptable values are 0 to 7
|
||||||
|
if (value & ~0x07) {
|
||||||
|
if (!isError) {
|
||||||
|
error(patch.src, patch.lineNo, "Value $%" PRIx32 " is not a bit index", value);
|
||||||
|
isError = true;
|
||||||
|
}
|
||||||
|
value = 0;
|
||||||
|
}
|
||||||
|
value = mask | (value << 3);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case RPN_CONST:
|
case RPN_CONST:
|
||||||
value = 0;
|
value = 0;
|
||||||
for (uint8_t shift = 0; shift < 32; shift += 8) {
|
for (uint8_t shift = 0; shift < 32; shift += 8) {
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ static uint8_t parseHexDigit(int c) {
|
|||||||
} else if (c >= 'a' && c <= 'f') {
|
} else if (c >= 'a' && c <= 'f') {
|
||||||
return c - 'a' + 10;
|
return c - 'a' + 10;
|
||||||
} else {
|
} else {
|
||||||
unreachable_();
|
unreachable_(); // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,10 +251,16 @@ yy::parser::symbol_type yylex() {
|
|||||||
if (c == EOF) {
|
if (c == EOF) {
|
||||||
// Basically yywrap().
|
// Basically yywrap().
|
||||||
if (lexerStack.size() != 1) {
|
if (lexerStack.size() != 1) {
|
||||||
lexerStack.pop_back();
|
if (!atEof) {
|
||||||
return yylex();
|
// Inject a newline at EOF to simplify parsing.
|
||||||
|
atEof = true;
|
||||||
|
return yy::parser::make_newline();
|
||||||
|
} else {
|
||||||
|
lexerStack.pop_back();
|
||||||
|
return yylex();
|
||||||
|
}
|
||||||
} else if (!atEof) {
|
} else if (!atEof) {
|
||||||
// Inject a newline at EOF, to avoid errors for files that don't end with one.
|
// Inject a newline at EOF to simplify parsing.
|
||||||
atEof = true;
|
atEof = true;
|
||||||
return yy::parser::make_newline();
|
return yy::parser::make_newline();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
|
||||||
#include "helpers.hpp" // assume
|
#include "helpers.hpp" // assume, literal_strlen
|
||||||
#include "linkdefs.hpp"
|
#include "linkdefs.hpp"
|
||||||
#include "platform.hpp"
|
#include "platform.hpp"
|
||||||
|
|
||||||
@@ -34,28 +34,31 @@ static char const *delim = " \f\n\r\t\v"; // Whitespace according to the C and P
|
|||||||
|
|
||||||
static int
|
static int
|
||||||
nextLine(std::vector<char> &lineBuf, uint32_t &lineNo, FileStackNode const &where, FILE *file) {
|
nextLine(std::vector<char> &lineBuf, uint32_t &lineNo, FileStackNode const &where, FILE *file) {
|
||||||
retry:
|
int firstChar;
|
||||||
++lineNo;
|
for (;;) {
|
||||||
int firstChar = getc(file);
|
++lineNo;
|
||||||
lineBuf.clear();
|
firstChar = getc(file);
|
||||||
|
lineBuf.clear();
|
||||||
|
|
||||||
switch (firstChar) {
|
switch (firstChar) {
|
||||||
case EOF:
|
case EOF:
|
||||||
return EOF;
|
return EOF;
|
||||||
case ';':
|
case ';':
|
||||||
// Discard comment line
|
// Discard comment line
|
||||||
// TODO: if `;!FILE [...]` on the first line (`lineNo`), return it
|
// TODO: if `;!FILE [...]` on the first line (`lineNo`), return it
|
||||||
do {
|
do {
|
||||||
firstChar = getc(file);
|
firstChar = getc(file);
|
||||||
} while (firstChar != EOF && firstChar != '\r' && firstChar != '\n');
|
} while (firstChar != EOF && firstChar != '\r' && firstChar != '\n');
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case '\r':
|
case '\r':
|
||||||
if (firstChar == '\r' && getc(file) != '\n') {
|
if (firstChar == '\r' && getc(file) != '\n') {
|
||||||
consumeLF(where, lineNo, file);
|
consumeLF(where, lineNo, file);
|
||||||
|
}
|
||||||
|
[[fallthrough]];
|
||||||
|
case '\n':
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
[[fallthrough]];
|
break;
|
||||||
case '\n':
|
|
||||||
goto retry;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
@@ -446,7 +449,7 @@ void sdobj_ReadFile(FileStackNode const &where, FILE *file, std::vector<Symbol>
|
|||||||
// It's fine to keep modifying the symbol after `AddSymbol`, only
|
// It's fine to keep modifying the symbol after `AddSymbol`, only
|
||||||
// the name must not be modified
|
// the name must not be modified
|
||||||
}
|
}
|
||||||
if (strncasecmp(&token[1], "ef", 2) != 0) {
|
if (strncasecmp(&token[1], "ef", literal_strlen("ef")) != 0) {
|
||||||
fatal(&where, lineNo, "'S' line is neither \"Def\" nor \"Ref\"");
|
fatal(&where, lineNo, "'S' line is neither \"Def\" nor \"Ref\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -208,8 +208,10 @@ static void mergeSections(Section &target, std::unique_ptr<Section> &&other) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case SECTION_NORMAL:
|
case SECTION_NORMAL:
|
||||||
|
// LCOV_EXCL_START
|
||||||
unreachable_();
|
unreachable_();
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
// Note that the order in which fragments are stored in the `nextu` list does not
|
// Note that the order in which fragments are stored in the `nextu` list does not
|
||||||
// really matter, only that offsets were properly computed above
|
// really matter, only that offsets were properly computed above
|
||||||
|
|||||||
@@ -6,6 +6,15 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
bool startsIdentifier(int c) {
|
||||||
|
// This returns false for anonymous labels, which internally start with a '!'
|
||||||
|
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '.' || c == '_';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool continuesIdentifier(int c) {
|
||||||
|
return startsIdentifier(c) || (c >= '0' && c <= '9') || c == '#' || c == '$' || c == '@';
|
||||||
|
}
|
||||||
|
|
||||||
char const *printChar(int c) {
|
char const *printChar(int c) {
|
||||||
// "'A'" + '\0': 4 bytes
|
// "'A'" + '\0': 4 bytes
|
||||||
// "'\\n'" + '\0': 5 bytes
|
// "'\\n'" + '\0': 5 bytes
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ error: anon-label-bad.asm(6):
|
|||||||
error: anon-label-bad.asm(9):
|
error: anon-label-bad.asm(9):
|
||||||
syntax error, unexpected anonymous label
|
syntax error, unexpected anonymous label
|
||||||
error: anon-label-bad.asm(10):
|
error: anon-label-bad.asm(10):
|
||||||
syntax error, unexpected anonymous label, expecting label or identifier or local identifier
|
syntax error, unexpected anonymous label, expecting symbol or label or local label
|
||||||
error: anon-label-bad.asm(22):
|
error: anon-label-bad.asm(22):
|
||||||
syntax error, unexpected ::
|
syntax error, unexpected ::
|
||||||
error: Assembly aborted (5 errors)!
|
error: Assembly aborted (5 errors)!
|
||||||
|
|||||||
@@ -7,6 +7,17 @@ FOR V, 1, 100
|
|||||||
PRINTLN "cont"
|
PRINTLN "cont"
|
||||||
ENDR
|
ENDR
|
||||||
WARN "done {d:V}"
|
WARN "done {d:V}"
|
||||||
|
rept 2
|
||||||
|
break
|
||||||
|
; skips nested code
|
||||||
|
rept 3
|
||||||
|
println "\tinner"
|
||||||
|
endr
|
||||||
|
if 1
|
||||||
|
println "\tconditional"
|
||||||
|
endc
|
||||||
|
println "outer"
|
||||||
|
endr
|
||||||
rept 1
|
rept 1
|
||||||
break
|
break
|
||||||
; skips invalid code
|
; skips invalid code
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
warning: break.asm(9): [-Wuser]
|
warning: break.asm(9): [-Wuser]
|
||||||
done 5
|
done 5
|
||||||
warning: break.asm(17): [-Wuser]
|
warning: break.asm(28): [-Wuser]
|
||||||
OK
|
OK
|
||||||
error: break.asm(18):
|
error: break.asm(29):
|
||||||
BREAK can only be used inside a REPT/FOR block
|
BREAK can only be used inside a REPT/FOR block
|
||||||
FATAL: break.asm(19) -> break.asm::REPT~1(23):
|
FATAL: break.asm(30) -> break.asm::REPT~1(34):
|
||||||
Ended block with 1 unterminated IF construct
|
Ended block with 1 unterminated IF construct
|
||||||
|
|||||||
27
test/asm/charcmp.asm
Normal file
27
test/asm/charcmp.asm
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
charmap "a", 1
|
||||||
|
charmap "b", 2
|
||||||
|
charmap "c", 0
|
||||||
|
charmap "w", 3, 2, 1
|
||||||
|
charmap "x", 1, 2
|
||||||
|
charmap "y", 2, 1
|
||||||
|
charmap "z", 1, 2, 3
|
||||||
|
|
||||||
|
macro test
|
||||||
|
println strfmt("\"%#s\" <=> \"%#s\" == %d", \1, \2, charcmp(\1, \2))
|
||||||
|
endm
|
||||||
|
|
||||||
|
test "", ""
|
||||||
|
test "a", "a"
|
||||||
|
test "aa", "aaa"
|
||||||
|
test "aaa", "aa"
|
||||||
|
test "a", "b"
|
||||||
|
test "b", "a"
|
||||||
|
test "", "b"
|
||||||
|
test "c", ""
|
||||||
|
test "abc", "cba"
|
||||||
|
test "cabc", "cxc"
|
||||||
|
test "zy", "abw"
|
||||||
|
test "abab", "xx"
|
||||||
|
test "abab", "ww"
|
||||||
|
test "w", "z"
|
||||||
|
test "xcy", "zw"
|
||||||
15
test/asm/charcmp.out
Normal file
15
test/asm/charcmp.out
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
"" <=> "" == 0
|
||||||
|
"a" <=> "a" == 0
|
||||||
|
"aa" <=> "aaa" == -1
|
||||||
|
"aaa" <=> "aa" == 1
|
||||||
|
"a" <=> "b" == -1
|
||||||
|
"b" <=> "a" == 1
|
||||||
|
"" <=> "b" == -1
|
||||||
|
"c" <=> "" == 1
|
||||||
|
"abc" <=> "cba" == 1
|
||||||
|
"cabc" <=> "cxc" == 0
|
||||||
|
"zy" <=> "abw" == 0
|
||||||
|
"abab" <=> "xx" == 0
|
||||||
|
"abab" <=> "ww" == -1
|
||||||
|
"w" <=> "z" == 1
|
||||||
|
"xcy" <=> "zw" == -1
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
opt Wno-unmapped-char
|
|
||||||
charmap "<NULL>", $00
|
|
||||||
charmap "A", $10
|
|
||||||
charmap "B", $20
|
|
||||||
charmap "C", $30
|
|
||||||
charmap "Bold", $88
|
|
||||||
|
|
||||||
SECTION "test", ROM0
|
|
||||||
|
|
||||||
DEF S EQUS "XBold<NULL>ABC"
|
|
||||||
|
|
||||||
assert CHARLEN("{S}") == 6
|
|
||||||
println CHARSUB("{S}", 2)
|
|
||||||
assert !STRCMP(CHARSUB("{S}", 2), "Bold")
|
|
||||||
assert CHARSUB("{S}", -5) == CHARSUB("{S}", CHARLEN("{S}") + 1 - 5)
|
|
||||||
assert CHARSUB("{S}", 2) == "Bold" && "Bold" == $88
|
|
||||||
assert CHARSUB("{S}", 1) == $58 ; ASCII "X"
|
|
||||||
db "{S}"
|
|
||||||
|
|
||||||
newcharmap ascii
|
|
||||||
|
|
||||||
assert CHARLEN("{S}") == 14
|
|
||||||
println CHARSUB("{S}", 2)
|
|
||||||
assert !STRCMP(CHARSUB("{S}", 2), "B")
|
|
||||||
assert CHARSUB("{S}", -5) == CHARSUB("{S}", CHARLEN("{S}") + 1 - 5)
|
|
||||||
assert CHARSUB("{S}", 2) == "B" && "B" == $42 ; ASCII "B"
|
|
||||||
assert CHARSUB("{S}", 1) == $58 ; ASCII "X"
|
|
||||||
db "{S}"
|
|
||||||
34
test/asm/charlen-strchar.asm
Normal file
34
test/asm/charlen-strchar.asm
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
opt Wno-unmapped-char
|
||||||
|
charmap "<NULL>", $00
|
||||||
|
charmap "A", $10
|
||||||
|
charmap "B", $20
|
||||||
|
charmap "C", $30
|
||||||
|
charmap "Bold", $88
|
||||||
|
|
||||||
|
SECTION "test", ROM0
|
||||||
|
|
||||||
|
DEF S EQUS "XBold<NULL>ABC"
|
||||||
|
|
||||||
|
assert CHARLEN("{S}") == 6
|
||||||
|
println STRCHAR("{S}", 1)
|
||||||
|
|
||||||
|
assert !STRCMP(STRCHAR("{S}", 1), "Bold")
|
||||||
|
assert STRCHAR("{S}", -5) == STRCHAR("{S}", CHARLEN("{S}") - 5)
|
||||||
|
assert STRCHAR("{S}", 1) == "Bold" && "Bold" == $88
|
||||||
|
assert STRCHAR("{S}", 0) == $58 ; ASCII "X"
|
||||||
|
db "{S}"
|
||||||
|
|
||||||
|
for n, CHARLEN("{S}")
|
||||||
|
assert STRCHAR("{S}", n) == CHARSUB("{S}", n + 1)
|
||||||
|
assert STRCHAR("{S}", -n - 1) == CHARSUB("{S}", -n - 1)
|
||||||
|
endr
|
||||||
|
|
||||||
|
newcharmap ascii
|
||||||
|
|
||||||
|
assert CHARLEN("{S}") == 14
|
||||||
|
println STRCHAR("{S}", 1)
|
||||||
|
assert !STRCMP(STRCHAR("{S}", 1), "B")
|
||||||
|
assert STRCHAR("{S}", -5) == STRCHAR("{S}", CHARLEN("{S}") - 5)
|
||||||
|
assert STRCHAR("{S}", 1) == "B" && "B" == $42 ; ASCII "B"
|
||||||
|
assert STRCHAR("{S}", 0) == $58 ; ASCII "X"
|
||||||
|
db "{S}"
|
||||||
20
test/asm/charsize.asm
Normal file
20
test/asm/charsize.asm
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
charmap "a", 1
|
||||||
|
charmap "b", 2, 3
|
||||||
|
charmap "cdef", 4
|
||||||
|
charmap "ghi", 5, 6, 7, 8, 9
|
||||||
|
charmap "jkl", 123, 456, 789
|
||||||
|
charmap "mno", 123456789
|
||||||
|
charmap "¡Pokémon!", 2, 3
|
||||||
|
|
||||||
|
assert charsize("a") == 1
|
||||||
|
assert charsize("b") == 2
|
||||||
|
assert charsize("cdef") == 1
|
||||||
|
assert charsize("ghi") == 5
|
||||||
|
assert charsize("jkl") == 3
|
||||||
|
assert charsize("mno") == 1
|
||||||
|
assert charsize("¡Pokémon!") == 2
|
||||||
|
|
||||||
|
assert charsize("") == 0
|
||||||
|
assert charsize("hello world") == 0
|
||||||
|
assert charsize("abcdef") == 0
|
||||||
|
assert charsize("é") == 0
|
||||||
9
test/asm/charsize.err
Normal file
9
test/asm/charsize.err
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
error: charsize.asm(17):
|
||||||
|
CHARSIZE: No character mapping for ""
|
||||||
|
error: charsize.asm(18):
|
||||||
|
CHARSIZE: No character mapping for "hello world"
|
||||||
|
error: charsize.asm(19):
|
||||||
|
CHARSIZE: No character mapping for "abcdef"
|
||||||
|
error: charsize.asm(20):
|
||||||
|
CHARSIZE: No character mapping for "é"
|
||||||
|
error: Assembly aborted (4 errors)!
|
||||||
23
test/asm/charval.asm
Normal file
23
test/asm/charval.asm
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
charmap "a", 1
|
||||||
|
charmap "b", 2, 3
|
||||||
|
charmap "cdef", 4
|
||||||
|
charmap "ghi", 5, 6, 7, 8, 9
|
||||||
|
charmap "jkl", 123, 456, 789
|
||||||
|
charmap "mno", 123456789
|
||||||
|
|
||||||
|
assert charval("a", 0) == 1
|
||||||
|
assert charval("a", -1) == 1
|
||||||
|
assert charval("b", 0) == 2
|
||||||
|
assert charval("b", 1) == 3
|
||||||
|
assert charval("b", -1) == 3
|
||||||
|
assert charval("b", -2) == 2
|
||||||
|
assert charval("cdef", 0) == 4
|
||||||
|
assert charval("ghi", 2) == charval("ghi", -3)
|
||||||
|
assert charval("jkl", -1) == 789
|
||||||
|
assert charval("mno", 0) == 123456789
|
||||||
|
|
||||||
|
assert charval("abc", 0) == 0
|
||||||
|
assert charval("cd", 1) == 0
|
||||||
|
assert charval("xyz", 2) == 0
|
||||||
|
assert charval("ghi", -10) == 5
|
||||||
|
assert charval("ghi", 10) == 0
|
||||||
11
test/asm/charval.err
Normal file
11
test/asm/charval.err
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
error: charval.asm(19):
|
||||||
|
CHARVAL: No character mapping for "abc"
|
||||||
|
error: charval.asm(20):
|
||||||
|
CHARVAL: No character mapping for "cd"
|
||||||
|
error: charval.asm(21):
|
||||||
|
CHARVAL: No character mapping for "xyz"
|
||||||
|
warning: charval.asm(22): [-Wbuiltin-args]
|
||||||
|
CHARVAL: Index starts at 0
|
||||||
|
warning: charval.asm(23): [-Wbuiltin-args]
|
||||||
|
CHARVAL: Index 10 is past the end of the character mapping
|
||||||
|
error: Assembly aborted (3 errors)!
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
error: command-line-symbols.asm(3):
|
error: command-line-symbols.asm(3):
|
||||||
'FOO' already defined at <command-line>
|
'FOO' already defined at <command-line>
|
||||||
|
(should it be {interpolated} to define its contents "hello"?)
|
||||||
error: Assembly aborted (1 error)!
|
error: Assembly aborted (1 error)!
|
||||||
|
|||||||
@@ -15,3 +15,6 @@ SECTION "Unaligned", ROM0
|
|||||||
|
|
||||||
println @ & 0
|
println @ & 0
|
||||||
println @ & 1 ; Nope
|
println @ & 1 ; Nope
|
||||||
|
|
||||||
|
Floating:
|
||||||
|
println Floating & Aligned ; Neither defined
|
||||||
|
|||||||
@@ -4,4 +4,6 @@ error: const-and.asm(11):
|
|||||||
Expected constant expression: 'Aligned' is not constant at assembly time
|
Expected constant expression: 'Aligned' is not constant at assembly time
|
||||||
error: const-and.asm(17):
|
error: const-and.asm(17):
|
||||||
Expected constant expression: PC is not constant at assembly time
|
Expected constant expression: PC is not constant at assembly time
|
||||||
error: Assembly aborted (3 errors)!
|
error: const-and.asm(20):
|
||||||
|
Expected constant expression: 'Floating' is not constant at assembly time
|
||||||
|
error: Assembly aborted (4 errors)!
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ $0
|
|||||||
$A
|
$A
|
||||||
$0
|
$0
|
||||||
$0
|
$0
|
||||||
|
$0
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
error: def-scoped.asm(10):
|
error: def-scoped.asm(10):
|
||||||
syntax error, unexpected local identifier, expecting identifier
|
syntax error, unexpected local label, expecting symbol
|
||||||
error: def-scoped.asm(13):
|
error: def-scoped.asm(13):
|
||||||
syntax error, unexpected local identifier, expecting identifier
|
syntax error, unexpected local label, expecting symbol
|
||||||
error: def-scoped.asm(16):
|
error: def-scoped.asm(16):
|
||||||
syntax error, unexpected local identifier, expecting identifier
|
syntax error, unexpected local label, expecting symbol
|
||||||
error: Assembly aborted (3 errors)!
|
error: Assembly aborted (3 errors)!
|
||||||
|
|||||||
@@ -28,3 +28,15 @@ redef string equs "there"
|
|||||||
|
|
||||||
redef constant equ 6*9
|
redef constant equ 6*9
|
||||||
println constant
|
println constant
|
||||||
|
|
||||||
|
macro mac
|
||||||
|
endm
|
||||||
|
|
||||||
|
redef mac equ 42
|
||||||
|
|
||||||
|
def name equs "constant2"
|
||||||
|
def name equ 1337
|
||||||
|
|
||||||
|
redef name equs "mac2"
|
||||||
|
macro name
|
||||||
|
endm
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
error: def.asm(23):
|
error: def.asm(23):
|
||||||
'constant' already defined at def.asm(10)
|
'constant' already defined at def.asm(10)
|
||||||
error: Assembly aborted (1 error)!
|
error: def.asm(35):
|
||||||
|
'mac' already defined as non-EQU at def.asm(32)
|
||||||
|
error: def.asm(38):
|
||||||
|
'name' already defined at def.asm(37)
|
||||||
|
(should it be {interpolated} to define its contents "constant2"?)
|
||||||
|
error: def.asm(42):
|
||||||
|
'name' already defined at def.asm(40)
|
||||||
|
(should it be {interpolated} to define its contents "mac2"?)
|
||||||
|
error: Assembly aborted (4 errors)!
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
error: ds-bad.asm(3):
|
error: ds-bad.asm(3):
|
||||||
Expected constant expression: 'unknown' is not constant at assembly time
|
Expected constant expression: 'unknown' is not constant at assembly time
|
||||||
warning: ds-bad.asm(4): [-Wtruncation]
|
warning: ds-bad.asm(4): [-Wtruncation]
|
||||||
Expression must be 8-bit
|
Expression must be 8-bit; use LOW() to force 8-bit
|
||||||
error: Assembly aborted (1 error)!
|
error: Assembly aborted (1 error)!
|
||||||
|
|||||||
@@ -3,6 +3,5 @@ warning: equs-newline.asm(3): [-Wuser]
|
|||||||
while expanding symbol "ACT"
|
while expanding symbol "ACT"
|
||||||
warning: equs-newline.asm(3): [-Wuser]
|
warning: equs-newline.asm(3): [-Wuser]
|
||||||
Second
|
Second
|
||||||
while expanding symbol "ACT"
|
|
||||||
warning: equs-newline.asm(4): [-Wuser]
|
warning: equs-newline.asm(4): [-Wuser]
|
||||||
Third
|
Third
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
println 42, 1 2 3 4
|
println 42, 1 2 3 4
|
||||||
|
|
||||||
for n, 5
|
for n, 3
|
||||||
println "start {d:n}"
|
println "start {d:n}"
|
||||||
println syntax error
|
println syntax error
|
||||||
println "finish {d:n}"
|
println "finish {d:n}"
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
error: error-recovery.asm(3):
|
error: error-recovery.asm(3):
|
||||||
syntax error, unexpected number
|
syntax error, unexpected number
|
||||||
error: error-recovery.asm(5) -> error-recovery.asm::REPT~1(7):
|
error: error-recovery.asm(5) -> error-recovery.asm::REPT~1(7):
|
||||||
syntax error, unexpected identifier
|
syntax error, unexpected symbol
|
||||||
error: Assembly aborted (2 errors)!
|
error: error-recovery.asm(5) -> error-recovery.asm::REPT~2(7):
|
||||||
|
syntax error, unexpected symbol
|
||||||
|
error: error-recovery.asm(5) -> error-recovery.asm::REPT~3(7):
|
||||||
|
syntax error, unexpected symbol
|
||||||
|
error: Assembly aborted (4 errors)!
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
begin
|
begin
|
||||||
$2Astart 0
|
$2Astart 0
|
||||||
finish 0
|
finish 0
|
||||||
end 0
|
start 1
|
||||||
|
finish 1
|
||||||
|
start 2
|
||||||
|
finish 2
|
||||||
|
end 3
|
||||||
|
|||||||
13
test/asm/for-loop-variable.asm
Normal file
13
test/asm/for-loop-variable.asm
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
def x = 4
|
||||||
|
for x, x
|
||||||
|
def x *= 2
|
||||||
|
println x
|
||||||
|
endr
|
||||||
|
println x
|
||||||
|
|
||||||
|
def y equ 5
|
||||||
|
for y, y
|
||||||
|
def y *= 3
|
||||||
|
println y
|
||||||
|
endr
|
||||||
|
println y
|
||||||
3
test/asm/for-loop-variable.err
Normal file
3
test/asm/for-loop-variable.err
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
error: for-loop-variable.asm(12):
|
||||||
|
'y' already defined as constant at for-loop-variable.asm(8)
|
||||||
|
error: Assembly aborted (1 error)!
|
||||||
6
test/asm/for-loop-variable.out
Normal file
6
test/asm/for-loop-variable.out
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
$0
|
||||||
|
$2
|
||||||
|
$4
|
||||||
|
$6
|
||||||
|
$4
|
||||||
|
$5
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user