mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-21 02:32:06 +00:00
Compare commits
158 Commits
v0.4.2
...
v0.5.0-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b61a187b19 | ||
|
|
6382f13647 | ||
|
|
714d39c86f | ||
|
|
f11241c2ae | ||
|
|
cb47ac8b97 | ||
|
|
028e7af7d1 | ||
|
|
e141da1d94 | ||
|
|
35367282e4 | ||
|
|
4c0fa6732e | ||
|
|
e9d9a44687 | ||
|
|
611bd46e0b | ||
|
|
e67254093e | ||
|
|
3ce3d2f662 | ||
|
|
3da9d81071 | ||
|
|
88646093ca | ||
|
|
0a0427afbb | ||
|
|
88048cdcd7 | ||
|
|
c878ff8775 | ||
|
|
a4049a3f1b | ||
|
|
8a75cc5051 | ||
|
|
b674c5fcaa | ||
|
|
a6d3df7ac9 | ||
|
|
365484ef18 | ||
|
|
5e2bd35239 | ||
|
|
6655e04ef0 | ||
|
|
40c6b840f8 | ||
|
|
5d6e0677d9 | ||
|
|
56071599e7 | ||
|
|
c637447d5d | ||
|
|
ac2cefdd87 | ||
|
|
0774f5eb9d | ||
|
|
76d8862900 | ||
|
|
1dafc1c762 | ||
|
|
63d15ac8c9 | ||
|
|
1f579deaff | ||
|
|
fad48326f8 | ||
|
|
e7d6ddf593 | ||
|
|
953f79c0d9 | ||
|
|
3c5e1caa7c | ||
|
|
d4028fff10 | ||
|
|
dd892d61d8 | ||
|
|
a09f2d4115 | ||
|
|
dafef5a953 | ||
|
|
929e2a4490 | ||
|
|
cc1129093d | ||
|
|
72911ada2d | ||
|
|
037bc7abb3 | ||
|
|
5fd636ac4b | ||
|
|
a6d844a9a5 | ||
|
|
bee62076c6 | ||
|
|
cd072d5e6a | ||
|
|
6ef3ee1391 | ||
|
|
c29b616f93 | ||
|
|
39c179aa32 | ||
|
|
5c1ae4ce22 | ||
|
|
efbfeca292 | ||
|
|
7dd34f1572 | ||
|
|
748e7dd4c7 | ||
|
|
d049ffc0f0 | ||
|
|
8c0275480c | ||
|
|
76d6ef8695 | ||
|
|
c67a696a87 | ||
|
|
ee20d9010e | ||
|
|
2bc12447e2 | ||
|
|
cb61da8842 | ||
|
|
122ba6eba5 | ||
|
|
dddff0f450 | ||
|
|
a919f922a1 | ||
|
|
8f20620c16 | ||
|
|
96bce05be2 | ||
|
|
fc2bf3d11d | ||
|
|
8415ce3ed0 | ||
|
|
ebb5aab6db | ||
|
|
464a3a4892 | ||
|
|
88e1cc7302 | ||
|
|
b3c0db218d | ||
|
|
76446e6d00 | ||
|
|
6623b1dc45 | ||
|
|
70bbb098d3 | ||
|
|
1926065377 | ||
|
|
e3d355d976 | ||
|
|
a679e02246 | ||
|
|
592e9b3725 | ||
|
|
effc58241d | ||
|
|
3697065afc | ||
|
|
fa0fa4d5ac | ||
|
|
5acc48fa54 | ||
|
|
1487eebe79 | ||
|
|
993c034039 | ||
|
|
09f16bda4a | ||
|
|
41d544a4eb | ||
|
|
f28b4abafc | ||
|
|
ca1c934629 | ||
|
|
d5a00cf634 | ||
|
|
fb39c3a70e | ||
|
|
15ec6efc28 | ||
|
|
93d83e17dc | ||
|
|
df16e64fc6 | ||
|
|
a4ebb87858 | ||
|
|
5ef8e0a1f6 | ||
|
|
eb4952c188 | ||
|
|
57b734a7df | ||
|
|
5be1c0da62 | ||
|
|
b598911e96 | ||
|
|
cab9cb06a3 | ||
|
|
62bea23c49 | ||
|
|
7ce5cf1595 | ||
|
|
7e3fc1db03 | ||
|
|
77279984a5 | ||
|
|
669a392fcd | ||
|
|
51ce0b038a | ||
|
|
bd244e6865 | ||
|
|
a70ecba06f | ||
|
|
9d2d5cfcfe | ||
|
|
18f3c8ff9a | ||
|
|
895ec5564d | ||
|
|
7bb6f71f0b | ||
|
|
10e3f1a02b | ||
|
|
2a9d52871b | ||
|
|
c4fb5591ee | ||
|
|
c0ce1da4c3 | ||
|
|
aa27e714d4 | ||
|
|
6874f694e5 | ||
|
|
3690546795 | ||
|
|
7bc42d468b | ||
|
|
097999cad3 | ||
|
|
f82edaa7ec | ||
|
|
d8e8b796e7 | ||
|
|
900fd8cabc | ||
|
|
c20ac35074 | ||
|
|
255b8bf9ba | ||
|
|
ad6f17cd93 | ||
|
|
063a22ddf9 | ||
|
|
1d9cc01ae1 | ||
|
|
f31deb5010 | ||
|
|
0956d300c4 | ||
|
|
4ef490f3cb | ||
|
|
8f2a894b88 | ||
|
|
0e40543757 | ||
|
|
ce58f6d6be | ||
|
|
5aabb915ec | ||
|
|
0d9de01f9d | ||
|
|
f5b0eae9cd | ||
|
|
e6552064bf | ||
|
|
861cb552c4 | ||
|
|
417cceb0de | ||
|
|
165bd8cb71 | ||
|
|
e54e02dc77 | ||
|
|
2bf3b08d76 | ||
|
|
21b58b08b8 | ||
|
|
af530859f0 | ||
|
|
58739b0bf2 | ||
|
|
3e4c2fe712 | ||
|
|
bdfce25db0 | ||
|
|
2b6d9cd1e0 | ||
|
|
bf789dd7b3 | ||
|
|
9b6f01047c | ||
|
|
3fe2fa43bb |
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
github: avivace
|
||||||
|
patreon: gbdev01
|
||||||
|
open_collective: gbdev
|
||||||
8
.github/actions/doc_postproc.awk
vendored
8
.github/actions/doc_postproc.awk
vendored
@@ -7,6 +7,14 @@
|
|||||||
sub(/b><\/td/, "th");
|
sub(/b><\/td/, "th");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# The whole page is being generated, so it's not meant to contain any Liquid
|
||||||
|
BEGIN {
|
||||||
|
print "{% raw %}"
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
print "{% endraw %}"
|
||||||
|
}
|
||||||
|
|
||||||
BEGIN {
|
BEGIN {
|
||||||
in_synopsis = 0
|
in_synopsis = 0
|
||||||
}
|
}
|
||||||
|
|||||||
29
.github/actions/get-pages.sh
vendored
29
.github/actions/get-pages.sh
vendored
@@ -5,20 +5,21 @@ usage() {
|
|||||||
Usage: $0 [-h] [-r] <rgbds-www> <version>
|
Usage: $0 [-h] [-r] <rgbds-www> <version>
|
||||||
Copy renders from RGBDS repository to rgbds-www documentation
|
Copy renders from RGBDS repository to rgbds-www documentation
|
||||||
Execute from the root folder of the RGBDS repo, checked out at the desired tag
|
Execute from the root folder of the RGBDS repo, checked out at the desired tag
|
||||||
<rgbds-www> : Path to the '_documentation' folder in the rgbds-www repository
|
<rgbds-www> : Path to the rgbds-www repository
|
||||||
<version> : Version to be copied, such as 'v0.4.1' or 'master'
|
<version> : Version to be copied, such as 'v0.4.1' or 'master'
|
||||||
|
|
||||||
-h Display this help message
|
-h Display this help message
|
||||||
-r Update "latest stable" redirection pages (use for releases, not master)
|
-r Update "latest stable" redirection pages and add a new entry to the index
|
||||||
|
(use for releases, not master)
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
update_redirects=0
|
is_release=0
|
||||||
bad_usage=0
|
bad_usage=0
|
||||||
while getopts ":hr" opt; do
|
while getopts ":hr" opt; do
|
||||||
case $opt in
|
case $opt in
|
||||||
r)
|
r)
|
||||||
update_redirects=1
|
is_release=1
|
||||||
;;
|
;;
|
||||||
h)
|
h)
|
||||||
usage
|
usage
|
||||||
@@ -50,7 +51,7 @@ PAGES=(
|
|||||||
[gbz80.7.html]=src/gbz80.7
|
[gbz80.7.html]=src/gbz80.7
|
||||||
)
|
)
|
||||||
WWWPATH="/docs"
|
WWWPATH="/docs"
|
||||||
mkdir -p "$1/$2"
|
mkdir -p "$1/_documentation/$2"
|
||||||
|
|
||||||
# `mandoc` uses a different format for referring to man pages present in the **current** directory
|
# `mandoc` uses a different format for referring to man pages present in the **current** directory
|
||||||
# We want that format for RGBDS man pages, and the other one for the t=rest;
|
# We want that format for RGBDS man pages, and the other one for the t=rest;
|
||||||
@@ -64,7 +65,7 @@ stem="${page%.html}"
|
|||||||
manpage="${stem%.?}(${stem#*.})"
|
manpage="${stem%.?}(${stem#*.})"
|
||||||
descr="$(awk -v 'FS=.Nd ' '/.Nd/ { print $2; }' "${PAGES[$page]}")"
|
descr="$(awk -v 'FS=.Nd ' '/.Nd/ { print $2; }' "${PAGES[$page]}")"
|
||||||
|
|
||||||
cat - >"$1/$2/$page" <<EOF
|
cat >"$1/_documentation/$2/$page" <<EOF
|
||||||
---
|
---
|
||||||
layout: doc
|
layout: doc
|
||||||
title: $manpage [$2]
|
title: $manpage [$2]
|
||||||
@@ -75,9 +76,9 @@ EOF
|
|||||||
if [ $stem = rgbasm.5 ]; then
|
if [ $stem = rgbasm.5 ]; then
|
||||||
options+=,toc
|
options+=,toc
|
||||||
fi
|
fi
|
||||||
mandoc -Thtml -I os=Linux -O$options "${PAGES[$page]##*/}" | .github/actions/doc_postproc.awk >> "$1/$2/$page"
|
mandoc -Thtml -I os=Linux -O$options "${PAGES[$page]##*/}" | .github/actions/doc_postproc.awk >> "$1/_documentation/$2/$page"
|
||||||
if [ $update_redirects -ne 0 ]; then
|
if [ $is_release -ne 0 ]; then
|
||||||
cat - >"$1/$page" <<EOF
|
cat - >"$1/_documentation/$page" <<EOF
|
||||||
---
|
---
|
||||||
redirect_to: $WWWPATH/$2/${page%.html}
|
redirect_to: $WWWPATH/$2/${page%.html}
|
||||||
permalink: $WWWPATH/${page%.html}/
|
permalink: $WWWPATH/${page%.html}/
|
||||||
@@ -87,7 +88,7 @@ description: RGBDS latest stable — $descr
|
|||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
cat - >"$1/$2/index.html" <<EOF
|
cat - >"$1/_documentation/$2/index.html" <<EOF
|
||||||
---
|
---
|
||||||
layout: doc_index
|
layout: doc_index
|
||||||
permalink: /docs/$2/
|
permalink: /docs/$2/
|
||||||
@@ -96,5 +97,13 @@ description: RGBDS $2 - Online manual
|
|||||||
---
|
---
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
# If making a release, add a new entry right after `master`
|
||||||
|
if [ $is_release -ne 0 ]; then
|
||||||
|
awk '{ print }
|
||||||
|
/"name": "master"/ { print "\t\t{\"name\": \"'$2'\", \"text\": \"'$2'\" }," }
|
||||||
|
' "$1/_data/doc.json" >"$1/_data/doc.json.tmp"
|
||||||
|
mv "$1/_data/doc.json"{.tmp,}
|
||||||
|
fi
|
||||||
|
|
||||||
# Clean up
|
# Clean up
|
||||||
rm "${PAGES[@]##*/}"
|
rm "${PAGES[@]##*/}"
|
||||||
|
|||||||
8
.github/actions/install_deps.sh
vendored
8
.github/actions/install_deps.sh
vendored
@@ -4,11 +4,15 @@ case `echo $1 | cut -d '-' -f 1` in
|
|||||||
sudo apt-get install -yq bison libpng-dev pkg-config
|
sudo apt-get install -yq bison libpng-dev pkg-config
|
||||||
;;
|
;;
|
||||||
macos)
|
macos)
|
||||||
brew install libpng pkg-config md5sha1sum
|
brew install bison libpng pkg-config md5sha1sum
|
||||||
|
# For the version check below exclusively, re-do this before building
|
||||||
|
export PATH="/usr/local/opt/bison/bin:$PATH"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "WARNING: Cannot install deps for OS '$1'"
|
echo "WARNING: Cannot install deps for OS '$1'"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
yacc --version
|
bison --version
|
||||||
|
make --version
|
||||||
|
cmake --version
|
||||||
|
|||||||
96
.github/workflows/create-release-artifacts.yaml
vendored
Normal file
96
.github/workflows/create-release-artifacts.yaml
vendored
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
name: "Create release artifacts"
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- v[0-9]*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
windows:
|
||||||
|
runs-on: windows-2019
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Get version from tag
|
||||||
|
shell: bash
|
||||||
|
run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z"
|
||||||
|
VERSION="${{ github.ref }}"
|
||||||
|
echo "version=${VERSION##*/v}" >> $GITHUB_ENV
|
||||||
|
- name: Get zlib, libpng and bison
|
||||||
|
run: | # TODO: use an array
|
||||||
|
$wc = New-Object System.Net.WebClient
|
||||||
|
$wc.DownloadFile('https://www.zlib.net/zlib1211.zip', 'zlib.zip')
|
||||||
|
$hash = (Get-FileHash "zlib.zip" -Algorithm SHA256).Hash
|
||||||
|
if ($hash -ne 'd7510a8ee1918b7d0cad197a089c0a2cd4d6df05fee22389f67f115e738b178d') {
|
||||||
|
Write-Host "zlib SHA256 mismatch! ($hash)"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
$wc.DownloadFile('https://download.sourceforge.net/libpng/lpng1637.zip', 'libpng.zip')
|
||||||
|
$hash = (Get-FileHash "libpng.zip" -Algorithm SHA256).Hash
|
||||||
|
if ($hash -ne '3b4b1cbd0bae6822f749d39b1ccadd6297f05e2b85a83dd2ce6ecd7d09eabdf2') {
|
||||||
|
Write-Host "libpng SHA256 mismatch! ($hash)"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
$wc.DownloadFile('https://github.com/lexxmark/winflexbison/releases/download/v2.5.23/win_flex_bison-2.5.23.zip', 'winflexbison.zip')
|
||||||
|
$hash = (Get-FileHash "winflexbison.zip" -Algorithm SHA256).Hash
|
||||||
|
if ($hash -ne '6AA5C8EA662DA1550020A5804C28BE63FFAA53486DA9F6842E24C379EC422DFC') {
|
||||||
|
Write-Host "bison SHA256 mismatch! ($hash)"
|
||||||
|
}
|
||||||
|
Expand-Archive -DestinationPath . "zlib.zip"
|
||||||
|
Expand-Archive -DestinationPath . "libpng.zip"
|
||||||
|
Expand-Archive -DestinationPath install_dir "winflexbison.zip"
|
||||||
|
Move-Item zlib-1.2.11 zlib
|
||||||
|
Move-Item lpng1637 libpng
|
||||||
|
- name: Build 32-bit zlib
|
||||||
|
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
|
||||||
|
cmake -S zlib -B zbuild32 -A Win32 -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
|
||||||
|
cmake --build zbuild32 --config Release
|
||||||
|
cmake --install zbuild32
|
||||||
|
- name: Build 32-bit libpng
|
||||||
|
run: |
|
||||||
|
cmake -S libpng -B pngbuild32 -A Win32 -DCMAKE_INSTALL_PREFIX=install_dir -DPNG_SHARED=ON -DPNG_STATIC=ON -DPNG_TESTS=OFF
|
||||||
|
cmake --build pngbuild32 --config Release
|
||||||
|
cmake --install pngbuild32
|
||||||
|
- name: Build 32-bit Windows binaries
|
||||||
|
run: |
|
||||||
|
cmake -S . -B build32 -A Win32 -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release
|
||||||
|
cmake --build build32 --config Release
|
||||||
|
cmake --install build32
|
||||||
|
- name: Package 32-bit binaries
|
||||||
|
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 }}-win32.zip"
|
||||||
|
- name: Build 64-bit zlib
|
||||||
|
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
|
||||||
|
cmake -S zlib -B zbuild64 -A x64 -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
|
||||||
|
cmake --build zbuild64 --config Release
|
||||||
|
cmake --install zbuild64
|
||||||
|
- name: Build 64-bit libpng
|
||||||
|
run: |
|
||||||
|
cmake -S libpng -B pngbuild64 -A x64 -DCMAKE_INSTALL_PREFIX=install_dir -DPNG_SHARED=ON -DPNG_STATIC=ON -DPNG_TESTS=OFF
|
||||||
|
cmake --build pngbuild64 --config Release
|
||||||
|
cmake --install pngbuild64
|
||||||
|
- name: Build 64-bit Windows binaries
|
||||||
|
run: |
|
||||||
|
cmake -S . -B build64 -A x64 -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release
|
||||||
|
cmake --build build64 --config Release
|
||||||
|
cmake --install build64
|
||||||
|
- name: Package 64-bit binaries
|
||||||
|
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 }}-win64.zip"
|
||||||
|
- name: Package sources
|
||||||
|
run: |
|
||||||
|
make dist
|
||||||
|
- name: Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
body: |
|
||||||
|
Please ensure that the three assets below work properly.
|
||||||
|
Once that's done, replace this text with the changelog, un-draft the release, and update the `release` branch.
|
||||||
|
By the way, if you forgot to update `include/version.h`, RGBASM's version test is gonna fail in the tag's regression testing! (Use `git push --delete origin <tag>` to delete it)
|
||||||
|
draft: true # Don't publish the release quite yet...
|
||||||
|
prerelease: ${{ contains(github.ref, '-rc') }}
|
||||||
|
files: |
|
||||||
|
rgbds-${{ env.version }}-win32.zip
|
||||||
|
rgbds-${{ env.version }}-win64.zip
|
||||||
|
rgbds-${{ env.version }}.tar.gz
|
||||||
|
fail_on_unmatched_files: true
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
24
.github/workflows/create-release-docs.yml
vendored
24
.github/workflows/create-release-docs.yml
vendored
@@ -2,24 +2,22 @@ name: "Create release docs"
|
|||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- created
|
- released # This avoids triggering on pre-releases
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-18.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout rgbds@master
|
- name: Checkout rgbds@release
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
repository: gbdev/rgbds
|
|
||||||
ref: master
|
|
||||||
path: rgbds
|
path: rgbds
|
||||||
- name: Checkout rgbds-www@master
|
- name: Checkout rgbds-www@master
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
repository: gbdev/rgbds-www
|
repository: ${{ github.repository_owner }}/rgbds-www
|
||||||
ref: master
|
|
||||||
path: rgbds-www
|
path: rgbds-www
|
||||||
|
# `-O toc` was added in 1.14.5, but the repos only have 1.14.4
|
||||||
- name: Build and install mandoc
|
- name: Build and install mandoc
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get -qq update
|
sudo apt-get -qq update
|
||||||
@@ -32,23 +30,23 @@ jobs:
|
|||||||
sudo make install
|
sudo make install
|
||||||
- name: Update pages
|
- name: Update pages
|
||||||
working-directory: rgbds
|
working-directory: rgbds
|
||||||
run: |
|
run: | # The ref appears to be in the format "refs/tags/<version>", so strip that
|
||||||
./.github/actions/get-pages.sh ../rgbds-www/_documentation ${GITHUB_REF}
|
./.github/actions/get-pages.sh -r ../rgbds-www ${GITHUB_REF##*/}
|
||||||
- name: Push new pages
|
- name: Push new pages
|
||||||
working-directory: rgbds-www
|
working-directory: rgbds-www
|
||||||
run: |
|
run: |
|
||||||
mkdir -p -m 700 ~/.ssh
|
mkdir -p -m 700 ~/.ssh
|
||||||
echo "${{ secrets.SSH_KEY_SECRET }}" > ~/.ssh/id_ed25519
|
cat > ~/.ssh/id_ed25519 <<<"${{ secrets.SSH_KEY_SECRET }}"
|
||||||
chmod 0600 ~/.ssh/id_ed25519
|
chmod 0600 ~/.ssh/id_ed25519
|
||||||
eval $(ssh-agent -s)
|
eval $(ssh-agent -s)
|
||||||
ssh-add ~/.ssh/id_ed25519
|
ssh-add ~/.ssh/id_ed25519
|
||||||
git config --global user.name "GitHub Action"
|
git config --global user.name "GitHub Action"
|
||||||
git config --global user.email "community@gbdev.io"
|
git config --global user.email "community@gbdev.io"
|
||||||
git add .
|
git add -A
|
||||||
git commit -m "Create RGBDS ${GITHUB_REF} documentation"
|
git commit -m "Create RGBDS ${GITHUB_REF##*/} documentation"
|
||||||
if git remote | grep -q origin; then
|
if git remote | grep -q origin; then
|
||||||
git remote set-url origin git@github.com:gbdev/rgbds-www.git
|
git remote set-url origin git@github.com:${{ github.repository_owner }}/rgbds-www.git
|
||||||
else
|
else
|
||||||
git remote add origin git@github.com:gbdev/rgbds-www.git
|
git remote add origin git@github.com:${{ github.repository_owner }}/rgbds-www.git
|
||||||
fi
|
fi
|
||||||
git push origin master
|
git push origin master
|
||||||
|
|||||||
9
.github/workflows/testing.yml
vendored
9
.github/workflows/testing.yml
vendored
@@ -27,14 +27,19 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
./.github/actions/install_deps.sh ${{ matrix.os }}
|
./.github/actions/install_deps.sh ${{ matrix.os }}
|
||||||
|
# The `export` lines are to allow working on macOS...
|
||||||
|
# Apple's base version is severely outdated, not even supporting -Wall,
|
||||||
|
# but it overrides Homebrew's version nonetheless...
|
||||||
- name: Build & install using Make
|
- name: Build & install using Make
|
||||||
run: |
|
run: |
|
||||||
|
export PATH="/usr/local/opt/bison/bin:$PATH"
|
||||||
make ${{ matrix.target }} -j Q= CC=${{ matrix.cc }}
|
make ${{ matrix.target }} -j Q= CC=${{ matrix.cc }}
|
||||||
sudo make install -j Q=
|
sudo make install -j Q=
|
||||||
if: matrix.buildsys == 'make'
|
if: matrix.buildsys == 'make'
|
||||||
- name: Build & install using CMake
|
- name: Build & install using CMake
|
||||||
run: |
|
run: |
|
||||||
cmake -S . -B build -DCMAKE_VERBOSE_MAKEFILE=ON ${{ matrix.cmakevars }}
|
export PATH="/usr/local/opt/bison/bin:$PATH"
|
||||||
|
cmake -S . -B build -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=${{ matrix.cc }} ${{ matrix.cmakevars }}
|
||||||
cmake --build build
|
cmake --build build
|
||||||
cp build/src/rgb{asm,link,fix,gfx} .
|
cp build/src/rgb{asm,link,fix,gfx} .
|
||||||
sudo cmake --install build
|
sudo cmake --install build
|
||||||
@@ -128,7 +133,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
bits: [32, 64]
|
bits: [32, 64]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-18.04]
|
||||||
include:
|
include:
|
||||||
- bits: 32
|
- bits: 32
|
||||||
arch: i686
|
arch: i686
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,6 +5,7 @@ rgbgfx
|
|||||||
rgbshim.sh
|
rgbshim.sh
|
||||||
*.o
|
*.o
|
||||||
*.exe
|
*.exe
|
||||||
|
*.dll
|
||||||
.checkpatch-camelcase.*
|
.checkpatch-camelcase.*
|
||||||
CMakeCache.txt
|
CMakeCache.txt
|
||||||
CMakeFiles
|
CMakeFiles
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
#
|
#
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.0)
|
# 3.9 required for LTO checks
|
||||||
cmake_policy(VERSION 3.0)
|
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
|
||||||
|
|
||||||
project(rgbds
|
project(rgbds
|
||||||
LANGUAGES C)
|
LANGUAGES C)
|
||||||
@@ -23,15 +23,15 @@ if(srcdir STREQUAL bindir)
|
|||||||
message(FATAL_ERROR "Terminating configuration")
|
message(FATAL_ERROR "Terminating configuration")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
include_directories("${PROJECT_SOURCE_DIR}/include")
|
option(SANITIZERS "Build with sanitizers enabled" OFF) # Ignored on MSVC
|
||||||
|
option(MORE_WARNINGS "Turn on more warnings" OFF) # Ignored on MSVC
|
||||||
option(SANITIZERS "Build with sanitizers enabled" OFF)
|
|
||||||
option(MORE_WARNINGS "Turn on more warnings" OFF)
|
|
||||||
option(TRACE_PARSER "Trace parser execution" OFF)
|
option(TRACE_PARSER "Trace parser execution" OFF)
|
||||||
option(TRACE_LEXER "Trace lexer execution" OFF)
|
option(TRACE_LEXER "Trace lexer execution" OFF)
|
||||||
|
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
add_compile_options(/W1 /MP)
|
# MSVC's standard library triggers warning C5105,
|
||||||
|
# "macro expansion producing 'defined' has undefined behavior"
|
||||||
|
add_compile_options(/std:c11 /W1 /MP /wd5105)
|
||||||
add_definitions(/D_CRT_SECURE_NO_WARNINGS)
|
add_definitions(/D_CRT_SECURE_NO_WARNINGS)
|
||||||
else()
|
else()
|
||||||
add_compile_options(-Wall -pedantic)
|
add_compile_options(-Wall -pedantic)
|
||||||
@@ -65,6 +65,8 @@ execute_process(COMMAND git describe --tags --dirty --always
|
|||||||
ERROR_QUIET)
|
ERROR_QUIET)
|
||||||
string(STRIP "${GIT_REV}" GIT_REV)
|
string(STRIP "${GIT_REV}" GIT_REV)
|
||||||
|
|
||||||
|
include_directories("${PROJECT_SOURCE_DIR}/include")
|
||||||
|
|
||||||
add_definitions(-DBUILD_VERSION_STRING="${GIT_REV}")
|
add_definitions(-DBUILD_VERSION_STRING="${GIT_REV}")
|
||||||
|
|
||||||
set(CMAKE_C_STANDARD 11)
|
set(CMAKE_C_STANDARD 11)
|
||||||
@@ -72,6 +74,20 @@ set(CMAKE_C_STANDARD_REQUIRED True)
|
|||||||
|
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
|
|
||||||
|
# By default, build in Release mode; Debug mode must be explicitly requested
|
||||||
|
# (You may want to augment it with the options above)
|
||||||
|
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||||
|
message(CHECK_START "Checking if LTO is supported")
|
||||||
|
include(CheckIPOSupported)
|
||||||
|
check_ipo_supported(RESULT enable_lto)
|
||||||
|
if(enable_lto)
|
||||||
|
message(CHECK_PASS "yes")
|
||||||
|
set_property(TARGET rgbasm rgblink rgbfix rgbgfx PROPERTY INTERPROCEDURAL_OPTIMIZATION ON)
|
||||||
|
else()
|
||||||
|
message(CHECK_FAIL "no")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
if(TRACE_PARSER)
|
if(TRACE_PARSER)
|
||||||
target_compile_definitions(rgbasm PRIVATE -DYYDEBUG)
|
target_compile_definitions(rgbasm PRIVATE -DYYDEBUG)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ Other contributors
|
|||||||
|
|
||||||
- Quint Guvernator <quint@guvernator.net>
|
- Quint Guvernator <quint@guvernator.net>
|
||||||
|
|
||||||
|
- Rangi <http://github.com/Rangi42>
|
||||||
|
|
||||||
- Sanqui <gsanky@gmail.com>
|
- Sanqui <gsanky@gmail.com>
|
||||||
|
|
||||||
- YamaArashi <shadow962@live.com>
|
- YamaArashi <shadow962@live.com>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
RUN apk add --update \
|
RUN apk add --update \
|
||||||
build-base \
|
build-base \
|
||||||
byacc \
|
bison \
|
||||||
libpng-dev
|
libpng-dev
|
||||||
COPY . /rgbds
|
COPY . /rgbds
|
||||||
WORKDIR /rgbds
|
WORKDIR /rgbds
|
||||||
|
|||||||
45
Makefile
45
Makefile
@@ -33,7 +33,7 @@ VERSION_STRING := `git describe --tags --dirty --always 2>/dev/null`
|
|||||||
WARNFLAGS := -Wall
|
WARNFLAGS := -Wall
|
||||||
|
|
||||||
# Overridable CFLAGS
|
# Overridable CFLAGS
|
||||||
CFLAGS ?= -O3 -DNDEBUG
|
CFLAGS ?= -O3 -flto -DNDEBUG
|
||||||
# Non-overridable CFLAGS
|
# Non-overridable CFLAGS
|
||||||
REALCFLAGS := ${CFLAGS} ${WARNFLAGS} -std=gnu11 -D_POSIX_C_SOURCE=200809L \
|
REALCFLAGS := ${CFLAGS} ${WARNFLAGS} -std=gnu11 -D_POSIX_C_SOURCE=200809L \
|
||||||
-Iinclude
|
-Iinclude
|
||||||
@@ -43,9 +43,9 @@ LDFLAGS ?=
|
|||||||
REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} \
|
REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} \
|
||||||
-DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
|
-DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
|
||||||
|
|
||||||
YFLAGS ?=
|
YFLAGS ?= -Wall
|
||||||
|
|
||||||
YACC := yacc
|
BISON := bison
|
||||||
RM := rm -rf
|
RM := rm -rf
|
||||||
|
|
||||||
# Rules to build the RGBDS binaries
|
# Rules to build the RGBDS binaries
|
||||||
@@ -54,12 +54,14 @@ all: rgbasm rgblink rgbfix rgbgfx
|
|||||||
|
|
||||||
rgbasm_obj := \
|
rgbasm_obj := \
|
||||||
src/asm/charmap.o \
|
src/asm/charmap.o \
|
||||||
|
src/asm/fixpoint.o \
|
||||||
|
src/asm/format.o \
|
||||||
src/asm/fstack.o \
|
src/asm/fstack.o \
|
||||||
src/asm/lexer.o \
|
src/asm/lexer.o \
|
||||||
src/asm/macro.o \
|
src/asm/macro.o \
|
||||||
src/asm/main.o \
|
src/asm/main.o \
|
||||||
src/asm/math.o \
|
|
||||||
src/asm/parser.o \
|
src/asm/parser.o \
|
||||||
|
src/asm/opt.o \
|
||||||
src/asm/output.o \
|
src/asm/output.o \
|
||||||
src/asm/rpn.o \
|
src/asm/rpn.o \
|
||||||
src/asm/section.o \
|
src/asm/section.o \
|
||||||
@@ -70,7 +72,8 @@ rgbasm_obj := \
|
|||||||
src/extern/getopt.o \
|
src/extern/getopt.o \
|
||||||
src/extern/utf8decoder.o \
|
src/extern/utf8decoder.o \
|
||||||
src/hashmap.o \
|
src/hashmap.o \
|
||||||
src/linkdefs.o
|
src/linkdefs.o \
|
||||||
|
src/opmath.o
|
||||||
|
|
||||||
src/asm/lexer.o src/asm/main.o: src/asm/parser.h
|
src/asm/lexer.o src/asm/main.o: src/asm/parser.h
|
||||||
|
|
||||||
@@ -86,7 +89,8 @@ rgblink_obj := \
|
|||||||
src/extern/err.o \
|
src/extern/err.o \
|
||||||
src/extern/getopt.o \
|
src/extern/getopt.o \
|
||||||
src/hashmap.o \
|
src/hashmap.o \
|
||||||
src/linkdefs.o
|
src/linkdefs.o \
|
||||||
|
src/opmath.o
|
||||||
|
|
||||||
rgbfix_obj := \
|
rgbfix_obj := \
|
||||||
src/fix/main.o \
|
src/fix/main.o \
|
||||||
@@ -114,15 +118,27 @@ rgbgfx: ${rgbgfx_obj}
|
|||||||
|
|
||||||
# Rules to process files
|
# Rules to process files
|
||||||
|
|
||||||
# We want the yacc invocations to pass through our rules, not default ones
|
# We want the Bison invocation to pass through our rules, not default ones
|
||||||
.y.o:
|
.y.o:
|
||||||
|
|
||||||
# yacc-generated C files have an accompanying header
|
# Bison-generated C files have an accompanying header
|
||||||
.c.h:
|
src/asm/parser.h: src/asm/parser.c
|
||||||
$Qtouch $@
|
$Qtouch $@
|
||||||
|
|
||||||
.y.c:
|
src/asm/parser.c: src/asm/parser.y
|
||||||
$Q${YACC} -d ${YFLAGS} -o $@ $<
|
$QDEFS=; \
|
||||||
|
add_flag(){ \
|
||||||
|
if src/check_bison_ver.sh $$1 $$2; then \
|
||||||
|
DEFS="-D$$3 $$DEFS"; \
|
||||||
|
fi \
|
||||||
|
}; \
|
||||||
|
add_flag 3 5 api.token.raw=true; \
|
||||||
|
add_flag 3 6 parse.error=detailed; \
|
||||||
|
add_flag 3 0 parse.error=verbose; \
|
||||||
|
add_flag 3 0 parse.lac=full; \
|
||||||
|
add_flag 3 0 lr.type=ielr; \
|
||||||
|
echo "DEFS=$$DEFS"; \
|
||||||
|
${BISON} $$DEFS -d ${YFLAGS} -o $@ $<
|
||||||
|
|
||||||
.c.o:
|
.c.o:
|
||||||
$Q${CC} ${REALCFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
$Q${CC} ${REALCFLAGS} ${PNGCFLAGS} -c -o $@ $<
|
||||||
@@ -178,6 +194,7 @@ checkpatch:
|
|||||||
for commit in `git rev-list $$COMMON_COMMIT..HEAD`; do \
|
for commit in `git rev-list $$COMMON_COMMIT..HEAD`; do \
|
||||||
echo "[*] Analyzing commit '$$commit'"; \
|
echo "[*] Analyzing commit '$$commit'"; \
|
||||||
git format-patch --stdout "$$commit~..$$commit" \
|
git format-patch --stdout "$$commit~..$$commit" \
|
||||||
|
-- src include '!src/extern' '!include/extern' \
|
||||||
| ${CHECKPATCH} - || true; \
|
| ${CHECKPATCH} - || true; \
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -189,7 +206,7 @@ develop:
|
|||||||
$Qenv $(MAKE) -j WARNFLAGS="-Werror -Wall -Wextra -Wpedantic -Wno-type-limits \
|
$Qenv $(MAKE) -j WARNFLAGS="-Werror -Wall -Wextra -Wpedantic -Wno-type-limits \
|
||||||
-Wno-sign-compare -Wvla -Wformat -Wformat-security -Wformat-overflow=2 \
|
-Wno-sign-compare -Wvla -Wformat -Wformat-security -Wformat-overflow=2 \
|
||||||
-Wformat-truncation=1 -Wformat-y2k -Wswitch-enum -Wunused \
|
-Wformat-truncation=1 -Wformat-y2k -Wswitch-enum -Wunused \
|
||||||
-Wuninitialized -Wunknown-pragmas -Wstrict-overflow=5 \
|
-Wuninitialized -Wunknown-pragmas -Wstrict-overflow=4 \
|
||||||
-Wstringop-overflow=4 -Walloc-zero -Wduplicated-cond \
|
-Wstringop-overflow=4 -Walloc-zero -Wduplicated-cond \
|
||||||
-Wfloat-equal -Wshadow -Wcast-qual -Wcast-align -Wlogical-op \
|
-Wfloat-equal -Wshadow -Wcast-qual -Wcast-align -Wlogical-op \
|
||||||
-Wnested-externs -Wno-aggressive-loop-optimizations -Winline \
|
-Wnested-externs -Wno-aggressive-loop-optimizations -Winline \
|
||||||
@@ -206,11 +223,11 @@ develop:
|
|||||||
# install instructions instead.
|
# install instructions instead.
|
||||||
|
|
||||||
mingw32:
|
mingw32:
|
||||||
$Qmake CC=i686-w64-mingw32-gcc YACC=bison \
|
$Qmake CC=i686-w64-mingw32-gcc BISON=bison \
|
||||||
PKG_CONFIG=i686-w64-mingw32-pkg-config -j
|
PKG_CONFIG=i686-w64-mingw32-pkg-config -j
|
||||||
|
|
||||||
mingw64:
|
mingw64:
|
||||||
$Qmake CC=x86_64-w64-mingw32-gcc YACC=bison \
|
$Qmake CC=x86_64-w64-mingw32-gcc BISON=bison \
|
||||||
PKG_CONFIG=x86_64-w64-mingw32-pkg-config -j
|
PKG_CONFIG=x86_64-w64-mingw32-pkg-config -j
|
||||||
|
|
||||||
wine-shim:
|
wine-shim:
|
||||||
|
|||||||
@@ -31,31 +31,33 @@ diff <(xxd $1) <(xxd $2) | while read -r LINE; do
|
|||||||
# Separator between files switches states
|
# Separator between files switches states
|
||||||
echo $LINE
|
echo $LINE
|
||||||
STATE=3
|
STATE=3
|
||||||
elif echo $LINE | grep -Eq '^[0-9]+(,[0-9]+)?c[0-9]+(,[0-9]+)?'; then
|
elif grep -Eq '^[0-9]+(,[0-9]+)?[cd][0-9]+(,[0-9]+)?' <<< "$LINE"; then
|
||||||
# Line info resets the whole thing
|
# Line info resets the whole thing
|
||||||
STATE=1
|
STATE=1
|
||||||
elif [ $STATE -eq 1 -o $STATE -eq 3 ]; then
|
elif [ $STATE -eq 1 -o $STATE -eq 3 ]; then
|
||||||
# Compute the GB address from the ROM offset
|
# Compute the GB address from the ROM offset
|
||||||
OFS=$(echo $LINE | cut -d ' ' -f 2 | tr -d ':')
|
OFS=$(cut -d ' ' -f 2 <<< "$LINE" | tr -d ':')
|
||||||
BANK=$((0x$OFS / 0x4000))
|
BANK=$((0x$OFS / 0x4000))
|
||||||
ADDR=$((0x$OFS % 0x4000 + ($BANK != 0) * 0x4000))
|
ADDR=$((0x$OFS % 0x4000 + ($BANK != 0) * 0x4000))
|
||||||
# Try finding the preceding symbol closest to the diff
|
# Try finding the preceding symbol closest to the diff
|
||||||
if [ $STATE -eq 1 ]; then
|
if [ $STATE -eq 1 ]; then
|
||||||
STATE=2
|
STATE=2
|
||||||
SYMFILE=$(echo $1 | cut -d '.' -f 1).sym
|
SYMFILE=${1%.*}.sym
|
||||||
else
|
else
|
||||||
STATE=4
|
STATE=4
|
||||||
SYMFILE=$(echo $2 | cut -d '.' -f 1).sym
|
SYMFILE=${2%.*}.sym
|
||||||
fi
|
fi
|
||||||
EXTRA=$(if [ -f "$SYMFILE" ]; then
|
EXTRA=$(if [ -f "$SYMFILE" ]; then
|
||||||
# Read the sym file for such a symbol
|
# Read the sym file for such a symbol
|
||||||
# Ignore comment lines, only pick matching bank
|
# Ignore comment lines, only pick matching bank
|
||||||
grep -Fv ';' "$SYMFILE" |
|
# (The bank regex ignores comments already, make `cut` and `tr` process less lines)
|
||||||
grep -Ei $(printf "^%02x:" $BANK) |
|
grep -Ei $(printf "^%02x:" $BANK) "$SYMFILE" |
|
||||||
|
cut -d ';' -f 1 |
|
||||||
|
tr -d "\r" |
|
||||||
while read -r SYMADDR SYM; do
|
while read -r SYMADDR SYM; do
|
||||||
SYMADDR=$((0x$(echo $SYMADDR | cut -d ':' -f 2)))
|
SYMADDR=$((0x${SYMADDR#*:}))
|
||||||
if [ $SYMADDR -le $ADDR ]; then
|
if [ $SYMADDR -le $ADDR ]; then
|
||||||
printf " (%s+%#x)\n" $SYM $(($ADDR - $SYMADDR))
|
printf " (%s+%#x)\n" "$SYM" $(($ADDR - $SYMADDR))
|
||||||
fi
|
fi
|
||||||
# TODO: assumes sorted sym files
|
# TODO: assumes sorted sym files
|
||||||
done | tail -n 1
|
done | tail -n 1
|
||||||
@@ -63,9 +65,9 @@ diff <(xxd $1) <(xxd $2) | while read -r LINE; do
|
|||||||
printf "%02x:%04x %s\n" $BANK $ADDR $EXTRA
|
printf "%02x:%04x %s\n" $BANK $ADDR $EXTRA
|
||||||
fi
|
fi
|
||||||
if [ $STATE -eq 2 -o $STATE -eq 4 ]; then
|
if [ $STATE -eq 2 -o $STATE -eq 4 ]; then
|
||||||
OFS=$(echo $LINE | cut -d ' ' -f 2 | tr -d ':')
|
OFS=$(cut -d ' ' -f 2 <<< "$LINE" | tr -d ':')
|
||||||
BANK=$((0x$OFS / 0x4000))
|
BANK=$((0x$OFS / 0x4000))
|
||||||
ADDR=$((0x$OFS % 0x4000 + ($BANK != 0) * 0x4000))
|
ADDR=$((0x$OFS % 0x4000 + ($BANK != 0) * 0x4000))
|
||||||
printf "%s %02x:%04x: %s\n" "$(echo $LINE | cut -d ' ' -f 1)" $BANK $ADDR "$(echo $LINE | cut -d ' ' -f 3-)"
|
printf "%s %02x:%04x: %s\n" "${LINE:0:1}" $BANK $ADDR "${LINE#*: }"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Contains some assembler-wide defines and externs
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef RGBDS_ASM_ASM_H
|
|
||||||
#define RGBDS_ASM_ASM_H
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
#include "asm/localasm.h"
|
|
||||||
#include "asm/symbol.h"
|
|
||||||
|
|
||||||
#define MAXMACROARGS 99999
|
|
||||||
#define MAXINCPATHS 128
|
|
||||||
|
|
||||||
extern uint32_t nTotalLines;
|
|
||||||
extern uint32_t nIFDepth;
|
|
||||||
extern struct Section *pCurrentSection;
|
|
||||||
|
|
||||||
#endif /* RGBDS_ASM_ASM_H */
|
|
||||||
31
include/asm/fixpoint.h
Normal file
31
include/asm/fixpoint.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 1997-2021, Carsten Sorensen and RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef RGBDS_ASM_FIXPOINT_H
|
||||||
|
#define RGBDS_ASM_FIXPOINT_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
int32_t fix_Callback_PI(void);
|
||||||
|
void fix_Print(int32_t i);
|
||||||
|
int32_t fix_Sin(int32_t i);
|
||||||
|
int32_t fix_Cos(int32_t i);
|
||||||
|
int32_t fix_Tan(int32_t i);
|
||||||
|
int32_t fix_ASin(int32_t i);
|
||||||
|
int32_t fix_ACos(int32_t i);
|
||||||
|
int32_t fix_ATan(int32_t i);
|
||||||
|
int32_t fix_ATan2(int32_t i, int32_t j);
|
||||||
|
int32_t fix_Mul(int32_t i, int32_t j);
|
||||||
|
int32_t fix_Div(int32_t i, int32_t j);
|
||||||
|
int32_t fix_Pow(int32_t i, int32_t j);
|
||||||
|
int32_t fix_Log(int32_t i, int32_t j);
|
||||||
|
int32_t fix_Round(int32_t i);
|
||||||
|
int32_t fix_Ceil(int32_t i);
|
||||||
|
int32_t fix_Floor(int32_t i);
|
||||||
|
|
||||||
|
#endif /* RGBDS_ASM_FIXPOINT_H */
|
||||||
63
include/asm/format.h
Normal file
63
include/asm/format.h
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020, RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef RGBDS_FORMAT_SPEC_H
|
||||||
|
#define RGBDS_FORMAT_SPEC_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
enum FormatState {
|
||||||
|
FORMAT_SIGN, // expects '+' or ' ' (optional)
|
||||||
|
FORMAT_PREFIX, // expects '#' (optional)
|
||||||
|
FORMAT_ALIGN, // expects '-' (optional)
|
||||||
|
FORMAT_WIDTH, // expects '0'-'9', max 255 (optional) (leading '0' indicates pad)
|
||||||
|
FORMAT_FRAC, // got '.', expects '0'-'9', max 255 (optional)
|
||||||
|
FORMAT_DONE, // got [duXxbofs] (required)
|
||||||
|
FORMAT_INVALID, // got unexpected character
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FormatSpec {
|
||||||
|
enum FormatState state;
|
||||||
|
int sign;
|
||||||
|
bool prefix;
|
||||||
|
bool alignLeft;
|
||||||
|
bool padZero;
|
||||||
|
uint8_t width;
|
||||||
|
bool hasFrac;
|
||||||
|
uint8_t fracWidth;
|
||||||
|
int type;
|
||||||
|
bool valid;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StrFmtArg {
|
||||||
|
union {
|
||||||
|
uint32_t number;
|
||||||
|
char *string;
|
||||||
|
};
|
||||||
|
bool isNumeric;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define INITIAL_STRFMT_ARG_SIZE 4
|
||||||
|
struct StrFmtArgList {
|
||||||
|
char *format;
|
||||||
|
size_t nbArgs;
|
||||||
|
size_t capacity;
|
||||||
|
struct StrFmtArg *args;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FormatSpec fmt_NewSpec(void);
|
||||||
|
bool fmt_IsEmpty(struct FormatSpec const *fmt);
|
||||||
|
bool fmt_IsValid(struct FormatSpec const *fmt);
|
||||||
|
bool fmt_IsFinished(struct FormatSpec const *fmt);
|
||||||
|
void fmt_UseCharacter(struct FormatSpec *fmt, int c);
|
||||||
|
void fmt_FinishCharacters(struct FormatSpec *fmt);
|
||||||
|
void fmt_PrintString(char *buf, size_t bufLen, struct FormatSpec const *fmt, char const *value);
|
||||||
|
void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uint32_t value);
|
||||||
|
|
||||||
|
#endif /* RGBDS_FORMAT_SPEC_H */
|
||||||
@@ -13,10 +13,10 @@
|
|||||||
#ifndef RGBDS_ASM_FSTACK_H
|
#ifndef RGBDS_ASM_FSTACK_H
|
||||||
#define RGBDS_ASM_FSTACK_H
|
#define RGBDS_ASM_FSTACK_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "asm/asm.h"
|
|
||||||
#include "asm/lexer.h"
|
#include "asm/lexer.h"
|
||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
@@ -73,6 +73,10 @@ bool yywrap(void);
|
|||||||
void fstk_RunInclude(char const *path);
|
void fstk_RunInclude(char const *path);
|
||||||
void fstk_RunMacro(char const *macroName, struct MacroArgs *args);
|
void fstk_RunMacro(char const *macroName, struct MacroArgs *args);
|
||||||
void fstk_RunRept(uint32_t count, int32_t nReptLineNo, char *body, size_t size);
|
void fstk_RunRept(uint32_t count, int32_t nReptLineNo, char *body, size_t size);
|
||||||
|
void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step,
|
||||||
|
int32_t reptLineNo, char *body, size_t size);
|
||||||
|
void fstk_StopRept(void);
|
||||||
|
bool fstk_Break(void);
|
||||||
|
|
||||||
void fstk_Init(char const *mainPath, size_t maxRecursionDepth);
|
void fstk_Init(char const *mainPath, size_t maxRecursionDepth);
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
#ifndef RGBDS_ASM_LEXER_H
|
#ifndef RGBDS_ASM_LEXER_H
|
||||||
#define RGBDS_ASM_LEXER_H
|
#define RGBDS_ASM_LEXER_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
#define MAXSTRLEN 255
|
#define MAXSTRLEN 255
|
||||||
|
|
||||||
struct LexerState;
|
struct LexerState;
|
||||||
@@ -30,17 +32,21 @@ static inline void lexer_SetStateAtEOL(struct LexerState *state)
|
|||||||
lexerStateEOL = state;
|
lexerStateEOL = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern char const *binDigits;
|
extern char binDigits[2];
|
||||||
extern char const *gfxDigits;
|
extern char gfxDigits[4];
|
||||||
|
|
||||||
static inline void lexer_SetBinDigits(char const *digits)
|
static inline void lexer_SetBinDigits(char const digits[2])
|
||||||
{
|
{
|
||||||
binDigits = digits;
|
binDigits[0] = digits[0];
|
||||||
|
binDigits[1] = digits[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void lexer_SetGfxDigits(char const *digits)
|
static inline void lexer_SetGfxDigits(char const digits[4])
|
||||||
{
|
{
|
||||||
gfxDigits = digits;
|
gfxDigits[0] = digits[0];
|
||||||
|
gfxDigits[1] = digits[1];
|
||||||
|
gfxDigits[2] = digits[2];
|
||||||
|
gfxDigits[3] = digits[3];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -56,18 +62,40 @@ enum LexerMode {
|
|||||||
LEXER_NORMAL,
|
LEXER_NORMAL,
|
||||||
LEXER_RAW,
|
LEXER_RAW,
|
||||||
LEXER_SKIP_TO_ELIF,
|
LEXER_SKIP_TO_ELIF,
|
||||||
LEXER_SKIP_TO_ENDC
|
LEXER_SKIP_TO_ENDC,
|
||||||
|
LEXER_SKIP_TO_ENDR
|
||||||
};
|
};
|
||||||
|
|
||||||
void lexer_SetMode(enum LexerMode mode);
|
void lexer_SetMode(enum LexerMode mode);
|
||||||
void lexer_ToggleStringExpansion(bool enable);
|
void lexer_ToggleStringExpansion(bool enable);
|
||||||
|
|
||||||
|
uint32_t lexer_GetIFDepth(void);
|
||||||
|
void lexer_IncIFDepth(void);
|
||||||
|
void lexer_DecIFDepth(void);
|
||||||
|
bool lexer_RanIFBlock(void);
|
||||||
|
bool lexer_ReachedELSEBlock(void);
|
||||||
|
void lexer_RunIFBlock(void);
|
||||||
|
void lexer_ReachELSEBlock(void);
|
||||||
|
|
||||||
|
struct CaptureBody {
|
||||||
|
uint32_t lineNo;
|
||||||
|
char *body;
|
||||||
|
size_t size;
|
||||||
|
};
|
||||||
|
|
||||||
char const *lexer_GetFileName(void);
|
char const *lexer_GetFileName(void);
|
||||||
uint32_t lexer_GetLineNo(void);
|
uint32_t lexer_GetLineNo(void);
|
||||||
uint32_t lexer_GetColNo(void);
|
uint32_t lexer_GetColNo(void);
|
||||||
void lexer_DumpStringExpansions(void);
|
void lexer_DumpStringExpansions(void);
|
||||||
int yylex(void);
|
int yylex(void);
|
||||||
void lexer_CaptureRept(char **capture, size_t *size);
|
void lexer_CaptureRept(struct CaptureBody *capture);
|
||||||
void lexer_CaptureMacroBody(char **capture, size_t *size);
|
void lexer_CaptureMacroBody(struct CaptureBody *capture);
|
||||||
|
|
||||||
|
#define INITIAL_DS_ARG_SIZE 2
|
||||||
|
struct DsArgList {
|
||||||
|
size_t nbArgs;
|
||||||
|
size_t capacity;
|
||||||
|
struct Expression *args;
|
||||||
|
};
|
||||||
|
|
||||||
#endif /* RGBDS_ASM_LEXER_H */
|
#endif /* RGBDS_ASM_LEXER_H */
|
||||||
|
|||||||
@@ -1,131 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef RGBDS_ASM_LOCALASM_H
|
|
||||||
#define RGBDS_ASM_LOCALASM_H
|
|
||||||
|
|
||||||
/*
|
|
||||||
* GB Z80 instruction groups
|
|
||||||
*
|
|
||||||
* n3 = 3-bit
|
|
||||||
* n = 8-bit
|
|
||||||
* nn = 16-bit
|
|
||||||
*
|
|
||||||
* ADC A,n : 0xCE
|
|
||||||
* ADC A,r : 0x88|r
|
|
||||||
* ADD A,n : 0xC6
|
|
||||||
* ADD A,r : 0x80|r
|
|
||||||
* ADD HL,ss : 0x09|(ss<<4)
|
|
||||||
* ADD SP,n : 0xE8
|
|
||||||
* AND A,n : 0xE6
|
|
||||||
* AND A,r : 0xA0|r
|
|
||||||
* BIT n3,r : 0xCB 0x40|(n3<<3)|r
|
|
||||||
* CALL cc,nn : 0xC4|(cc<<3)
|
|
||||||
* CALL nn : 0xCD
|
|
||||||
* CCF : 0x3F
|
|
||||||
* CP A,n : 0xFE
|
|
||||||
* CP A,r : 0xB8|r
|
|
||||||
* CPL : 0x2F
|
|
||||||
* DAA : 0x27
|
|
||||||
* DEC r : 0x05|(r<<3)
|
|
||||||
* DEC ss : 0x0B|(ss<<4)
|
|
||||||
* DI : 0xF3
|
|
||||||
* EI : 0xFB
|
|
||||||
* HALT : 0x76
|
|
||||||
* INC r : 0x04|(r<<3)
|
|
||||||
* INC ss : 0x03|(ss<<4)
|
|
||||||
* JP HL : 0xE9
|
|
||||||
* JP cc,nn : 0xC2|(cc<<3)
|
|
||||||
* JP nn : 0xC3|(cc<<3)
|
|
||||||
* JR n : 0x18
|
|
||||||
* JR cc,n : 0x20|(cc<<3)
|
|
||||||
* LD (nn),SP : 0x08
|
|
||||||
* LD ($FF00+C),A : 0xE2
|
|
||||||
* LD ($FF00+n),A : 0xE0
|
|
||||||
* LD (nn),A : 0xEA
|
|
||||||
* LD (rr),A : 0x02|(rr<<4) // HL+ and HL- included
|
|
||||||
* LD A,($FF00+C) : 0xF2
|
|
||||||
* LD A,($FF00+n) : 0xF0
|
|
||||||
* LD A,(nn) : 0xFA
|
|
||||||
* LD A,(rr) : 0x0A|(rr<<4) // HL+ and HL- included
|
|
||||||
* LD HL,SP+n : 0xF8
|
|
||||||
* LD SP,HL : 0xF9
|
|
||||||
* LD r,n : 0x06|(r<<3)
|
|
||||||
* LD r,r' : 0x40|(r<<3)|r' // NOTE: LD (HL),(HL) not allowed
|
|
||||||
* LD ss,nn : 0x01|(ss<<4)
|
|
||||||
* NOP : 0x00
|
|
||||||
* OR A,n : 0xF6
|
|
||||||
* OR A,r : 0xB0|r
|
|
||||||
* POP tt : 0xC1|(tt<<4)
|
|
||||||
* PUSH tt : 0xC5|(tt<<4)
|
|
||||||
* RES n3,r : 0xCB 0x80|(n3<<3)|r
|
|
||||||
* RET : 0xC9
|
|
||||||
* RET cc : 0xC0|(cc<<3)
|
|
||||||
* RETI : 0xD9
|
|
||||||
* RL r : 0xCB 0x10|r
|
|
||||||
* RLA : 0x17
|
|
||||||
* RLC r : 0xCB 0x00|r
|
|
||||||
* RLCA : 0x07
|
|
||||||
* RR r : 0xCB 0x18|r
|
|
||||||
* RRA : 0x1F
|
|
||||||
* RRC r : 0xCB 0x08|r
|
|
||||||
* RRCA : 0x0F
|
|
||||||
* RST n : 0xC7|n
|
|
||||||
* SBC A,n : 0xDE
|
|
||||||
* SBC A,r : 0x98|r
|
|
||||||
* SCF : 0x37
|
|
||||||
* SET n3,r : 0xCB 0xC0|(n8<<3)|r
|
|
||||||
* SLA r : 0xCB 0x20|r
|
|
||||||
* SRA r : 0xCB 0x28|r
|
|
||||||
* SRL r : 0xCB 0x38|r
|
|
||||||
* STOP : 0x10 0x00
|
|
||||||
* SUB A,n : 0xD6
|
|
||||||
* SUB A,r : 0x90|r
|
|
||||||
* SWAP r : 0xCB 0x30|r
|
|
||||||
* XOR A,n : 0xEE
|
|
||||||
* XOR A,r : 0xA8|r
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* "r" defs */
|
|
||||||
enum {
|
|
||||||
REG_B = 0,
|
|
||||||
REG_C,
|
|
||||||
REG_D,
|
|
||||||
REG_E,
|
|
||||||
REG_H,
|
|
||||||
REG_L,
|
|
||||||
REG_HL_IND,
|
|
||||||
REG_A
|
|
||||||
};
|
|
||||||
|
|
||||||
/* "rr" defs */
|
|
||||||
enum {
|
|
||||||
REG_BC_IND = 0,
|
|
||||||
REG_DE_IND,
|
|
||||||
REG_HL_INDINC,
|
|
||||||
REG_HL_INDDEC,
|
|
||||||
};
|
|
||||||
|
|
||||||
/* "ss" defs (SP) and "tt" defs (AF) */
|
|
||||||
enum {
|
|
||||||
REG_BC = 0,
|
|
||||||
REG_DE = 1,
|
|
||||||
REG_HL = 2,
|
|
||||||
REG_SP = 3,
|
|
||||||
REG_AF = 3
|
|
||||||
};
|
|
||||||
|
|
||||||
/* "cc" defs */
|
|
||||||
enum {
|
|
||||||
CC_NZ = 0,
|
|
||||||
CC_Z,
|
|
||||||
CC_NC,
|
|
||||||
CC_C
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* RGBDS_ASM_LOCALASM_H */
|
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
#define RGBDS_MACRO_H
|
#define RGBDS_MACRO_H
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include "asm/warning.h"
|
#include "asm/warning.h"
|
||||||
@@ -24,6 +25,7 @@ void macro_AppendArg(struct MacroArgs **args, char *s);
|
|||||||
void macro_UseNewArgs(struct MacroArgs *args);
|
void macro_UseNewArgs(struct MacroArgs *args);
|
||||||
void macro_FreeArgs(struct MacroArgs *args);
|
void macro_FreeArgs(struct MacroArgs *args);
|
||||||
char const *macro_GetArg(uint32_t i);
|
char const *macro_GetArg(uint32_t i);
|
||||||
|
char *macro_GetAllArgs(void);
|
||||||
|
|
||||||
uint32_t macro_GetUniqueID(void);
|
uint32_t macro_GetUniqueID(void);
|
||||||
char const *macro_GetUniqueIDStr(void);
|
char const *macro_GetUniqueIDStr(void);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of RGBDS.
|
* This file is part of RGBDS.
|
||||||
*
|
*
|
||||||
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
|
* Copyright (c) 1997-2021, Carsten Sorensen and RGBDS contributors.
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
@@ -15,19 +15,6 @@
|
|||||||
|
|
||||||
#include "helpers.h"
|
#include "helpers.h"
|
||||||
|
|
||||||
struct sOptions {
|
|
||||||
char binary[2];
|
|
||||||
char gbgfx[4];
|
|
||||||
int32_t fillchar;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern char *tzNewMacro;
|
|
||||||
extern uint32_t ulNewMacroSize;
|
|
||||||
extern int32_t nGBGfxID;
|
|
||||||
extern int32_t nBinaryID;
|
|
||||||
|
|
||||||
extern struct sOptions DefaultOptions;
|
|
||||||
extern struct sOptions CurrentOptions;
|
|
||||||
extern bool haltnop;
|
extern bool haltnop;
|
||||||
extern bool optimizeloads;
|
extern bool optimizeloads;
|
||||||
extern bool verbose;
|
extern bool verbose;
|
||||||
@@ -39,13 +26,6 @@ extern bool oGeneratedMissingIncludes;
|
|||||||
extern bool oFailedOnMissingInclude;
|
extern bool oFailedOnMissingInclude;
|
||||||
extern bool oGeneratePhonyDeps;
|
extern bool oGeneratePhonyDeps;
|
||||||
|
|
||||||
void opt_Push(void);
|
|
||||||
void opt_Pop(void);
|
|
||||||
void opt_Parse(char *s);
|
|
||||||
|
|
||||||
void upperstring(char *s);
|
|
||||||
void lowerstring(char *s);
|
|
||||||
|
|
||||||
/* TODO: are these really needed? */
|
/* TODO: are these really needed? */
|
||||||
#define YY_FATAL_ERROR fatalerror
|
#define YY_FATAL_ERROR fatalerror
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef RGBDS_ASM_MATH_H
|
|
||||||
#define RGBDS_ASM_MATH_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
void math_DefinePI(void);
|
|
||||||
void math_Print(int32_t i);
|
|
||||||
int32_t math_Sin(int32_t i);
|
|
||||||
int32_t math_Cos(int32_t i);
|
|
||||||
int32_t math_Tan(int32_t i);
|
|
||||||
int32_t math_ASin(int32_t i);
|
|
||||||
int32_t math_ACos(int32_t i);
|
|
||||||
int32_t math_ATan(int32_t i);
|
|
||||||
int32_t math_ATan2(int32_t i, int32_t j);
|
|
||||||
int32_t math_Mul(int32_t i, int32_t j);
|
|
||||||
int32_t math_Div(int32_t i, int32_t j);
|
|
||||||
int32_t math_Round(int32_t i);
|
|
||||||
int32_t math_Ceil(int32_t i);
|
|
||||||
int32_t math_Floor(int32_t i);
|
|
||||||
|
|
||||||
#endif /* RGBDS_ASM_MATH_H */
|
|
||||||
22
include/asm/opt.h
Normal file
22
include/asm/opt.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021, Eldred Habert and RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef RGBDS_OPT_H
|
||||||
|
#define RGBDS_OPT_H
|
||||||
|
|
||||||
|
|
||||||
|
void opt_B(char chars[2]);
|
||||||
|
void opt_G(char chars[4]);
|
||||||
|
void opt_P(uint8_t fill);
|
||||||
|
void opt_Parse(char const *option);
|
||||||
|
|
||||||
|
void opt_Push(void);
|
||||||
|
void opt_Pop(void);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
#include "linkdefs.h"
|
#include "linkdefs.h"
|
||||||
|
|
||||||
struct Expression;
|
struct Expression;
|
||||||
|
struct FileStackNode;
|
||||||
|
|
||||||
extern char *tzObjectname;
|
extern char *tzObjectname;
|
||||||
extern struct Section *pSectionList, *pCurrentSection;
|
extern struct Section *pSectionList, *pCurrentSection;
|
||||||
@@ -21,8 +22,7 @@ extern struct Section *pSectionList, *pCurrentSection;
|
|||||||
void out_RegisterNode(struct FileStackNode *node);
|
void out_RegisterNode(struct FileStackNode *node);
|
||||||
void out_ReplaceNode(struct FileStackNode *node);
|
void out_ReplaceNode(struct FileStackNode *node);
|
||||||
void out_SetFileName(char *s);
|
void out_SetFileName(char *s);
|
||||||
void out_CreatePatch(uint32_t type, struct Expression const *expr,
|
void out_CreatePatch(uint32_t type, struct Expression const *expr, uint32_t ofs, uint32_t pcShift);
|
||||||
uint32_t ofs);
|
|
||||||
bool out_CreateAssert(enum AssertionType type, struct Expression const *expr,
|
bool out_CreateAssert(enum AssertionType type, struct Expression const *expr,
|
||||||
char const *message, uint32_t ofs);
|
char const *message, uint32_t ofs);
|
||||||
void out_WriteObject(void);
|
void out_WriteObject(void);
|
||||||
|
|||||||
@@ -27,9 +27,6 @@ struct Expression {
|
|||||||
uint32_t nRPNPatchSize; // Size the expression will take in the obj file
|
uint32_t nRPNPatchSize; // Size the expression will take in the obj file
|
||||||
};
|
};
|
||||||
|
|
||||||
/* FIXME: Should be defined in `parser.h`, but impossible with POSIX Yacc */
|
|
||||||
extern int32_t nPCOffset;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Determines if an expression is known at assembly time
|
* Determines if an expression is known at assembly time
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -14,12 +14,16 @@
|
|||||||
|
|
||||||
#include "linkdefs.h"
|
#include "linkdefs.h"
|
||||||
|
|
||||||
|
extern uint8_t fillByte;
|
||||||
|
|
||||||
struct Expression;
|
struct Expression;
|
||||||
|
|
||||||
struct Section {
|
struct Section {
|
||||||
char *name;
|
char *name;
|
||||||
enum SectionType type;
|
enum SectionType type;
|
||||||
enum SectionModifier modifier;
|
enum SectionModifier modifier;
|
||||||
|
struct FileStackNode *src; /* Where the section was defined */
|
||||||
|
uint32_t fileLine; /* Line where the section was defined */
|
||||||
uint32_t size;
|
uint32_t size;
|
||||||
uint32_t org;
|
uint32_t org;
|
||||||
uint32_t bank;
|
uint32_t bank;
|
||||||
@@ -41,7 +45,8 @@ void out_NewSection(char const *name, uint32_t secttype, uint32_t org,
|
|||||||
struct SectionSpec const *attributes,
|
struct SectionSpec const *attributes,
|
||||||
enum SectionModifier mod);
|
enum SectionModifier mod);
|
||||||
void out_SetLoadSection(char const *name, uint32_t secttype, uint32_t org,
|
void out_SetLoadSection(char const *name, uint32_t secttype, uint32_t org,
|
||||||
struct SectionSpec const *attributes);
|
struct SectionSpec const *attributes,
|
||||||
|
enum SectionModifier mod);
|
||||||
void out_EndLoadSection(void);
|
void out_EndLoadSection(void);
|
||||||
|
|
||||||
struct Section *sect_GetSymbolSection(void);
|
struct Section *sect_GetSymbolSection(void);
|
||||||
@@ -56,13 +61,15 @@ void sect_CheckUnionClosed(void);
|
|||||||
|
|
||||||
void out_AbsByte(uint8_t b);
|
void out_AbsByte(uint8_t b);
|
||||||
void out_AbsByteGroup(uint8_t const *s, int32_t length);
|
void out_AbsByteGroup(uint8_t const *s, int32_t length);
|
||||||
|
void out_AbsWordGroup(uint8_t const *s, int32_t length);
|
||||||
|
void out_AbsLongGroup(uint8_t const *s, int32_t length);
|
||||||
void out_Skip(int32_t skip, bool ds);
|
void out_Skip(int32_t skip, bool ds);
|
||||||
void out_String(char const *s);
|
void out_String(char const *s);
|
||||||
void out_RelByte(struct Expression *expr);
|
void out_RelByte(struct Expression *expr, uint32_t pcShift);
|
||||||
void out_RelBytes(struct Expression *expr, uint32_t n);
|
void out_RelBytes(uint32_t n, struct Expression *exprs, size_t size);
|
||||||
void out_RelWord(struct Expression *expr);
|
void out_RelWord(struct Expression *expr, uint32_t pcShift);
|
||||||
void out_RelLong(struct Expression *expr);
|
void out_RelLong(struct Expression *expr, uint32_t pcShift);
|
||||||
void out_PCRelByte(struct Expression *expr);
|
void out_PCRelByte(struct Expression *expr, uint32_t pcShift);
|
||||||
void out_BinaryFile(char const *s, int32_t startPos);
|
void out_BinaryFile(char const *s, int32_t startPos);
|
||||||
void out_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length);
|
void out_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length);
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,11 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
#include "asm/section.h"
|
#include "asm/section.h"
|
||||||
|
|
||||||
|
#include "platform.h" // MIN_NB_ELMS
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
#define HASHSIZE (1 << 16)
|
#define HASHSIZE (1 << 16)
|
||||||
@@ -115,6 +117,8 @@ int32_t sym_GetValue(struct Symbol const *sym);
|
|||||||
void sym_SetExportAll(bool set);
|
void sym_SetExportAll(bool set);
|
||||||
struct Symbol *sym_AddLocalLabel(char const *symName);
|
struct Symbol *sym_AddLocalLabel(char const *symName);
|
||||||
struct Symbol *sym_AddLabel(char const *symName);
|
struct Symbol *sym_AddLabel(char const *symName);
|
||||||
|
struct Symbol *sym_AddAnonLabel(void);
|
||||||
|
void sym_WriteAnonLabelName(char buf[MIN_NB_ELMS(MAXSYMLEN + 1)], uint32_t ofs, bool neg);
|
||||||
void sym_Export(char const *symName);
|
void sym_Export(char const *symName);
|
||||||
struct Symbol *sym_AddEqu(char const *symName, int32_t value);
|
struct Symbol *sym_AddEqu(char const *symName, int32_t value);
|
||||||
struct Symbol *sym_AddSet(char const *symName, int32_t value);
|
struct Symbol *sym_AddSet(char const *symName, int32_t value);
|
||||||
@@ -137,8 +141,9 @@ struct Symbol const *sym_GetPC(void);
|
|||||||
struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body, size_t size);
|
struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body, size_t size);
|
||||||
struct Symbol *sym_Ref(char const *symName);
|
struct Symbol *sym_Ref(char const *symName);
|
||||||
struct Symbol *sym_AddString(char const *symName, char const *value);
|
struct Symbol *sym_AddString(char const *symName, char const *value);
|
||||||
|
struct Symbol *sym_RedefString(char const *symName, char const *value);
|
||||||
void sym_Purge(char const *symName);
|
void sym_Purge(char const *symName);
|
||||||
void sym_Init(void);
|
void sym_Init(time_t now);
|
||||||
|
|
||||||
/* Functions to save and restore the current symbol scope. */
|
/* Functions to save and restore the current symbol scope. */
|
||||||
char const *sym_GetCurrentSymbolScope(void);
|
char const *sym_GetCurrentSymbolScope(void);
|
||||||
|
|||||||
@@ -13,6 +13,9 @@
|
|||||||
|
|
||||||
uint32_t calchash(const char *s);
|
uint32_t calchash(const char *s);
|
||||||
char const *print(int c);
|
char const *print(int c);
|
||||||
|
/*
|
||||||
|
* @return The number of bytes read, or 0 if invalid data was found
|
||||||
|
*/
|
||||||
size_t readUTF8Char(uint8_t *dest, char const *src);
|
size_t readUTF8Char(uint8_t *dest, char const *src);
|
||||||
|
|
||||||
#endif /* RGBDS_UTIL_H */
|
#endif /* RGBDS_UTIL_H */
|
||||||
|
|||||||
@@ -18,11 +18,13 @@ enum WarningID {
|
|||||||
WARNING_BUILTIN_ARG, /* Invalid args to builtins */
|
WARNING_BUILTIN_ARG, /* Invalid args to builtins */
|
||||||
WARNING_CHARMAP_REDEF, /* Charmap entry re-definition */
|
WARNING_CHARMAP_REDEF, /* Charmap entry re-definition */
|
||||||
WARNING_DIV, /* Division undefined behavior */
|
WARNING_DIV, /* Division undefined behavior */
|
||||||
WARNING_EMPTY_DATA_DIRECTIVE, /* `db`, `dw` or `dl` with no directive in ROM */
|
WARNING_EMPTY_DATA_DIRECTIVE, /* `db`, `dw` or `dl` directive without data in ROM */
|
||||||
WARNING_EMPTY_ENTRY, /* Empty entry in `db`, `dw` or `dl` */
|
WARNING_EMPTY_MACRO_ARG, /* Empty macro argument */
|
||||||
|
WARNING_EMPTY_STRRPL, /* Empty second argument in `STRRPL` */
|
||||||
WARNING_LARGE_CONSTANT, /* Constants too large */
|
WARNING_LARGE_CONSTANT, /* Constants too large */
|
||||||
WARNING_LONG_STR, /* String too long for internal buffers */
|
WARNING_LONG_STR, /* String too long for internal buffers */
|
||||||
WARNING_NESTED_COMMENT, /* Comment-start delimeter in a block comment */
|
WARNING_MACRO_SHIFT, /* Shift past available arguments in macro */
|
||||||
|
WARNING_NESTED_COMMENT, /* Comment-start delimiter in a block comment */
|
||||||
WARNING_OBSOLETE, /* Obsolete things */
|
WARNING_OBSOLETE, /* Obsolete things */
|
||||||
WARNING_SHIFT, /* Shifting undefined behavior */
|
WARNING_SHIFT, /* Shifting undefined behavior */
|
||||||
WARNING_SHIFT_AMOUNT, /* Strange shift amount */
|
WARNING_SHIFT_AMOUNT, /* Strange shift amount */
|
||||||
@@ -46,7 +48,7 @@ void processWarningFlag(char const *flag);
|
|||||||
* Used to warn the user about problems that don't prevent the generation of
|
* Used to warn the user about problems that don't prevent the generation of
|
||||||
* valid code.
|
* valid code.
|
||||||
*/
|
*/
|
||||||
void warning(enum WarningID id, const char *fmt, ...);
|
void warning(enum WarningID id, const char *fmt, ...) format_(printf, 2, 3);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Used for errors that compromise the whole assembly process by affecting the
|
* Used for errors that compromise the whole assembly process by affecting the
|
||||||
@@ -55,7 +57,7 @@ void warning(enum WarningID id, const char *fmt, ...);
|
|||||||
* It is also used when the assembler goes into an invalid state (for example,
|
* It is also used when the assembler goes into an invalid state (for example,
|
||||||
* when it fails to allocate memory).
|
* when it fails to allocate memory).
|
||||||
*/
|
*/
|
||||||
_Noreturn void fatalerror(const char *fmt, ...);
|
_Noreturn void fatalerror(const char *fmt, ...) format_(printf, 1, 2);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Used for errors that make it impossible to assemble correctly, but don't
|
* Used for errors that make it impossible to assemble correctly, but don't
|
||||||
@@ -63,6 +65,6 @@ _Noreturn void fatalerror(const char *fmt, ...);
|
|||||||
* get a list of all errors at the end, making it easier to fix all of them at
|
* get a list of all errors at the end, making it easier to fix all of them at
|
||||||
* once.
|
* once.
|
||||||
*/
|
*/
|
||||||
void error(const char *fmt, ...);
|
void error(const char *fmt, ...) format_(printf, 1, 2);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
4
include/extern/getopt.h
vendored
4
include/extern/getopt.h
vendored
@@ -26,8 +26,8 @@
|
|||||||
#ifndef RGBDS_EXTERN_GETOPT_H
|
#ifndef RGBDS_EXTERN_GETOPT_H
|
||||||
#define RGBDS_EXTERN_GETOPT_H
|
#define RGBDS_EXTERN_GETOPT_H
|
||||||
|
|
||||||
extern char *optarg;
|
extern char *musl_optarg;
|
||||||
extern int optind, opterr, optopt, optreset;
|
extern int musl_optind, musl_opterr, musl_optopt, musl_optreset;
|
||||||
|
|
||||||
struct option {
|
struct option {
|
||||||
const char *name;
|
const char *name;
|
||||||
|
|||||||
@@ -32,6 +32,58 @@
|
|||||||
static inline _Noreturn unreachable_(void) {}
|
static inline _Noreturn unreachable_(void) {}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Use builtins whenever possible, and shim them otherwise
|
||||||
|
#ifdef __GNUC__ // GCC or compatible
|
||||||
|
#define ctz __builtin_ctz
|
||||||
|
#define clz __builtin_clz
|
||||||
|
|
||||||
|
#elif defined(_MSC_VER)
|
||||||
|
#include <assert.h>
|
||||||
|
#include <intrin.h>
|
||||||
|
#pragma intrinsic(_BitScanReverse, _BitScanForward)
|
||||||
|
static inline int ctz(unsigned int x)
|
||||||
|
{
|
||||||
|
unsigned long cnt;
|
||||||
|
|
||||||
|
assert(x != 0);
|
||||||
|
_BitScanForward(&cnt, x);
|
||||||
|
return cnt;
|
||||||
|
}
|
||||||
|
static inline int clz(unsigned int x)
|
||||||
|
{
|
||||||
|
unsigned long cnt;
|
||||||
|
|
||||||
|
assert(x != 0);
|
||||||
|
_BitScanReverse(&cnt, x);
|
||||||
|
return 31 - cnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
// FIXME: these are rarely used, and need testing...
|
||||||
|
#include <limits.h>
|
||||||
|
static inline int ctz(unsigned int x)
|
||||||
|
{
|
||||||
|
int cnt = 0;
|
||||||
|
|
||||||
|
while (!(x & 1)) {
|
||||||
|
x >>= 1;
|
||||||
|
cnt++;
|
||||||
|
}
|
||||||
|
return cnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int clz(unsigned int x)
|
||||||
|
{
|
||||||
|
int cnt = 0;
|
||||||
|
|
||||||
|
while (x <= UINT_MAX / 2) {
|
||||||
|
x <<= 1;
|
||||||
|
cnt++;
|
||||||
|
}
|
||||||
|
return cnt;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Macros for stringification
|
// Macros for stringification
|
||||||
#define STR(x) #x
|
#define STR(x) #x
|
||||||
#define EXPAND_AND_STR(x) STR(x)
|
#define EXPAND_AND_STR(x) STR(x)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
#define RGBDS_OBJECT_VERSION_STRING "RGB%1u"
|
#define RGBDS_OBJECT_VERSION_STRING "RGB%1u"
|
||||||
#define RGBDS_OBJECT_VERSION_NUMBER 9U
|
#define RGBDS_OBJECT_VERSION_NUMBER 9U
|
||||||
#define RGBDS_OBJECT_REV 6U
|
#define RGBDS_OBJECT_REV 7U
|
||||||
|
|
||||||
enum AssertionType {
|
enum AssertionType {
|
||||||
ASSERT_WARN,
|
ASSERT_WARN,
|
||||||
@@ -29,6 +29,7 @@ enum RPNCommand {
|
|||||||
RPN_DIV = 0x03,
|
RPN_DIV = 0x03,
|
||||||
RPN_MOD = 0x04,
|
RPN_MOD = 0x04,
|
||||||
RPN_UNSUB = 0x05,
|
RPN_UNSUB = 0x05,
|
||||||
|
RPN_EXP = 0x06,
|
||||||
|
|
||||||
RPN_OR = 0x10,
|
RPN_OR = 0x10,
|
||||||
RPN_AND = 0x11,
|
RPN_AND = 0x11,
|
||||||
|
|||||||
20
include/opmath.h
Normal file
20
include/opmath.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 1997-2021, RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef RGBDS_OP_MATH_H
|
||||||
|
#define RGBDS_OP_MATH_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
int32_t op_divide(int32_t dividend, int32_t divisor);
|
||||||
|
int32_t op_modulo(int32_t dividend, int32_t divisor);
|
||||||
|
int32_t op_exponent(int32_t base, uint32_t power);
|
||||||
|
int32_t op_shift_left(int32_t value, int32_t amount);
|
||||||
|
int32_t op_shift_right(int32_t value, int32_t amount);
|
||||||
|
|
||||||
|
#endif /* RGBDS_OP_MATH_H */
|
||||||
@@ -34,9 +34,40 @@
|
|||||||
|
|
||||||
/* MSVC doesn't use POSIX types or defines for `read` */
|
/* MSVC doesn't use POSIX types or defines for `read` */
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
|
# include <io.h>
|
||||||
# define STDIN_FILENO 0
|
# define STDIN_FILENO 0
|
||||||
|
# define STDOUT_FILENO 1
|
||||||
|
# define STDERR_FILENO 2
|
||||||
# define ssize_t int
|
# define ssize_t int
|
||||||
# define SSIZE_MAX INT_MAX
|
# define SSIZE_MAX INT_MAX
|
||||||
|
#else
|
||||||
|
# include <fcntl.h>
|
||||||
|
# include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* MSVC doesn't support `[static N]` for array arguments from C99 */
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
# define MIN_NB_ELMS(N)
|
||||||
|
#else
|
||||||
|
# define MIN_NB_ELMS(N) static (N)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// MSVC uses a different name for O_RDWR, and needs an additional _O_BINARY flag
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
# include <fcntl.h>
|
||||||
|
# define O_RDWR _O_RDWR
|
||||||
|
# define S_ISREG(field) ((field) & _S_IFREG)
|
||||||
|
# define O_BINARY _O_BINARY
|
||||||
|
#elif !defined(O_BINARY) // Cross-compilers define O_BINARY
|
||||||
|
# define O_BINARY 0 // POSIX says we shouldn't care!
|
||||||
|
#endif // _MSC_VER
|
||||||
|
|
||||||
|
// Windows has stdin and stdout open as text by default, which we may not want
|
||||||
|
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||||
|
# include <io.h>
|
||||||
|
# define setmode(fd, mode) _setmode(fd, mode)
|
||||||
|
#else
|
||||||
|
# define setmode(fd, mode) ((void)0)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif /* RGBDS_PLATFORM_H */
|
#endif /* RGBDS_PLATFORM_H */
|
||||||
|
|||||||
@@ -9,9 +9,10 @@
|
|||||||
#ifndef EXTERN_VERSION_H
|
#ifndef EXTERN_VERSION_H
|
||||||
#define EXTERN_VERSION_H
|
#define EXTERN_VERSION_H
|
||||||
|
|
||||||
#define PACKAGE_VERSION_MAJOR (0)
|
#define PACKAGE_VERSION_MAJOR 0
|
||||||
#define PACKAGE_VERSION_MINOR (4)
|
#define PACKAGE_VERSION_MINOR 5
|
||||||
#define PACKAGE_VERSION_PATCH (2)
|
#define PACKAGE_VERSION_PATCH 0
|
||||||
|
#define PACKAGE_VERSION_RC 1
|
||||||
|
|
||||||
const char *get_package_version_string(void);
|
const char *get_package_version_string(void);
|
||||||
|
|
||||||
|
|||||||
@@ -12,29 +12,45 @@ set(common_src
|
|||||||
"version.c"
|
"version.c"
|
||||||
)
|
)
|
||||||
|
|
||||||
find_package(BISON REQUIRED)
|
|
||||||
find_package(PkgConfig)
|
find_package(PkgConfig)
|
||||||
|
if(MSVC OR NOT PKG_CONFIG_FOUND)
|
||||||
if(NOT PKG_CONFIG_FOUND)
|
|
||||||
# fallback to find_package
|
# fallback to find_package
|
||||||
find_package(PNG REQUIRED)
|
find_package(PNG REQUIRED)
|
||||||
else()
|
else()
|
||||||
pkg_check_modules(LIBPNG REQUIRED libpng)
|
pkg_check_modules(LIBPNG REQUIRED libpng)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
find_package(BISON REQUIRED)
|
||||||
|
set(BISON_FLAGS "-Wall")
|
||||||
|
# Set sompe optimization flags on versions that support them
|
||||||
|
if(BISON_VERSION VERSION_GREATER_EQUAL "3.5")
|
||||||
|
set(BISON_FLAGS "${BISON_FLAGS} -Dapi.token.raw=true")
|
||||||
|
endif()
|
||||||
|
if(BISON_VERSION VERSION_GREATER_EQUAL "3.6")
|
||||||
|
set(BISON_FLAGS "${BISON_FLAGS} -Dparse.error=detailed")
|
||||||
|
elseif(BISON_VERSION VERSION_GREATER_EQUAL "3.0")
|
||||||
|
set(BISON_FLAGS "${BISON_FLAGS} -Dparse.error=verbose")
|
||||||
|
endif()
|
||||||
|
if(BISON_VERSION VERSION_GREATER_EQUAL "3.0")
|
||||||
|
set(BISON_FLAGS "${BISON_FLAGS} -Dparse.lac=full")
|
||||||
|
set(BISON_FLAGS "${BISON_FLAGS} -Dlr.type=ielr")
|
||||||
|
endif()
|
||||||
BISON_TARGET(PARSER "asm/parser.y"
|
BISON_TARGET(PARSER "asm/parser.y"
|
||||||
"${PROJECT_SOURCE_DIR}/src/asm/parser.c"
|
"${PROJECT_SOURCE_DIR}/src/asm/parser.c"
|
||||||
|
COMPILE_FLAGS "${BISON_FLAGS}"
|
||||||
DEFINES_FILE "${PROJECT_SOURCE_DIR}/src/asm/parser.h"
|
DEFINES_FILE "${PROJECT_SOURCE_DIR}/src/asm/parser.h"
|
||||||
)
|
)
|
||||||
|
|
||||||
set(rgbasm_src
|
set(rgbasm_src
|
||||||
"${BISON_PARSER_OUTPUT_SOURCE}"
|
"${BISON_PARSER_OUTPUT_SOURCE}"
|
||||||
"asm/charmap.c"
|
"asm/charmap.c"
|
||||||
|
"asm/fixpoint.c"
|
||||||
|
"asm/format.c"
|
||||||
"asm/fstack.c"
|
"asm/fstack.c"
|
||||||
"asm/lexer.c"
|
"asm/lexer.c"
|
||||||
"asm/macro.c"
|
"asm/macro.c"
|
||||||
"asm/main.c"
|
"asm/main.c"
|
||||||
"asm/math.c"
|
"asm/opt.c"
|
||||||
"asm/output.c"
|
"asm/output.c"
|
||||||
"asm/rpn.c"
|
"asm/rpn.c"
|
||||||
"asm/section.c"
|
"asm/section.c"
|
||||||
@@ -44,6 +60,7 @@ set(rgbasm_src
|
|||||||
"extern/utf8decoder.c"
|
"extern/utf8decoder.c"
|
||||||
"hashmap.c"
|
"hashmap.c"
|
||||||
"linkdefs.c"
|
"linkdefs.c"
|
||||||
|
"opmath.c"
|
||||||
)
|
)
|
||||||
|
|
||||||
set(rgbfix_src
|
set(rgbfix_src
|
||||||
@@ -67,6 +84,7 @@ set(rgblink_src
|
|||||||
"link/symbol.c"
|
"link/symbol.c"
|
||||||
"hashmap.c"
|
"hashmap.c"
|
||||||
"linkdefs.c"
|
"linkdefs.c"
|
||||||
|
"opmath.c"
|
||||||
)
|
)
|
||||||
|
|
||||||
foreach(PROG "asm" "fix" "gfx" "link")
|
foreach(PROG "asm" "fix" "gfx" "link")
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "asm/asm.h"
|
|
||||||
#include "asm/charmap.h"
|
#include "asm/charmap.h"
|
||||||
#include "asm/main.h"
|
#include "asm/main.h"
|
||||||
#include "asm/output.h"
|
#include "asm/output.h"
|
||||||
@@ -183,7 +182,7 @@ void charmap_Add(char *mapping, uint8_t value)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (node->isTerminal)
|
if (node->isTerminal)
|
||||||
warning(WARNING_CHARMAP_REDEF, "Overriding charmap mapping");
|
warning(WARNING_CHARMAP_REDEF, "Overriding charmap mapping\n");
|
||||||
|
|
||||||
node->isTerminal = true;
|
node->isTerminal = true;
|
||||||
node->value = value;
|
node->value = value;
|
||||||
@@ -229,6 +228,10 @@ size_t charmap_Convert(char const *input, uint8_t *output)
|
|||||||
} else if (*input) { /* No match found */
|
} else if (*input) { /* No match found */
|
||||||
size_t codepointLen = readUTF8Char(output, input);
|
size_t codepointLen = readUTF8Char(output, input);
|
||||||
|
|
||||||
|
if (codepointLen == 0) {
|
||||||
|
error("Input string is not valid UTF-8!\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
input += codepointLen; /* OK because UTF-8 has no NUL in multi-byte chars */
|
input += codepointLen; /* OK because UTF-8 has no NUL in multi-byte chars */
|
||||||
output += codepointLen;
|
output += codepointLen;
|
||||||
outputLen += codepointLen;
|
outputLen += codepointLen;
|
||||||
|
|||||||
170
src/asm/fixpoint.c
Normal file
170
src/asm/fixpoint.c
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 1997-2021, Carsten Sorensen and RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fixed-point math routines
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "asm/fixpoint.h"
|
||||||
|
#include "asm/symbol.h"
|
||||||
|
#include "asm/warning.h"
|
||||||
|
|
||||||
|
#define fix2double(i) ((double)((i) / 65536.0))
|
||||||
|
#define double2fix(d) ((int32_t)round((d) * 65536.0))
|
||||||
|
|
||||||
|
// pi radians == 32768 fixed-point "degrees"
|
||||||
|
#define fdeg2rad(f) ((f) * (M_PI / 32768.0))
|
||||||
|
#define rad2fdeg(r) ((r) * (32768.0 / M_PI))
|
||||||
|
|
||||||
|
#ifndef M_PI
|
||||||
|
#define M_PI 3.14159265358979323846
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the _PI symbol value
|
||||||
|
*/
|
||||||
|
int32_t fix_Callback_PI(void)
|
||||||
|
{
|
||||||
|
warning(WARNING_OBSOLETE, "`_PI` is deprecated; use 3.14159\n");
|
||||||
|
|
||||||
|
return double2fix(M_PI);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print a fixed point value
|
||||||
|
*/
|
||||||
|
void fix_Print(int32_t i)
|
||||||
|
{
|
||||||
|
uint32_t u = i;
|
||||||
|
const char *sign = "";
|
||||||
|
|
||||||
|
if (i < 0) {
|
||||||
|
u = -u;
|
||||||
|
sign = "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("%s%" PRIu32 ".%05" PRIu32, sign, u >> 16,
|
||||||
|
((uint32_t)(fix2double(u) * 100000 + 0.5)) % 100000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate sine
|
||||||
|
*/
|
||||||
|
int32_t fix_Sin(int32_t i)
|
||||||
|
{
|
||||||
|
return double2fix(sin(fdeg2rad(fix2double(i))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate cosine
|
||||||
|
*/
|
||||||
|
int32_t fix_Cos(int32_t i)
|
||||||
|
{
|
||||||
|
return double2fix(cos(fdeg2rad(fix2double(i))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate tangent
|
||||||
|
*/
|
||||||
|
int32_t fix_Tan(int32_t i)
|
||||||
|
{
|
||||||
|
return double2fix(tan(fdeg2rad(fix2double(i))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate arcsine
|
||||||
|
*/
|
||||||
|
int32_t fix_ASin(int32_t i)
|
||||||
|
{
|
||||||
|
return double2fix(rad2fdeg(asin(fix2double(i))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate arccosine
|
||||||
|
*/
|
||||||
|
int32_t fix_ACos(int32_t i)
|
||||||
|
{
|
||||||
|
return double2fix(rad2fdeg(acos(fix2double(i))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate arctangent
|
||||||
|
*/
|
||||||
|
int32_t fix_ATan(int32_t i)
|
||||||
|
{
|
||||||
|
return double2fix(rad2fdeg(atan(fix2double(i))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate atan2
|
||||||
|
*/
|
||||||
|
int32_t fix_ATan2(int32_t i, int32_t j)
|
||||||
|
{
|
||||||
|
return double2fix(rad2fdeg(atan2(fix2double(i), fix2double(j))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Multiplication
|
||||||
|
*/
|
||||||
|
int32_t fix_Mul(int32_t i, int32_t j)
|
||||||
|
{
|
||||||
|
return double2fix(fix2double(i) * fix2double(j));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Division
|
||||||
|
*/
|
||||||
|
int32_t fix_Div(int32_t i, int32_t j)
|
||||||
|
{
|
||||||
|
return double2fix(fix2double(i) / fix2double(j));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Power
|
||||||
|
*/
|
||||||
|
int32_t fix_Pow(int32_t i, int32_t j)
|
||||||
|
{
|
||||||
|
return double2fix(pow(fix2double(i), fix2double(j)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Logarithm
|
||||||
|
*/
|
||||||
|
int32_t fix_Log(int32_t i, int32_t j)
|
||||||
|
{
|
||||||
|
return double2fix(log(fix2double(i)) / log(fix2double(j)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Round
|
||||||
|
*/
|
||||||
|
int32_t fix_Round(int32_t i)
|
||||||
|
{
|
||||||
|
return double2fix(round(fix2double(i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ceil
|
||||||
|
*/
|
||||||
|
int32_t fix_Ceil(int32_t i)
|
||||||
|
{
|
||||||
|
return double2fix(ceil(fix2double(i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Floor
|
||||||
|
*/
|
||||||
|
int32_t fix_Floor(int32_t i)
|
||||||
|
{
|
||||||
|
return double2fix(floor(fix2double(i)));
|
||||||
|
}
|
||||||
299
src/asm/format.c
Normal file
299
src/asm/format.c
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020, RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "asm/format.h"
|
||||||
|
#include "asm/warning.h"
|
||||||
|
|
||||||
|
struct FormatSpec fmt_NewSpec(void)
|
||||||
|
{
|
||||||
|
struct FormatSpec fmt = {0};
|
||||||
|
|
||||||
|
return fmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fmt_IsEmpty(struct FormatSpec const *fmt)
|
||||||
|
{
|
||||||
|
return !fmt->state;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fmt_IsValid(struct FormatSpec const *fmt)
|
||||||
|
{
|
||||||
|
return fmt->valid || fmt->state == FORMAT_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fmt_IsFinished(struct FormatSpec const *fmt)
|
||||||
|
{
|
||||||
|
return fmt->state >= FORMAT_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fmt_UseCharacter(struct FormatSpec *fmt, int c)
|
||||||
|
{
|
||||||
|
if (fmt->state == FORMAT_INVALID)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
/* sign */
|
||||||
|
case ' ':
|
||||||
|
case '+':
|
||||||
|
if (fmt->state > FORMAT_SIGN)
|
||||||
|
goto invalid;
|
||||||
|
fmt->state = FORMAT_PREFIX;
|
||||||
|
fmt->sign = c;
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* prefix */
|
||||||
|
case '#':
|
||||||
|
if (fmt->state > FORMAT_PREFIX)
|
||||||
|
goto invalid;
|
||||||
|
fmt->state = FORMAT_ALIGN;
|
||||||
|
fmt->prefix = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* align */
|
||||||
|
case '-':
|
||||||
|
if (fmt->state > FORMAT_ALIGN)
|
||||||
|
goto invalid;
|
||||||
|
fmt->state = FORMAT_WIDTH;
|
||||||
|
fmt->alignLeft = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* pad and width */
|
||||||
|
case '0':
|
||||||
|
if (fmt->state < FORMAT_WIDTH)
|
||||||
|
fmt->padZero = true;
|
||||||
|
/* fallthrough */
|
||||||
|
case '1':
|
||||||
|
case '2':
|
||||||
|
case '3':
|
||||||
|
case '4':
|
||||||
|
case '5':
|
||||||
|
case '6':
|
||||||
|
case '7':
|
||||||
|
case '8':
|
||||||
|
case '9':
|
||||||
|
if (fmt->state < FORMAT_WIDTH) {
|
||||||
|
fmt->state = FORMAT_WIDTH;
|
||||||
|
fmt->width = c - '0';
|
||||||
|
} else if (fmt->state == FORMAT_WIDTH) {
|
||||||
|
fmt->width = fmt->width * 10 + (c - '0');
|
||||||
|
} else if (fmt->state == FORMAT_FRAC) {
|
||||||
|
fmt->fracWidth = fmt->fracWidth * 10 + (c - '0');
|
||||||
|
} else {
|
||||||
|
goto invalid;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '.':
|
||||||
|
if (fmt->state > FORMAT_WIDTH)
|
||||||
|
goto invalid;
|
||||||
|
fmt->state = FORMAT_FRAC;
|
||||||
|
fmt->hasFrac = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* type */
|
||||||
|
case 'd':
|
||||||
|
case 'u':
|
||||||
|
case 'X':
|
||||||
|
case 'x':
|
||||||
|
case 'b':
|
||||||
|
case 'o':
|
||||||
|
case 'f':
|
||||||
|
case 's':
|
||||||
|
if (fmt->state >= FORMAT_DONE)
|
||||||
|
goto invalid;
|
||||||
|
fmt->state = FORMAT_DONE;
|
||||||
|
fmt->valid = true;
|
||||||
|
fmt->type = c;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
invalid:
|
||||||
|
fmt->state = FORMAT_INVALID;
|
||||||
|
fmt->valid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void fmt_FinishCharacters(struct FormatSpec *fmt)
|
||||||
|
{
|
||||||
|
if (!fmt_IsValid(fmt))
|
||||||
|
fmt->state = FORMAT_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fmt_PrintString(char *buf, size_t bufLen, struct FormatSpec const *fmt, char const *value)
|
||||||
|
{
|
||||||
|
if (fmt->sign)
|
||||||
|
error("Formatting string with sign flag '%c'\n", fmt->sign);
|
||||||
|
if (fmt->prefix)
|
||||||
|
error("Formatting string with prefix flag '#'\n");
|
||||||
|
if (fmt->padZero)
|
||||||
|
error("Formatting string with padding flag '0'\n");
|
||||||
|
if (fmt->hasFrac)
|
||||||
|
error("Formatting string with fractional width\n");
|
||||||
|
if (fmt->type != 's')
|
||||||
|
error("Formatting string as type '%c'\n", fmt->type);
|
||||||
|
|
||||||
|
size_t len = strlen(value);
|
||||||
|
size_t totalLen = fmt->width > len ? fmt->width : len;
|
||||||
|
|
||||||
|
if (totalLen + 1 > bufLen) /* bufLen includes terminator */
|
||||||
|
error("Formatted string value too long\n");
|
||||||
|
|
||||||
|
size_t padLen = fmt->width > len ? fmt->width - len : 0;
|
||||||
|
|
||||||
|
if (fmt->alignLeft) {
|
||||||
|
strncpy(buf, value, len < bufLen ? len : bufLen);
|
||||||
|
for (size_t i = 0; i < totalLen && len + i < bufLen; i++)
|
||||||
|
buf[len + i] = ' ';
|
||||||
|
} else {
|
||||||
|
for (size_t i = 0; i < padLen && i < bufLen; i++)
|
||||||
|
buf[i] = ' ';
|
||||||
|
if (bufLen > padLen)
|
||||||
|
strncpy(buf + padLen, value, bufLen - padLen - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[totalLen] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uint32_t value)
|
||||||
|
{
|
||||||
|
if (fmt->type != 'X' && fmt->type != 'x' && fmt->type != 'b' && fmt->type != 'o'
|
||||||
|
&& fmt->prefix)
|
||||||
|
error("Formatting type '%c' with prefix flag '#'\n", fmt->type);
|
||||||
|
if (fmt->type != 'f' && fmt->hasFrac)
|
||||||
|
error("Formatting type '%c' with fractional width\n", fmt->type);
|
||||||
|
if (fmt->type == 's')
|
||||||
|
error("Formatting number as type 's'\n");
|
||||||
|
|
||||||
|
char sign = fmt->sign; /* 0 or ' ' or '+' */
|
||||||
|
|
||||||
|
if (fmt->type == 'd' || fmt->type == 'f') {
|
||||||
|
int32_t v = value;
|
||||||
|
|
||||||
|
if (v < 0 && v != INT32_MIN) {
|
||||||
|
sign = '-';
|
||||||
|
value = -v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char prefix = !fmt->prefix ? 0
|
||||||
|
: fmt->type == 'X' ? '$'
|
||||||
|
: fmt->type == 'x' ? '$'
|
||||||
|
: fmt->type == 'b' ? '%'
|
||||||
|
: fmt->type == 'o' ? '&'
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
char valueBuf[262]; /* Max 5 digits + decimal + 255 fraction digits + terminator */
|
||||||
|
|
||||||
|
if (fmt->type == 'b') {
|
||||||
|
/* Special case for binary */
|
||||||
|
char *ptr = valueBuf;
|
||||||
|
|
||||||
|
do {
|
||||||
|
*ptr++ = (value & 1) + '0';
|
||||||
|
value >>= 1;
|
||||||
|
} while (value);
|
||||||
|
|
||||||
|
*ptr = '\0';
|
||||||
|
|
||||||
|
/* Reverse the digits */
|
||||||
|
size_t valueLen = ptr - valueBuf;
|
||||||
|
|
||||||
|
for (size_t i = 0, j = valueLen - 1; i < j; i++, j--) {
|
||||||
|
char c = valueBuf[i];
|
||||||
|
|
||||||
|
valueBuf[i] = valueBuf[j];
|
||||||
|
valueBuf[j] = c;
|
||||||
|
}
|
||||||
|
} else if (fmt->type == 'f') {
|
||||||
|
/* Special case for fixed-point */
|
||||||
|
|
||||||
|
/* Default fractional width (C's is 6 for "%f"; here 5 is enough) */
|
||||||
|
uint8_t fracWidth = fmt->hasFrac ? fmt->fracWidth : 5;
|
||||||
|
|
||||||
|
if (fracWidth) {
|
||||||
|
char spec[16]; /* Max "%" + 5-char PRIu32 + ".%0255.f" + terminator */
|
||||||
|
|
||||||
|
snprintf(spec, sizeof(spec), "%%" PRIu32 ".%%0%d.f", fracWidth);
|
||||||
|
snprintf(valueBuf, sizeof(valueBuf), spec, value >> 16,
|
||||||
|
(value % 65536) / 65536.0 * pow(10, fracWidth) + 0.5);
|
||||||
|
} else {
|
||||||
|
snprintf(valueBuf, sizeof(valueBuf), "%" PRIu32, value >> 16);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
char const *spec = fmt->type == 'd' ? "%" PRId32
|
||||||
|
: fmt->type == 'u' ? "%" PRIu32
|
||||||
|
: fmt->type == 'X' ? "%" PRIX32
|
||||||
|
: fmt->type == 'x' ? "%" PRIx32
|
||||||
|
: fmt->type == 'o' ? "%" PRIo32
|
||||||
|
: "%" PRId32;
|
||||||
|
|
||||||
|
snprintf(valueBuf, sizeof(valueBuf), spec, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t len = strlen(valueBuf);
|
||||||
|
size_t numLen = len;
|
||||||
|
|
||||||
|
if (sign)
|
||||||
|
numLen++;
|
||||||
|
if (prefix)
|
||||||
|
numLen++;
|
||||||
|
|
||||||
|
size_t totalLen = fmt->width > numLen ? fmt->width : numLen;
|
||||||
|
|
||||||
|
if (totalLen + 1 > bufLen) /* bufLen includes terminator */
|
||||||
|
error("Formatted numeric value too long\n");
|
||||||
|
|
||||||
|
size_t padLen = fmt->width > numLen ? fmt->width - numLen : 0;
|
||||||
|
|
||||||
|
if (fmt->alignLeft) {
|
||||||
|
size_t pos = 0;
|
||||||
|
|
||||||
|
if (sign && pos < bufLen)
|
||||||
|
buf[pos++] = sign;
|
||||||
|
if (prefix && pos < bufLen)
|
||||||
|
buf[pos++] = prefix;
|
||||||
|
|
||||||
|
strcpy(buf + pos, valueBuf);
|
||||||
|
pos += len;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < totalLen && pos + i < bufLen; i++)
|
||||||
|
buf[pos + i] = ' ';
|
||||||
|
} else {
|
||||||
|
size_t pos = 0;
|
||||||
|
|
||||||
|
if (fmt->padZero) {
|
||||||
|
/* sign, then prefix, then zero padding */
|
||||||
|
if (sign && pos < bufLen)
|
||||||
|
buf[pos++] = sign;
|
||||||
|
if (prefix && pos < bufLen)
|
||||||
|
buf[pos++] = prefix;
|
||||||
|
for (size_t i = 0; i < padLen && pos < bufLen; i++)
|
||||||
|
buf[pos++] = '0';
|
||||||
|
} else {
|
||||||
|
/* space padding, then sign, then prefix */
|
||||||
|
for (size_t i = 0; i < padLen && pos < bufLen; i++)
|
||||||
|
buf[pos++] = ' ';
|
||||||
|
if (sign && pos < bufLen)
|
||||||
|
buf[pos++] = sign;
|
||||||
|
if (prefix && pos < bufLen)
|
||||||
|
buf[pos++] = prefix;
|
||||||
|
}
|
||||||
|
if (bufLen > pos)
|
||||||
|
strcpy(buf + pos, valueBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[totalLen] = '\0';
|
||||||
|
}
|
||||||
119
src/asm/fstack.c
119
src/asm/fstack.c
@@ -21,8 +21,10 @@
|
|||||||
#include "asm/warning.h"
|
#include "asm/warning.h"
|
||||||
#include "platform.h" /* S_ISDIR (stat macro) */
|
#include "platform.h" /* S_ISDIR (stat macro) */
|
||||||
|
|
||||||
|
#define MAXINCPATHS 128
|
||||||
|
|
||||||
#ifdef LEXER_DEBUG
|
#ifdef LEXER_DEBUG
|
||||||
#define dbgPrint(...) fprintf(stderr, "[lexer] " __VA_ARGS__)
|
#define dbgPrint(...) fprintf(stderr, "[fstack] " __VA_ARGS__)
|
||||||
#else
|
#else
|
||||||
#define dbgPrint(...)
|
#define dbgPrint(...)
|
||||||
#endif
|
#endif
|
||||||
@@ -34,6 +36,9 @@ struct Context {
|
|||||||
uint32_t uniqueID;
|
uint32_t uniqueID;
|
||||||
struct MacroArgs *macroArgs; /* Macro args are *saved* here */
|
struct MacroArgs *macroArgs; /* Macro args are *saved* here */
|
||||||
uint32_t nbReptIters;
|
uint32_t nbReptIters;
|
||||||
|
int32_t forValue;
|
||||||
|
int32_t forStep;
|
||||||
|
char *forName;
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct Context *contextStack;
|
static struct Context *contextStack;
|
||||||
@@ -44,7 +49,7 @@ size_t nMaxRecursionDepth;
|
|||||||
static unsigned int nbIncPaths = 0;
|
static unsigned int nbIncPaths = 0;
|
||||||
static char const *includePaths[MAXINCPATHS];
|
static char const *includePaths[MAXINCPATHS];
|
||||||
|
|
||||||
char const *dumpNodeAndParents(struct FileStackNode const *node)
|
static const char *dumpNodeAndParents(struct FileStackNode const *node)
|
||||||
{
|
{
|
||||||
char const *name;
|
char const *name;
|
||||||
|
|
||||||
@@ -85,6 +90,9 @@ void fstk_DumpCurrent(void)
|
|||||||
|
|
||||||
struct FileStackNode *fstk_GetFileStack(void)
|
struct FileStackNode *fstk_GetFileStack(void)
|
||||||
{
|
{
|
||||||
|
if (!contextStack)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
struct FileStackNode *node = contextStack->fileInfo;
|
struct FileStackNode *node = contextStack->fileInfo;
|
||||||
|
|
||||||
/* Mark node and all of its parents as referenced if not already so they don't get freed */
|
/* Mark node and all of its parents as referenced if not already so they don't get freed */
|
||||||
@@ -197,6 +205,12 @@ bool fstk_FindFile(char const *path, char **fullPath, size_t *size)
|
|||||||
|
|
||||||
bool yywrap(void)
|
bool yywrap(void)
|
||||||
{
|
{
|
||||||
|
uint32_t nIFDepth = lexer_GetIFDepth();
|
||||||
|
|
||||||
|
if (nIFDepth != 0)
|
||||||
|
fatalerror("Ended block with %" PRIu32 " unterminated IF construct%s\n",
|
||||||
|
nIFDepth, nIFDepth == 1 ? "" : "s");
|
||||||
|
|
||||||
if (contextStack->fileInfo->type == NODE_REPT) { /* The context is a REPT block, which may loop */
|
if (contextStack->fileInfo->type == NODE_REPT) { /* The context is a REPT block, which may loop */
|
||||||
struct FileStackReptNode *fileInfo = (struct FileStackReptNode *)contextStack->fileInfo;
|
struct FileStackReptNode *fileInfo = (struct FileStackReptNode *)contextStack->fileInfo;
|
||||||
|
|
||||||
@@ -216,6 +230,17 @@ bool yywrap(void)
|
|||||||
contextStack->fileInfo = (struct FileStackNode *)fileInfo;
|
contextStack->fileInfo = (struct FileStackNode *)fileInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If this is a FOR, update the symbol value */
|
||||||
|
if (contextStack->forName && fileInfo->iters[0] <= contextStack->nbReptIters) {
|
||||||
|
contextStack->forValue += contextStack->forStep;
|
||||||
|
struct Symbol *sym = sym_AddSet(contextStack->forName,
|
||||||
|
contextStack->forValue);
|
||||||
|
|
||||||
|
/* This error message will refer to the current iteration */
|
||||||
|
if (sym->type != SYM_SET)
|
||||||
|
fatalerror("Failed to update FOR symbol value\n");
|
||||||
|
}
|
||||||
|
/* Advance to the next iteration */
|
||||||
fileInfo->iters[0]++;
|
fileInfo->iters[0]++;
|
||||||
/* If this wasn't the last iteration, wrap instead of popping */
|
/* If this wasn't the last iteration, wrap instead of popping */
|
||||||
if (fileInfo->iters[0] <= contextStack->nbReptIters) {
|
if (fileInfo->iters[0] <= contextStack->nbReptIters) {
|
||||||
@@ -231,17 +256,20 @@ bool yywrap(void)
|
|||||||
struct Context *context = contextStack;
|
struct Context *context = contextStack;
|
||||||
|
|
||||||
contextStack = contextStack->parent;
|
contextStack = contextStack->parent;
|
||||||
|
assert(contextDepth != 0); // This is never supposed to underflow
|
||||||
contextDepth--;
|
contextDepth--;
|
||||||
|
|
||||||
lexer_DeleteState(context->lexerState);
|
lexer_DeleteState(context->lexerState);
|
||||||
/* Restore args if a macro (not REPT) saved them */
|
/* Restore args if a macro (not REPT) saved them */
|
||||||
if (context->fileInfo->type == NODE_MACRO) {
|
if (context->fileInfo->type == NODE_MACRO) {
|
||||||
dbgPrint("Restoring macro args %p\n", contextStack->macroArgs);
|
dbgPrint("Restoring macro args %p\n", (void *)contextStack->macroArgs);
|
||||||
macro_UseNewArgs(contextStack->macroArgs);
|
macro_UseNewArgs(contextStack->macroArgs);
|
||||||
}
|
}
|
||||||
/* Free the file stack node */
|
/* Free the file stack node */
|
||||||
if (!context->fileInfo->referenced)
|
if (!context->fileInfo->referenced)
|
||||||
free(context->fileInfo);
|
free(context->fileInfo);
|
||||||
|
/* Free the FOR symbol name */
|
||||||
|
free(context->forName);
|
||||||
/* Free the entry and make its parent the current entry */
|
/* Free the entry and make its parent the current entry */
|
||||||
free(context);
|
free(context);
|
||||||
|
|
||||||
@@ -253,6 +281,7 @@ bool yywrap(void)
|
|||||||
/*
|
/*
|
||||||
* Make sure not to switch the lexer state before calling this, so the saved line no is correct
|
* Make sure not to switch the lexer state before calling this, so the saved line no is correct
|
||||||
* BE CAREFUL!! This modifies the file stack directly, you should have set up the file info first
|
* BE CAREFUL!! This modifies the file stack directly, you should have set up the file info first
|
||||||
|
* Callers should set contextStack->lexerState after this so it is not NULL
|
||||||
*/
|
*/
|
||||||
static void newContext(struct FileStackNode *fileInfo)
|
static void newContext(struct FileStackNode *fileInfo)
|
||||||
{
|
{
|
||||||
@@ -267,13 +296,13 @@ static void newContext(struct FileStackNode *fileInfo)
|
|||||||
fileInfo->referenced = false;
|
fileInfo->referenced = false;
|
||||||
fileInfo->lineNo = lexer_GetLineNo();
|
fileInfo->lineNo = lexer_GetLineNo();
|
||||||
context->fileInfo = fileInfo;
|
context->fileInfo = fileInfo;
|
||||||
|
context->forName = NULL;
|
||||||
/*
|
/*
|
||||||
* Link new entry to its parent so it's reachable later
|
* Link new entry to its parent so it's reachable later
|
||||||
* ERRORS SHOULD NOT OCCUR AFTER THIS!!
|
* ERRORS SHOULD NOT OCCUR AFTER THIS!!
|
||||||
*/
|
*/
|
||||||
context->parent = contextStack;
|
context->parent = contextStack;
|
||||||
contextStack = context;
|
contextStack = context;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void fstk_RunInclude(char const *path)
|
void fstk_RunInclude(char const *path)
|
||||||
@@ -376,9 +405,8 @@ void fstk_RunMacro(char const *macroName, struct MacroArgs *args)
|
|||||||
memcpy(dest, macro->name, macroNameLen + 1);
|
memcpy(dest, macro->name, macroNameLen + 1);
|
||||||
|
|
||||||
newContext((struct FileStackNode *)fileInfo);
|
newContext((struct FileStackNode *)fileInfo);
|
||||||
/* Line minus 1 because buffer begins with a newline */
|
|
||||||
contextStack->lexerState = lexer_OpenFileView(macro->macro, macro->macroSize,
|
contextStack->lexerState = lexer_OpenFileView(macro->macro, macro->macroSize,
|
||||||
macro->fileLine - 1);
|
macro->fileLine);
|
||||||
if (!contextStack->lexerState)
|
if (!contextStack->lexerState)
|
||||||
fatalerror("Failed to set up lexer for macro invocation\n");
|
fatalerror("Failed to set up lexer for macro invocation\n");
|
||||||
lexer_SetStateAtEOL(contextStack->lexerState);
|
lexer_SetStateAtEOL(contextStack->lexerState);
|
||||||
@@ -386,12 +414,8 @@ void fstk_RunMacro(char const *macroName, struct MacroArgs *args)
|
|||||||
macro_UseNewArgs(args);
|
macro_UseNewArgs(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
void fstk_RunRept(uint32_t count, int32_t reptLineNo, char *body, size_t size)
|
static bool newReptContext(int32_t reptLineNo, char *body, size_t size)
|
||||||
{
|
{
|
||||||
dbgPrint("Running REPT(%" PRIu32 ")\n", count);
|
|
||||||
if (count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
uint32_t reptDepth = contextStack->fileInfo->type == NODE_REPT
|
uint32_t reptDepth = contextStack->fileInfo->type == NODE_REPT
|
||||||
? ((struct FileStackReptNode *)contextStack->fileInfo)->reptDepth
|
? ((struct FileStackReptNode *)contextStack->fileInfo)->reptDepth
|
||||||
: 0;
|
: 0;
|
||||||
@@ -400,7 +424,7 @@ void fstk_RunRept(uint32_t count, int32_t reptLineNo, char *body, size_t size)
|
|||||||
|
|
||||||
if (!fileInfo) {
|
if (!fileInfo) {
|
||||||
error("Failed to alloc file info for REPT: %s\n", strerror(errno));
|
error("Failed to alloc file info for REPT: %s\n", strerror(errno));
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
fileInfo->node.type = NODE_REPT;
|
fileInfo->node.type = NODE_REPT;
|
||||||
fileInfo->reptDepth = reptDepth + 1;
|
fileInfo->reptDepth = reptDepth + 1;
|
||||||
@@ -417,11 +441,75 @@ void fstk_RunRept(uint32_t count, int32_t reptLineNo, char *body, size_t size)
|
|||||||
|
|
||||||
contextStack->lexerState = lexer_OpenFileView(body, size, reptLineNo);
|
contextStack->lexerState = lexer_OpenFileView(body, size, reptLineNo);
|
||||||
if (!contextStack->lexerState)
|
if (!contextStack->lexerState)
|
||||||
fatalerror("Failed to set up lexer for rept block\n");
|
fatalerror("Failed to set up lexer for REPT block\n");
|
||||||
lexer_SetStateAtEOL(contextStack->lexerState);
|
lexer_SetStateAtEOL(contextStack->lexerState);
|
||||||
contextStack->uniqueID = macro_UseNewUniqueID();
|
contextStack->uniqueID = macro_UseNewUniqueID();
|
||||||
contextStack->nbReptIters = count;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fstk_RunRept(uint32_t count, int32_t reptLineNo, char *body, size_t size)
|
||||||
|
{
|
||||||
|
dbgPrint("Running REPT(%" PRIu32 ")\n", count);
|
||||||
|
|
||||||
|
if (count == 0)
|
||||||
|
return;
|
||||||
|
if (!newReptContext(reptLineNo, body, size))
|
||||||
|
return;
|
||||||
|
|
||||||
|
contextStack->nbReptIters = count;
|
||||||
|
contextStack->forName = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step,
|
||||||
|
int32_t reptLineNo, char *body, size_t size)
|
||||||
|
{
|
||||||
|
dbgPrint("Running FOR(\"%s\", %" PRId32 ", %" PRId32 ", %" PRId32 ")\n",
|
||||||
|
symName, start, stop, step);
|
||||||
|
|
||||||
|
struct Symbol *sym = sym_AddSet(symName, start);
|
||||||
|
|
||||||
|
if (sym->type != SYM_SET)
|
||||||
|
return;
|
||||||
|
|
||||||
|
uint32_t count = 0;
|
||||||
|
|
||||||
|
if (step > 0 && start < stop)
|
||||||
|
count = (stop - start - 1) / step + 1;
|
||||||
|
else if (step < 0 && stop < start)
|
||||||
|
count = (start - stop - 1) / -step + 1;
|
||||||
|
else if (step == 0)
|
||||||
|
error("FOR cannot have a step value of 0\n");
|
||||||
|
|
||||||
|
if (count == 0)
|
||||||
|
return;
|
||||||
|
if (!newReptContext(reptLineNo, body, size))
|
||||||
|
return;
|
||||||
|
|
||||||
|
contextStack->nbReptIters = count;
|
||||||
|
contextStack->forValue = start;
|
||||||
|
contextStack->forStep = step;
|
||||||
|
contextStack->forName = strdup(symName);
|
||||||
|
if (!contextStack->forName)
|
||||||
|
fatalerror("Not enough memory for FOR symbol name: %s\n", strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
void fstk_StopRept(void)
|
||||||
|
{
|
||||||
|
/* Prevent more iterations */
|
||||||
|
contextStack->nbReptIters = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fstk_Break(void)
|
||||||
|
{
|
||||||
|
dbgPrint("Breaking out of REPT/FOR\n");
|
||||||
|
|
||||||
|
if (contextStack->fileInfo->type != NODE_REPT) {
|
||||||
|
error("BREAK can only be used inside a REPT/FOR block\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fstk_StopRept();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void fstk_Init(char const *mainPath, size_t maxRecursionDepth)
|
void fstk_Init(char const *mainPath, size_t maxRecursionDepth)
|
||||||
@@ -453,6 +541,9 @@ void fstk_Init(char const *mainPath, size_t maxRecursionDepth)
|
|||||||
context->uniqueID = 0;
|
context->uniqueID = 0;
|
||||||
macro_SetUniqueID(0);
|
macro_SetUniqueID(0);
|
||||||
context->nbReptIters = 0;
|
context->nbReptIters = 0;
|
||||||
|
context->forValue = 0;
|
||||||
|
context->forStep = 0;
|
||||||
|
context->forName = NULL;
|
||||||
|
|
||||||
/* Now that it's set up properly, register the context */
|
/* Now that it's set up properly, register the context */
|
||||||
contextStack = context;
|
contextStack = context;
|
||||||
|
|||||||
1193
src/asm/lexer.c
1193
src/asm/lexer.c
File diff suppressed because it is too large
Load Diff
@@ -2,14 +2,16 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "asm/asm.h"
|
|
||||||
#include "asm/macro.h"
|
#include "asm/macro.h"
|
||||||
#include "asm/warning.h"
|
#include "asm/warning.h"
|
||||||
|
|
||||||
|
#define MAXMACROARGS 99999
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Your average macro invocation does not go past the tens, but some go further
|
* Your average macro invocation does not go past the tens, but some go further
|
||||||
* This ensures that sane and slightly insane invocations suffer no penalties,
|
* This ensures that sane and slightly insane invocations suffer no penalties,
|
||||||
@@ -36,7 +38,7 @@ static uint32_t maxUniqueID = 0;
|
|||||||
* guarantees the size of the buffer will be correct. I was unable to find a
|
* guarantees the size of the buffer will be correct. I was unable to find a
|
||||||
* better solution, but if you have one, please feel free!
|
* better solution, but if you have one, please feel free!
|
||||||
*/
|
*/
|
||||||
static char uniqueIDBuf[] = "_" EXPAND_AND_STR(UINT32_MAX);
|
static char uniqueIDBuf[] = "_u4294967295"; // UINT32_MAX
|
||||||
static char *uniqueIDPtr = NULL;
|
static char *uniqueIDPtr = NULL;
|
||||||
|
|
||||||
struct MacroArgs *macro_GetCurrentArgs(void)
|
struct MacroArgs *macro_GetCurrentArgs(void)
|
||||||
@@ -60,14 +62,15 @@ struct MacroArgs *macro_NewArgs(void)
|
|||||||
void macro_AppendArg(struct MacroArgs **argPtr, char *s)
|
void macro_AppendArg(struct MacroArgs **argPtr, char *s)
|
||||||
{
|
{
|
||||||
#define macArgs (*argPtr)
|
#define macArgs (*argPtr)
|
||||||
|
if (s[0] == '\0')
|
||||||
|
warning(WARNING_EMPTY_MACRO_ARG, "Empty macro argument\n");
|
||||||
if (macArgs->nbArgs == MAXMACROARGS)
|
if (macArgs->nbArgs == MAXMACROARGS)
|
||||||
error("A maximum of " EXPAND_AND_STR(MAXMACROARGS)
|
error("A maximum of " EXPAND_AND_STR(MAXMACROARGS) " arguments is allowed\n");
|
||||||
" arguments is allowed\n");
|
|
||||||
if (macArgs->nbArgs >= macArgs->capacity) {
|
if (macArgs->nbArgs >= macArgs->capacity) {
|
||||||
macArgs->capacity *= 2;
|
macArgs->capacity *= 2;
|
||||||
/* Check that overflow didn't roll us back */
|
/* Check that overflow didn't roll us back */
|
||||||
if (macArgs->capacity <= macArgs->nbArgs)
|
if (macArgs->capacity <= macArgs->nbArgs)
|
||||||
fatalerror("Failed to add new macro argument: possible capacity overflow\n");
|
fatalerror("Failed to add new macro argument: capacity overflow\n");
|
||||||
macArgs = realloc(macArgs, SIZEOF_ARGS(macArgs->capacity));
|
macArgs = realloc(macArgs, SIZEOF_ARGS(macArgs->capacity));
|
||||||
if (!macArgs)
|
if (!macArgs)
|
||||||
fatalerror("Error adding new macro argument: %s\n", strerror(errno));
|
fatalerror("Error adding new macro argument: %s\n", strerror(errno));
|
||||||
@@ -98,6 +101,40 @@ char const *macro_GetArg(uint32_t i)
|
|||||||
: macroArgs->args[realIndex];
|
: macroArgs->args[realIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *macro_GetAllArgs(void)
|
||||||
|
{
|
||||||
|
if (!macroArgs)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (macroArgs->shift >= macroArgs->nbArgs)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
size_t len = 0;
|
||||||
|
|
||||||
|
for (uint32_t i = macroArgs->shift; i < macroArgs->nbArgs; i++)
|
||||||
|
len += strlen(macroArgs->args[i]) + 1; /* 1 for comma */
|
||||||
|
|
||||||
|
char *str = malloc(len + 1); /* 1 for '\0' */
|
||||||
|
char *ptr = str;
|
||||||
|
|
||||||
|
if (!str)
|
||||||
|
fatalerror("Failed to allocate memory for expanding '\\#': %s\n", strerror(errno));
|
||||||
|
|
||||||
|
for (uint32_t i = macroArgs->shift; i < macroArgs->nbArgs; i++) {
|
||||||
|
size_t n = strlen(macroArgs->args[i]);
|
||||||
|
|
||||||
|
memcpy(ptr, macroArgs->args[i], n);
|
||||||
|
ptr += n;
|
||||||
|
|
||||||
|
/* Commas go between args and after a last empty arg */
|
||||||
|
if (i < macroArgs->nbArgs - 1 || n == 0)
|
||||||
|
*ptr++ = ','; /* no space after comma */
|
||||||
|
}
|
||||||
|
*ptr = '\0';
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t macro_GetUniqueID(void)
|
uint32_t macro_GetUniqueID(void)
|
||||||
{
|
{
|
||||||
return uniqueID;
|
return uniqueID;
|
||||||
@@ -117,7 +154,8 @@ void macro_SetUniqueID(uint32_t id)
|
|||||||
if (uniqueID > maxUniqueID)
|
if (uniqueID > maxUniqueID)
|
||||||
maxUniqueID = uniqueID;
|
maxUniqueID = uniqueID;
|
||||||
/* The buffer is guaranteed to be the correct size */
|
/* The buffer is guaranteed to be the correct size */
|
||||||
sprintf(uniqueIDBuf, "_%" PRIu32, id);
|
/* This is a valid label fragment, but not a valid numeric */
|
||||||
|
sprintf(uniqueIDBuf, "_u%" PRIu32, id);
|
||||||
uniqueIDPtr = uniqueIDBuf;
|
uniqueIDPtr = uniqueIDBuf;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,12 +170,17 @@ void macro_ShiftCurrentArgs(int32_t count)
|
|||||||
{
|
{
|
||||||
if (!macroArgs) {
|
if (!macroArgs) {
|
||||||
error("Cannot shift macro arguments outside of a macro\n");
|
error("Cannot shift macro arguments outside of a macro\n");
|
||||||
} else if (count < 0) {
|
} else if (count > 0 && (count > macroArgs->nbArgs
|
||||||
error("Cannot shift arguments by negative amount %" PRId32 "\n", count);
|
|| macroArgs->shift > macroArgs->nbArgs - count)) {
|
||||||
} else if (macroArgs->shift < macroArgs->nbArgs) {
|
warning(WARNING_MACRO_SHIFT,
|
||||||
macroArgs->shift += count;
|
"Cannot shift macro arguments past their end\n");
|
||||||
if (macroArgs->shift > macroArgs->nbArgs)
|
|
||||||
macroArgs->shift = macroArgs->nbArgs;
|
macroArgs->shift = macroArgs->nbArgs;
|
||||||
|
} else if (count < 0 && macroArgs->shift < -count) {
|
||||||
|
warning(WARNING_MACRO_SHIFT,
|
||||||
|
"Cannot shift macro arguments past their beginning\n");
|
||||||
|
macroArgs->shift = 0;
|
||||||
|
} else {
|
||||||
|
macroArgs->shift += count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
376
src/asm/main.c
376
src/asm/main.c
@@ -19,9 +19,11 @@
|
|||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
#include "asm/charmap.h"
|
#include "asm/charmap.h"
|
||||||
|
#include "asm/format.h"
|
||||||
#include "asm/fstack.h"
|
#include "asm/fstack.h"
|
||||||
#include "asm/lexer.h"
|
#include "asm/lexer.h"
|
||||||
#include "asm/main.h"
|
#include "asm/main.h"
|
||||||
|
#include "asm/opt.h"
|
||||||
#include "asm/output.h"
|
#include "asm/output.h"
|
||||||
#include "asm/rpn.h"
|
#include "asm/rpn.h"
|
||||||
#include "asm/symbol.h"
|
#include "asm/symbol.h"
|
||||||
@@ -38,15 +40,6 @@
|
|||||||
// Unfortunately, macOS still ships 2.3, which is from 2008...
|
// Unfortunately, macOS still ships 2.3, which is from 2008...
|
||||||
int yyparse(void);
|
int yyparse(void);
|
||||||
|
|
||||||
size_t cldefines_index;
|
|
||||||
size_t cldefines_numindices;
|
|
||||||
size_t cldefines_bufsize;
|
|
||||||
const size_t cldefine_entrysize = 2 * sizeof(void *);
|
|
||||||
char **cldefines;
|
|
||||||
|
|
||||||
clock_t nStartClock, nEndClock;
|
|
||||||
uint32_t nTotalLines, nIFDepth;
|
|
||||||
|
|
||||||
#if defined(YYDEBUG) && YYDEBUG
|
#if defined(YYDEBUG) && YYDEBUG
|
||||||
extern int yydebug;
|
extern int yydebug;
|
||||||
#endif
|
#endif
|
||||||
@@ -57,162 +50,11 @@ bool oFailedOnMissingInclude;
|
|||||||
bool oGeneratePhonyDeps;
|
bool oGeneratePhonyDeps;
|
||||||
char *tzTargetFileName;
|
char *tzTargetFileName;
|
||||||
|
|
||||||
/*
|
|
||||||
* Option stack
|
|
||||||
*/
|
|
||||||
|
|
||||||
struct sOptions DefaultOptions;
|
|
||||||
struct sOptions CurrentOptions;
|
|
||||||
bool haltnop;
|
bool haltnop;
|
||||||
bool optimizeloads;
|
bool optimizeloads;
|
||||||
bool verbose;
|
bool verbose;
|
||||||
bool warnings; /* True to enable warnings, false to disable them. */
|
bool warnings; /* True to enable warnings, false to disable them. */
|
||||||
|
|
||||||
struct sOptionStackEntry {
|
|
||||||
struct sOptions Options;
|
|
||||||
struct sOptionStackEntry *next;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct sOptionStackEntry *pOptionStack;
|
|
||||||
|
|
||||||
void opt_SetCurrentOptions(struct sOptions *opt)
|
|
||||||
{
|
|
||||||
CurrentOptions = *opt;
|
|
||||||
lexer_SetGfxDigits(CurrentOptions.gbgfx);
|
|
||||||
lexer_SetBinDigits(CurrentOptions.binary);
|
|
||||||
}
|
|
||||||
|
|
||||||
void opt_Parse(char *s)
|
|
||||||
{
|
|
||||||
struct sOptions newopt;
|
|
||||||
|
|
||||||
newopt = CurrentOptions;
|
|
||||||
|
|
||||||
switch (s[0]) {
|
|
||||||
case 'g':
|
|
||||||
if (strlen(&s[1]) == 4) {
|
|
||||||
newopt.gbgfx[0] = s[1];
|
|
||||||
newopt.gbgfx[1] = s[2];
|
|
||||||
newopt.gbgfx[2] = s[3];
|
|
||||||
newopt.gbgfx[3] = s[4];
|
|
||||||
} else {
|
|
||||||
error("Must specify exactly 4 characters for option 'g'\n");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'b':
|
|
||||||
if (strlen(&s[1]) == 2) {
|
|
||||||
newopt.binary[0] = s[1];
|
|
||||||
newopt.binary[1] = s[2];
|
|
||||||
} else {
|
|
||||||
error("Must specify exactly 2 characters for option 'b'\n");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'z':
|
|
||||||
warning(WARNING_OBSOLETE, "Option 'z' is a deprecated alias for 'p'\n");
|
|
||||||
/* fallthrough */
|
|
||||||
case 'p':
|
|
||||||
if (strlen(&s[1]) <= 2) {
|
|
||||||
int result;
|
|
||||||
unsigned int fillchar;
|
|
||||||
|
|
||||||
result = sscanf(&s[1], "%x", &fillchar);
|
|
||||||
if (result != EOF && result != 1)
|
|
||||||
error("Invalid argument for option 'z'\n");
|
|
||||||
else
|
|
||||||
newopt.fillchar = fillchar;
|
|
||||||
} else {
|
|
||||||
error("Invalid argument for option 'z'\n");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
error("Unknown option\n");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
opt_SetCurrentOptions(&newopt);
|
|
||||||
}
|
|
||||||
|
|
||||||
void opt_Push(void)
|
|
||||||
{
|
|
||||||
struct sOptionStackEntry *pOpt;
|
|
||||||
|
|
||||||
pOpt = malloc(sizeof(struct sOptionStackEntry));
|
|
||||||
|
|
||||||
if (pOpt == NULL)
|
|
||||||
fatalerror("No memory for option stack\n");
|
|
||||||
|
|
||||||
pOpt->Options = CurrentOptions;
|
|
||||||
pOpt->next = pOptionStack;
|
|
||||||
pOptionStack = pOpt;
|
|
||||||
}
|
|
||||||
|
|
||||||
void opt_Pop(void)
|
|
||||||
{
|
|
||||||
if (pOptionStack == NULL)
|
|
||||||
fatalerror("No entries in the option stack\n");
|
|
||||||
|
|
||||||
struct sOptionStackEntry *pOpt;
|
|
||||||
|
|
||||||
pOpt = pOptionStack;
|
|
||||||
opt_SetCurrentOptions(&(pOpt->Options));
|
|
||||||
pOptionStack = pOpt->next;
|
|
||||||
free(pOpt);
|
|
||||||
}
|
|
||||||
|
|
||||||
void opt_AddDefine(char *s)
|
|
||||||
{
|
|
||||||
char *value, *equals;
|
|
||||||
|
|
||||||
if (cldefines_index >= cldefines_numindices) {
|
|
||||||
/* Check for overflows */
|
|
||||||
if ((cldefines_numindices * 2) < cldefines_numindices)
|
|
||||||
fatalerror("No memory for command line defines\n");
|
|
||||||
|
|
||||||
if ((cldefines_bufsize * 2) < cldefines_bufsize)
|
|
||||||
fatalerror("No memory for command line defines\n");
|
|
||||||
|
|
||||||
cldefines_numindices *= 2;
|
|
||||||
cldefines_bufsize *= 2;
|
|
||||||
|
|
||||||
cldefines = realloc(cldefines, cldefines_bufsize);
|
|
||||||
if (!cldefines)
|
|
||||||
fatalerror("No memory for command line defines\n");
|
|
||||||
}
|
|
||||||
equals = strchr(s, '=');
|
|
||||||
if (equals) {
|
|
||||||
*equals = '\0';
|
|
||||||
value = equals + 1;
|
|
||||||
} else {
|
|
||||||
value = "1";
|
|
||||||
}
|
|
||||||
cldefines[cldefines_index++] = s;
|
|
||||||
cldefines[cldefines_index++] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void opt_ParseDefines(void)
|
|
||||||
{
|
|
||||||
uint32_t i;
|
|
||||||
|
|
||||||
for (i = 0; i < cldefines_index; i += 2)
|
|
||||||
sym_AddString(cldefines[i], cldefines[i + 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void upperstring(char *s)
|
|
||||||
{
|
|
||||||
while (*s) {
|
|
||||||
*s = toupper(*s);
|
|
||||||
s++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void lowerstring(char *s)
|
|
||||||
{
|
|
||||||
while (*s) {
|
|
||||||
*s = tolower(*s);
|
|
||||||
s++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Escapes Make-special chars from a string */
|
/* Escapes Make-special chars from a string */
|
||||||
static char *make_escape(const char *str)
|
static char *make_escape(const char *str)
|
||||||
{
|
{
|
||||||
@@ -234,7 +76,7 @@ static char *make_escape(const char *str)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Short options */
|
/* Short options */
|
||||||
static char const *optstring = "b:D:Eg:hi:LM:o:p:r:VvW:w";
|
static const char *optstring = "b:D:Eg:hi:LM:o:p:r:VvW:w";
|
||||||
|
|
||||||
/* Variables for the long-only options */
|
/* Variables for the long-only options */
|
||||||
static int depType; /* Variants of `-M` */
|
static int depType; /* Variants of `-M` */
|
||||||
@@ -276,7 +118,7 @@ static void print_usage(void)
|
|||||||
fputs(
|
fputs(
|
||||||
"Usage: rgbasm [-EhLVvw] [-b chars] [-D name[=value]] [-g chars] [-i path]\n"
|
"Usage: rgbasm [-EhLVvw] [-b chars] [-D name[=value]] [-g chars] [-i path]\n"
|
||||||
" [-M depend_file] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n"
|
" [-M depend_file] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n"
|
||||||
" [-o out_file] [-p pad_value] [-r depth] [-W warning] <file> ...\n"
|
" [-o out_file] [-p pad_value] [-r depth] [-W warning] <file>\n"
|
||||||
"Useful options:\n"
|
"Useful options:\n"
|
||||||
" -E, --export-all export all labels\n"
|
" -E, --export-all export all labels\n"
|
||||||
" -M, --dependfile <path> set the output dependency file\n"
|
" -M, --dependfile <path> set the output dependency file\n"
|
||||||
@@ -295,120 +137,130 @@ int main(int argc, char *argv[])
|
|||||||
int ch;
|
int ch;
|
||||||
char *ep;
|
char *ep;
|
||||||
|
|
||||||
struct sOptions newopt;
|
time_t now = time(NULL);
|
||||||
|
char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH");
|
||||||
|
|
||||||
char *tzMainfile;
|
/*
|
||||||
|
* Support SOURCE_DATE_EPOCH for reproducible builds
|
||||||
|
* https://reproducible-builds.org/docs/source-date-epoch/
|
||||||
|
*/
|
||||||
|
if (sourceDateEpoch)
|
||||||
|
now = (time_t)strtoul(sourceDateEpoch, NULL, 0);
|
||||||
|
|
||||||
dependfile = NULL;
|
dependfile = NULL;
|
||||||
|
|
||||||
/* Initial number of allocated elements in array */
|
|
||||||
cldefines_numindices = 32;
|
|
||||||
cldefines_bufsize = cldefines_numindices * cldefine_entrysize;
|
|
||||||
cldefines = malloc(cldefines_bufsize);
|
|
||||||
if (!cldefines)
|
|
||||||
fatalerror("No memory for command line defines\n");
|
|
||||||
|
|
||||||
#if defined(YYDEBUG) && YYDEBUG
|
#if defined(YYDEBUG) && YYDEBUG
|
||||||
yydebug = 1;
|
yydebug = 1;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Perform some init for below
|
||||||
|
sym_Init(now);
|
||||||
|
|
||||||
|
// Set defaults
|
||||||
|
|
||||||
oGeneratePhonyDeps = false;
|
oGeneratePhonyDeps = false;
|
||||||
oGeneratedMissingIncludes = false;
|
oGeneratedMissingIncludes = false;
|
||||||
oFailedOnMissingInclude = false;
|
oFailedOnMissingInclude = false;
|
||||||
tzTargetFileName = NULL;
|
tzTargetFileName = NULL;
|
||||||
uint32_t maxRecursionDepth = 64;
|
|
||||||
size_t nTargetFileNameLen = 0;
|
|
||||||
|
|
||||||
DefaultOptions.gbgfx[0] = '0';
|
opt_B("01");
|
||||||
DefaultOptions.gbgfx[1] = '1';
|
opt_G("0123");
|
||||||
DefaultOptions.gbgfx[2] = '2';
|
opt_P(0);
|
||||||
DefaultOptions.gbgfx[3] = '3';
|
|
||||||
DefaultOptions.binary[0] = '0';
|
|
||||||
DefaultOptions.binary[1] = '1';
|
|
||||||
DefaultOptions.fillchar = 0;
|
|
||||||
optimizeloads = true;
|
optimizeloads = true;
|
||||||
haltnop = true;
|
haltnop = true;
|
||||||
verbose = false;
|
verbose = false;
|
||||||
warnings = true;
|
warnings = true;
|
||||||
bool exportall = false;
|
sym_SetExportAll(false);
|
||||||
|
uint32_t maxRecursionDepth = 64;
|
||||||
|
size_t nTargetFileNameLen = 0;
|
||||||
|
|
||||||
opt_SetCurrentOptions(&DefaultOptions);
|
while ((ch = musl_getopt_long_only(argc, argv, optstring, longopts, NULL)) != -1) {
|
||||||
|
|
||||||
newopt = CurrentOptions;
|
|
||||||
|
|
||||||
while ((ch = musl_getopt_long_only(argc, argv, optstring, longopts,
|
|
||||||
NULL)) != -1) {
|
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
case 'b':
|
case 'b':
|
||||||
if (strlen(optarg) == 2) {
|
if (strlen(musl_optarg) == 2)
|
||||||
newopt.binary[0] = optarg[1];
|
opt_B(&musl_optarg[1]);
|
||||||
newopt.binary[1] = optarg[2];
|
else
|
||||||
} else {
|
|
||||||
errx(1, "Must specify exactly 2 characters for option 'b'");
|
errx(1, "Must specify exactly 2 characters for option 'b'");
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
char *equals;
|
||||||
case 'D':
|
case 'D':
|
||||||
opt_AddDefine(optarg);
|
equals = strchr(musl_optarg, '=');
|
||||||
break;
|
if (equals) {
|
||||||
case 'E':
|
*equals = '\0';
|
||||||
exportall = true;
|
sym_AddString(musl_optarg, equals + 1);
|
||||||
break;
|
|
||||||
case 'g':
|
|
||||||
if (strlen(optarg) == 4) {
|
|
||||||
newopt.gbgfx[0] = optarg[1];
|
|
||||||
newopt.gbgfx[1] = optarg[2];
|
|
||||||
newopt.gbgfx[2] = optarg[3];
|
|
||||||
newopt.gbgfx[3] = optarg[4];
|
|
||||||
} else {
|
} else {
|
||||||
errx(1, "Must specify exactly 4 characters for option 'g'");
|
sym_AddString(musl_optarg, "1");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'E':
|
||||||
|
sym_SetExportAll(true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'g':
|
||||||
|
if (strlen(musl_optarg) == 4)
|
||||||
|
opt_G(&musl_optarg[1]);
|
||||||
|
else
|
||||||
|
errx(1, "Must specify exactly 4 characters for option 'g'");
|
||||||
|
break;
|
||||||
|
|
||||||
case 'h':
|
case 'h':
|
||||||
haltnop = false;
|
haltnop = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'i':
|
case 'i':
|
||||||
fstk_AddIncludePath(optarg);
|
fstk_AddIncludePath(musl_optarg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'L':
|
case 'L':
|
||||||
optimizeloads = false;
|
optimizeloads = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'M':
|
case 'M':
|
||||||
if (!strcmp("-", optarg))
|
if (!strcmp("-", musl_optarg))
|
||||||
dependfile = stdout;
|
dependfile = stdout;
|
||||||
else
|
else
|
||||||
dependfile = fopen(optarg, "w");
|
dependfile = fopen(musl_optarg, "w");
|
||||||
if (dependfile == NULL)
|
if (dependfile == NULL)
|
||||||
err(1, "Could not open dependfile %s",
|
err(1, "Could not open dependfile %s", musl_optarg);
|
||||||
optarg);
|
|
||||||
break;
|
break;
|
||||||
case 'o':
|
|
||||||
out_SetFileName(optarg);
|
|
||||||
break;
|
|
||||||
case 'p':
|
|
||||||
newopt.fillchar = strtoul(optarg, &ep, 0);
|
|
||||||
|
|
||||||
if (optarg[0] == '\0' || *ep != '\0')
|
case 'o':
|
||||||
|
out_SetFileName(musl_optarg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
unsigned long fill;
|
||||||
|
case 'p':
|
||||||
|
fill = strtoul(musl_optarg, &ep, 0);
|
||||||
|
|
||||||
|
if (musl_optarg[0] == '\0' || *ep != '\0')
|
||||||
errx(1, "Invalid argument for option 'p'");
|
errx(1, "Invalid argument for option 'p'");
|
||||||
|
|
||||||
if (newopt.fillchar < 0 || newopt.fillchar > 0xFF)
|
if (fill < 0 || fill > 0xFF)
|
||||||
errx(1, "Argument for option 'p' must be between 0 and 0xFF");
|
errx(1, "Argument for option 'p' must be between 0 and 0xFF");
|
||||||
|
|
||||||
|
opt_P(fill);
|
||||||
break;
|
break;
|
||||||
case 'r':
|
|
||||||
maxRecursionDepth = strtoul(optarg, &ep, 0);
|
|
||||||
|
|
||||||
if (optarg[0] == '\0' || *ep != '\0')
|
case 'r':
|
||||||
|
maxRecursionDepth = strtoul(musl_optarg, &ep, 0);
|
||||||
|
|
||||||
|
if (musl_optarg[0] == '\0' || *ep != '\0')
|
||||||
errx(1, "Invalid argument for option 'r'");
|
errx(1, "Invalid argument for option 'r'");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'V':
|
case 'V':
|
||||||
printf("rgbasm %s\n", get_package_version_string());
|
printf("rgbasm %s\n", get_package_version_string());
|
||||||
exit(0);
|
exit(0);
|
||||||
case 'v':
|
case 'v':
|
||||||
verbose = true;
|
verbose = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'W':
|
case 'W':
|
||||||
processWarningFlag(optarg);
|
processWarningFlag(musl_optarg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'w':
|
case 'w':
|
||||||
warnings = false;
|
warnings = false;
|
||||||
break;
|
break;
|
||||||
@@ -419,28 +271,27 @@ int main(int argc, char *argv[])
|
|||||||
case 'G':
|
case 'G':
|
||||||
oGeneratedMissingIncludes = true;
|
oGeneratedMissingIncludes = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'P':
|
case 'P':
|
||||||
oGeneratePhonyDeps = true;
|
oGeneratePhonyDeps = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'Q':
|
case 'Q':
|
||||||
case 'T':
|
case 'T':
|
||||||
if (optind == argc)
|
if (musl_optind == argc)
|
||||||
errx(1, "-M%c takes a target file name argument",
|
errx(1, "-M%c takes a target file name argument", depType);
|
||||||
depType);
|
ep = musl_optarg;
|
||||||
ep = optarg;
|
|
||||||
if (depType == 'Q')
|
if (depType == 'Q')
|
||||||
ep = make_escape(ep);
|
ep = make_escape(ep);
|
||||||
|
|
||||||
nTargetFileNameLen += strlen(ep) + 1;
|
nTargetFileNameLen += strlen(ep) + 1;
|
||||||
if (!tzTargetFileName) {
|
if (!tzTargetFileName) {
|
||||||
/* On first alloc, make an empty str */
|
/* On first alloc, make an empty str */
|
||||||
tzTargetFileName =
|
tzTargetFileName = malloc(nTargetFileNameLen + 1);
|
||||||
malloc(nTargetFileNameLen + 1);
|
|
||||||
if (tzTargetFileName)
|
if (tzTargetFileName)
|
||||||
*tzTargetFileName = '\0';
|
*tzTargetFileName = '\0';
|
||||||
} else {
|
} else {
|
||||||
tzTargetFileName =
|
tzTargetFileName = realloc(tzTargetFileName,
|
||||||
realloc(tzTargetFileName,
|
|
||||||
nTargetFileNameLen + 1);
|
nTargetFileNameLen + 1);
|
||||||
}
|
}
|
||||||
if (tzTargetFileName == NULL)
|
if (tzTargetFileName == NULL)
|
||||||
@@ -448,8 +299,8 @@ int main(int argc, char *argv[])
|
|||||||
strcat(tzTargetFileName, ep);
|
strcat(tzTargetFileName, ep);
|
||||||
if (depType == 'Q')
|
if (depType == 'Q')
|
||||||
free(ep);
|
free(ep);
|
||||||
char *ptr = tzTargetFileName +
|
char *ptr = tzTargetFileName + strlen(tzTargetFileName);
|
||||||
strlen(tzTargetFileName);
|
|
||||||
*ptr++ = ' ';
|
*ptr++ = ' ';
|
||||||
*ptr = '\0';
|
*ptr = '\0';
|
||||||
break;
|
break;
|
||||||
@@ -462,76 +313,49 @@ int main(int argc, char *argv[])
|
|||||||
/* NOTREACHED */
|
/* NOTREACHED */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
argc -= optind;
|
|
||||||
argv += optind;
|
|
||||||
|
|
||||||
if (tzTargetFileName == NULL)
|
if (tzTargetFileName == NULL)
|
||||||
tzTargetFileName = tzObjectname;
|
tzTargetFileName = tzObjectname;
|
||||||
|
|
||||||
opt_SetCurrentOptions(&newopt);
|
if (argc == musl_optind) {
|
||||||
|
fputs("FATAL: No input files\n", stderr);
|
||||||
DefaultOptions = CurrentOptions;
|
print_usage();
|
||||||
|
} else if (argc != musl_optind + 1) {
|
||||||
if (argc == 0) {
|
fputs("FATAL: More than one input file given\n", stderr);
|
||||||
fputs("FATAL: no input files\n", stderr);
|
|
||||||
print_usage();
|
print_usage();
|
||||||
}
|
}
|
||||||
|
|
||||||
tzMainfile = argv[argc - 1];
|
char const *mainFileName = argv[musl_optind];
|
||||||
|
|
||||||
if (verbose)
|
if (verbose)
|
||||||
printf("Assembling %s\n", tzMainfile);
|
printf("Assembling %s\n", mainFileName);
|
||||||
|
|
||||||
if (dependfile) {
|
if (dependfile) {
|
||||||
if (!tzTargetFileName)
|
if (!tzTargetFileName)
|
||||||
errx(1, "Dependency files can only be created if a target file is specified with either -o, -MQ or -MT.\n");
|
errx(1, "Dependency files can only be created if a target file is specified with either -o, -MQ or -MT\n");
|
||||||
|
|
||||||
fprintf(dependfile, "%s: %s\n", tzTargetFileName, tzMainfile);
|
fprintf(dependfile, "%s: %s\n", tzTargetFileName, mainFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Init file stack; important to do first, since it provides the file name, line, etc */
|
|
||||||
lexer_Init();
|
|
||||||
fstk_Init(tzMainfile, maxRecursionDepth);
|
|
||||||
|
|
||||||
nStartClock = clock();
|
|
||||||
|
|
||||||
nTotalLines = 0;
|
|
||||||
nIFDepth = 0;
|
|
||||||
sym_Init();
|
|
||||||
sym_SetExportAll(exportall);
|
|
||||||
|
|
||||||
opt_ParseDefines();
|
|
||||||
charmap_New("main", NULL);
|
charmap_New("main", NULL);
|
||||||
|
|
||||||
opt_SetCurrentOptions(&DefaultOptions);
|
// Init lexer and file stack, prodiving file info
|
||||||
|
lexer_Init();
|
||||||
|
fstk_Init(mainFileName, maxRecursionDepth);
|
||||||
|
|
||||||
|
// Perform parse (yyparse is auto-generated from `parser.y`)
|
||||||
|
if (yyparse() != 0 && nbErrors == 0)
|
||||||
|
nbErrors = 1;
|
||||||
|
|
||||||
if (yyparse() != 0 || nbErrors != 0)
|
|
||||||
errx(1, "Assembly aborted (%u errors)!", nbErrors);
|
|
||||||
if (dependfile)
|
if (dependfile)
|
||||||
fclose(dependfile);
|
fclose(dependfile);
|
||||||
|
|
||||||
if (nIFDepth != 0)
|
|
||||||
errx(1, "Unterminated IF construct (%" PRIu32 " levels)!",
|
|
||||||
nIFDepth);
|
|
||||||
|
|
||||||
sect_CheckUnionClosed();
|
sect_CheckUnionClosed();
|
||||||
|
|
||||||
double timespent;
|
if (nbErrors != 0)
|
||||||
|
errx(1, "Assembly aborted (%u errors)!", nbErrors);
|
||||||
nEndClock = clock();
|
|
||||||
timespent = ((double)(nEndClock - nStartClock))
|
|
||||||
/ (double)CLOCKS_PER_SEC;
|
|
||||||
if (verbose) {
|
|
||||||
printf("Success! %" PRIu32 " lines in %d.%02d seconds ",
|
|
||||||
nTotalLines, (int)timespent,
|
|
||||||
((int)(timespent * 100.0)) % 100);
|
|
||||||
if (timespent < FLT_MIN_EXP)
|
|
||||||
printf("(INFINITY lines/minute)\n");
|
|
||||||
else
|
|
||||||
printf("(%d lines/minute)\n",
|
|
||||||
(int)(60 / timespent * nTotalLines));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// If parse aborted due to missing an include, and `-MG` was given, exit normally
|
||||||
if (oFailedOnMissingInclude)
|
if (oFailedOnMissingInclude)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
|||||||
147
src/asm/math.c
147
src/asm/math.c
@@ -1,147 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of RGBDS.
|
|
||||||
*
|
|
||||||
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Fixedpoint math routines
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <inttypes.h>
|
|
||||||
#include <math.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
#include "asm/mymath.h"
|
|
||||||
#include "asm/symbol.h"
|
|
||||||
|
|
||||||
#define fx2double(i) ((double)((i) / 65536.0))
|
|
||||||
#define double2fx(d) ((int32_t)((d) * 65536.0))
|
|
||||||
|
|
||||||
#ifndef M_PI
|
|
||||||
#define M_PI 3.14159265358979323846
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Define the _PI symbol
|
|
||||||
*/
|
|
||||||
void math_DefinePI(void)
|
|
||||||
{
|
|
||||||
sym_AddEqu("_PI", double2fx(M_PI));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Print a fixed point value
|
|
||||||
*/
|
|
||||||
void math_Print(int32_t i)
|
|
||||||
{
|
|
||||||
uint32_t u = i;
|
|
||||||
const char *sign = "";
|
|
||||||
|
|
||||||
if (i < 0) {
|
|
||||||
u = -u;
|
|
||||||
sign = "-";
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("%s%" PRIu32 ".%05" PRIu32, sign, u >> 16,
|
|
||||||
((uint32_t)(fx2double(u) * 100000 + 0.5)) % 100000);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Calculate sine
|
|
||||||
*/
|
|
||||||
int32_t math_Sin(int32_t i)
|
|
||||||
{
|
|
||||||
return double2fx(sin(fx2double(i) * 2 * M_PI / 65536));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Calculate cosine
|
|
||||||
*/
|
|
||||||
int32_t math_Cos(int32_t i)
|
|
||||||
{
|
|
||||||
return double2fx(cos(fx2double(i) * 2 * M_PI / 65536));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Calculate tangent
|
|
||||||
*/
|
|
||||||
int32_t math_Tan(int32_t i)
|
|
||||||
{
|
|
||||||
return double2fx(tan(fx2double(i) * 2 * M_PI / 65536));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Calculate arcsine
|
|
||||||
*/
|
|
||||||
int32_t math_ASin(int32_t i)
|
|
||||||
{
|
|
||||||
return double2fx(asin(fx2double(i)) / 2 / M_PI * 65536);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Calculate arccosine
|
|
||||||
*/
|
|
||||||
int32_t math_ACos(int32_t i)
|
|
||||||
{
|
|
||||||
return double2fx(acos(fx2double(i)) / 2 / M_PI * 65536);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Calculate arctangent
|
|
||||||
*/
|
|
||||||
int32_t math_ATan(int32_t i)
|
|
||||||
{
|
|
||||||
return double2fx(atan(fx2double(i)) / 2 / M_PI * 65536);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Calculate atan2
|
|
||||||
*/
|
|
||||||
int32_t math_ATan2(int32_t i, int32_t j)
|
|
||||||
{
|
|
||||||
return double2fx(atan2(fx2double(i), fx2double(j)) / 2 / M_PI * 65536);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Multiplication
|
|
||||||
*/
|
|
||||||
int32_t math_Mul(int32_t i, int32_t j)
|
|
||||||
{
|
|
||||||
return double2fx(fx2double(i) * fx2double(j));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Division
|
|
||||||
*/
|
|
||||||
int32_t math_Div(int32_t i, int32_t j)
|
|
||||||
{
|
|
||||||
return double2fx(fx2double(i) / fx2double(j));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Round
|
|
||||||
*/
|
|
||||||
int32_t math_Round(int32_t i)
|
|
||||||
{
|
|
||||||
return double2fx(round(fx2double(i)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Ceil
|
|
||||||
*/
|
|
||||||
int32_t math_Ceil(int32_t i)
|
|
||||||
{
|
|
||||||
return double2fx(ceil(fx2double(i)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Floor
|
|
||||||
*/
|
|
||||||
int32_t math_Floor(int32_t i)
|
|
||||||
{
|
|
||||||
return double2fx(floor(fx2double(i)));
|
|
||||||
}
|
|
||||||
111
src/asm/opt.c
Normal file
111
src/asm/opt.c
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "asm/lexer.h"
|
||||||
|
#include "asm/section.h"
|
||||||
|
#include "asm/warning.h"
|
||||||
|
|
||||||
|
struct OptStackEntry {
|
||||||
|
char binary[2];
|
||||||
|
char gbgfx[4];
|
||||||
|
int32_t fillByte;
|
||||||
|
struct OptStackEntry *next;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct OptStackEntry *stack = NULL;
|
||||||
|
|
||||||
|
void opt_B(char chars[2])
|
||||||
|
{
|
||||||
|
lexer_SetBinDigits(chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
void opt_G(char chars[4])
|
||||||
|
{
|
||||||
|
lexer_SetGfxDigits(chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
void opt_P(uint8_t fill)
|
||||||
|
{
|
||||||
|
fillByte = fill;
|
||||||
|
}
|
||||||
|
|
||||||
|
void opt_Parse(char *s)
|
||||||
|
{
|
||||||
|
switch (s[0]) {
|
||||||
|
case 'b':
|
||||||
|
if (strlen(&s[1]) == 2)
|
||||||
|
opt_B(&s[1]);
|
||||||
|
else
|
||||||
|
error("Must specify exactly 2 characters for option 'b'\n");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'g':
|
||||||
|
if (strlen(&s[1]) == 4)
|
||||||
|
opt_G(&s[1]);
|
||||||
|
else
|
||||||
|
error("Must specify exactly 4 characters for option 'g'\n");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'p':
|
||||||
|
if (strlen(&s[1]) <= 2) {
|
||||||
|
int result;
|
||||||
|
unsigned int fillchar;
|
||||||
|
|
||||||
|
result = sscanf(&s[1], "%x", &fillchar);
|
||||||
|
if (result != EOF && result != 1)
|
||||||
|
error("Invalid argument for option 'p'\n");
|
||||||
|
else
|
||||||
|
opt_P(fillchar);
|
||||||
|
} else {
|
||||||
|
error("Invalid argument for option 'p'\n");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
error("Unknown option '%c'\n", s[0]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void opt_Push(void)
|
||||||
|
{
|
||||||
|
struct OptStackEntry *entry = malloc(sizeof(*entry));
|
||||||
|
|
||||||
|
if (entry == NULL)
|
||||||
|
fatalerror("Failed to alloc option stack entry: %s\n", strerror(errno));
|
||||||
|
|
||||||
|
// Both of these pulled from lexer.h
|
||||||
|
entry->binary[0] = binDigits[0];
|
||||||
|
entry->binary[1] = binDigits[1];
|
||||||
|
|
||||||
|
entry->gbgfx[0] = gfxDigits[0];
|
||||||
|
entry->gbgfx[1] = gfxDigits[1];
|
||||||
|
entry->gbgfx[2] = gfxDigits[2];
|
||||||
|
entry->gbgfx[3] = gfxDigits[3];
|
||||||
|
|
||||||
|
entry->fillByte = fillByte; // Pulled from section.h
|
||||||
|
|
||||||
|
entry->next = stack;
|
||||||
|
stack = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
void opt_Pop(void)
|
||||||
|
{
|
||||||
|
if (stack == NULL) {
|
||||||
|
error("No entries in the option stack\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OptStackEntry *entry = stack;
|
||||||
|
|
||||||
|
opt_B(entry->binary);
|
||||||
|
opt_G(entry->gbgfx);
|
||||||
|
opt_P(entry->fillByte);
|
||||||
|
stack = entry->next;
|
||||||
|
free(entry);
|
||||||
|
}
|
||||||
111
src/asm/output.c
111
src/asm/output.c
@@ -18,7 +18,6 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "asm/asm.h"
|
|
||||||
#include "asm/charmap.h"
|
#include "asm/charmap.h"
|
||||||
#include "asm/fstack.h"
|
#include "asm/fstack.h"
|
||||||
#include "asm/main.h"
|
#include "asm/main.h"
|
||||||
@@ -69,7 +68,7 @@ static struct FileStackNode *fileStackNodes = NULL;
|
|||||||
/*
|
/*
|
||||||
* Count the number of sections used in this object
|
* Count the number of sections used in this object
|
||||||
*/
|
*/
|
||||||
static uint32_t countsections(void)
|
static uint32_t countSections(void)
|
||||||
{
|
{
|
||||||
uint32_t count = 0;
|
uint32_t count = 0;
|
||||||
|
|
||||||
@@ -82,7 +81,7 @@ static uint32_t countsections(void)
|
|||||||
/*
|
/*
|
||||||
* Count the number of patches used in this object
|
* Count the number of patches used in this object
|
||||||
*/
|
*/
|
||||||
static uint32_t countpatches(struct Section const *sect)
|
static uint32_t countPatches(struct Section const *sect)
|
||||||
{
|
{
|
||||||
uint32_t r = 0;
|
uint32_t r = 0;
|
||||||
|
|
||||||
@@ -96,7 +95,7 @@ static uint32_t countpatches(struct Section const *sect)
|
|||||||
/**
|
/**
|
||||||
* Count the number of assertions used in this object
|
* Count the number of assertions used in this object
|
||||||
*/
|
*/
|
||||||
static uint32_t countasserts(void)
|
static uint32_t countAsserts(void)
|
||||||
{
|
{
|
||||||
struct Assertion *assert = assertions;
|
struct Assertion *assert = assertions;
|
||||||
uint32_t count = 0;
|
uint32_t count = 0;
|
||||||
@@ -111,22 +110,22 @@ static uint32_t countasserts(void)
|
|||||||
/*
|
/*
|
||||||
* Write a long to a file (little-endian)
|
* Write a long to a file (little-endian)
|
||||||
*/
|
*/
|
||||||
static void fputlong(uint32_t i, FILE *f)
|
static void putlong(uint32_t i, FILE *f)
|
||||||
{
|
{
|
||||||
fputc(i, f);
|
putc(i, f);
|
||||||
fputc(i >> 8, f);
|
putc(i >> 8, f);
|
||||||
fputc(i >> 16, f);
|
putc(i >> 16, f);
|
||||||
fputc(i >> 24, f);
|
putc(i >> 24, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Write a NULL-terminated string to a file
|
* Write a NULL-terminated string to a file
|
||||||
*/
|
*/
|
||||||
static void fputstring(char const *s, FILE *f)
|
static void putstring(char const *s, FILE *f)
|
||||||
{
|
{
|
||||||
while (*s)
|
while (*s)
|
||||||
fputc(*s++, f);
|
putc(*s++, f);
|
||||||
fputc(0, f);
|
putc(0, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t getNbFileStackNodes(void)
|
static uint32_t getNbFileStackNodes(void)
|
||||||
@@ -204,14 +203,13 @@ static uint32_t getSectIDIfAny(struct Section const *sect)
|
|||||||
static void writepatch(struct Patch const *patch, FILE *f)
|
static void writepatch(struct Patch const *patch, FILE *f)
|
||||||
{
|
{
|
||||||
assert(patch->src->ID != -1);
|
assert(patch->src->ID != -1);
|
||||||
|
putlong(patch->src->ID, f);
|
||||||
fputlong(patch->src->ID, f);
|
putlong(patch->lineNo, f);
|
||||||
fputlong(patch->lineNo, f);
|
putlong(patch->nOffset, f);
|
||||||
fputlong(patch->nOffset, f);
|
putlong(getSectIDIfAny(patch->pcSection), f);
|
||||||
fputlong(getSectIDIfAny(patch->pcSection), f);
|
putlong(patch->pcOffset, f);
|
||||||
fputlong(patch->pcOffset, f);
|
putc(patch->type, f);
|
||||||
fputc(patch->type, f);
|
putlong(patch->nRPNSize, f);
|
||||||
fputlong(patch->nRPNSize, f);
|
|
||||||
fwrite(patch->pRPN, 1, patch->nRPNSize, f);
|
fwrite(patch->pRPN, 1, patch->nRPNSize, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,23 +218,23 @@ static void writepatch(struct Patch const *patch, FILE *f)
|
|||||||
*/
|
*/
|
||||||
static void writesection(struct Section const *sect, FILE *f)
|
static void writesection(struct Section const *sect, FILE *f)
|
||||||
{
|
{
|
||||||
fputstring(sect->name, f);
|
putstring(sect->name, f);
|
||||||
|
|
||||||
fputlong(sect->size, f);
|
putlong(sect->size, f);
|
||||||
|
|
||||||
bool isUnion = sect->modifier == SECTION_UNION;
|
bool isUnion = sect->modifier == SECTION_UNION;
|
||||||
bool isFragment = sect->modifier == SECTION_FRAGMENT;
|
bool isFragment = sect->modifier == SECTION_FRAGMENT;
|
||||||
|
|
||||||
fputc(sect->type | isUnion << 7 | isFragment << 6, f);
|
putc(sect->type | isUnion << 7 | isFragment << 6, f);
|
||||||
|
|
||||||
fputlong(sect->org, f);
|
putlong(sect->org, f);
|
||||||
fputlong(sect->bank, f);
|
putlong(sect->bank, f);
|
||||||
fputc(sect->align, f);
|
putc(sect->align, f);
|
||||||
fputlong(sect->alignOfs, f);
|
putlong(sect->alignOfs, f);
|
||||||
|
|
||||||
if (sect_HasData(sect->type)) {
|
if (sect_HasData(sect->type)) {
|
||||||
fwrite(sect->data, 1, sect->size, f);
|
fwrite(sect->data, 1, sect->size, f);
|
||||||
fputlong(countpatches(sect), f);
|
putlong(countPatches(sect), f);
|
||||||
|
|
||||||
for (struct Patch const *patch = sect->patches; patch != NULL;
|
for (struct Patch const *patch = sect->patches; patch != NULL;
|
||||||
patch = patch->next)
|
patch = patch->next)
|
||||||
@@ -249,17 +247,17 @@ static void writesection(struct Section const *sect, FILE *f)
|
|||||||
*/
|
*/
|
||||||
static void writesymbol(struct Symbol const *sym, FILE *f)
|
static void writesymbol(struct Symbol const *sym, FILE *f)
|
||||||
{
|
{
|
||||||
fputstring(sym->name, f);
|
putstring(sym->name, f);
|
||||||
if (!sym_IsDefined(sym)) {
|
if (!sym_IsDefined(sym)) {
|
||||||
fputc(SYMTYPE_IMPORT, f);
|
putc(SYMTYPE_IMPORT, f);
|
||||||
} else {
|
} else {
|
||||||
assert(sym->src->ID != -1);
|
assert(sym->src->ID != -1);
|
||||||
|
|
||||||
fputc(sym->isExported ? SYMTYPE_EXPORT : SYMTYPE_LOCAL, f);
|
putc(sym->isExported ? SYMTYPE_EXPORT : SYMTYPE_LOCAL, f);
|
||||||
fputlong(sym->src->ID, f);
|
putlong(sym->src->ID, f);
|
||||||
fputlong(sym->fileLine, f);
|
putlong(sym->fileLine, f);
|
||||||
fputlong(getSectIDIfAny(sym_GetSection(sym)), f);
|
putlong(getSectIDIfAny(sym_GetSection(sym)), f);
|
||||||
fputlong(sym->value, f);
|
putlong(sym->value, f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,10 +409,15 @@ static struct Patch *allocpatch(uint32_t type, struct Expression const *expr, ui
|
|||||||
/*
|
/*
|
||||||
* Create a new patch (includes the rpn expr)
|
* Create a new patch (includes the rpn expr)
|
||||||
*/
|
*/
|
||||||
void out_CreatePatch(uint32_t type, struct Expression const *expr, uint32_t ofs)
|
void out_CreatePatch(uint32_t type, struct Expression const *expr, uint32_t ofs, uint32_t pcShift)
|
||||||
{
|
{
|
||||||
struct Patch *patch = allocpatch(type, expr, ofs);
|
struct Patch *patch = allocpatch(type, expr, ofs);
|
||||||
|
|
||||||
|
// If the patch had a quantity of bytes output before it,
|
||||||
|
// PC is not at the patch's location, but at the location
|
||||||
|
// before those bytes.
|
||||||
|
patch->pcOffset -= pcShift;
|
||||||
|
|
||||||
patch->next = pCurrentSection->patches;
|
patch->next = pCurrentSection->patches;
|
||||||
pCurrentSection->patches = patch;
|
pCurrentSection->patches = patch;
|
||||||
}
|
}
|
||||||
@@ -446,30 +449,32 @@ bool out_CreateAssert(enum AssertionType type, struct Expression const *expr,
|
|||||||
static void writeassert(struct Assertion *assert, FILE *f)
|
static void writeassert(struct Assertion *assert, FILE *f)
|
||||||
{
|
{
|
||||||
writepatch(assert->patch, f);
|
writepatch(assert->patch, f);
|
||||||
fputstring(assert->message, f);
|
putstring(assert->message, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void writeFileStackNode(struct FileStackNode const *node, FILE *f)
|
static void writeFileStackNode(struct FileStackNode const *node, FILE *f)
|
||||||
{
|
{
|
||||||
fputlong(node->parent ? node->parent->ID : -1, f);
|
putlong(node->parent ? node->parent->ID : -1, f);
|
||||||
fputlong(node->lineNo, f);
|
putlong(node->lineNo, f);
|
||||||
fputc(node->type, f);
|
putc(node->type, f);
|
||||||
if (node->type != NODE_REPT) {
|
if (node->type != NODE_REPT) {
|
||||||
fputstring(((struct FileStackNamedNode const *)node)->name, f);
|
putstring(((struct FileStackNamedNode const *)node)->name, f);
|
||||||
} else {
|
} else {
|
||||||
struct FileStackReptNode const *reptNode = (struct FileStackReptNode const *)node;
|
struct FileStackReptNode const *reptNode = (struct FileStackReptNode const *)node;
|
||||||
|
|
||||||
fputlong(reptNode->reptDepth, f);
|
putlong(reptNode->reptDepth, f);
|
||||||
/* Iters are stored by decreasing depth, so reverse the order for output */
|
/* Iters are stored by decreasing depth, so reverse the order for output */
|
||||||
for (uint32_t i = reptNode->reptDepth; i--; )
|
for (uint32_t i = reptNode->reptDepth; i--; )
|
||||||
fputlong(reptNode->iters[i], f);
|
putlong(reptNode->iters[i], f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void registerExportedSymbol(struct Symbol *symbol, void *arg)
|
static void registerUnregisteredSymbol(struct Symbol *symbol, void *arg)
|
||||||
{
|
{
|
||||||
(void)arg;
|
(void)arg; // sym_ForEach requires a void* parameter, but we are not using it.
|
||||||
if (sym_IsExported(symbol) && symbol->ID == -1) {
|
|
||||||
|
// Check for symbol->src, to skip any built-in symbol from rgbasm
|
||||||
|
if (symbol->src && symbol->ID == -1) {
|
||||||
registerSymbol(symbol);
|
registerSymbol(symbol);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -488,16 +493,16 @@ void out_WriteObject(void)
|
|||||||
if (!f)
|
if (!f)
|
||||||
err(1, "Couldn't write file '%s'", tzObjectname);
|
err(1, "Couldn't write file '%s'", tzObjectname);
|
||||||
|
|
||||||
/* Also write exported symbols that weren't written above */
|
/* Also write symbols that weren't written above */
|
||||||
sym_ForEach(registerExportedSymbol, NULL);
|
sym_ForEach(registerUnregisteredSymbol, NULL);
|
||||||
|
|
||||||
fprintf(f, RGBDS_OBJECT_VERSION_STRING, RGBDS_OBJECT_VERSION_NUMBER);
|
fprintf(f, RGBDS_OBJECT_VERSION_STRING, RGBDS_OBJECT_VERSION_NUMBER);
|
||||||
fputlong(RGBDS_OBJECT_REV, f);
|
putlong(RGBDS_OBJECT_REV, f);
|
||||||
|
|
||||||
fputlong(nbSymbols, f);
|
putlong(nbSymbols, f);
|
||||||
fputlong(countsections(), f);
|
putlong(countSections(), f);
|
||||||
|
|
||||||
fputlong(getNbFileStackNodes(), f);
|
putlong(getNbFileStackNodes(), f);
|
||||||
for (struct FileStackNode const *node = fileStackNodes; node; node = node->next) {
|
for (struct FileStackNode const *node = fileStackNodes; node; node = node->next) {
|
||||||
writeFileStackNode(node, f);
|
writeFileStackNode(node, f);
|
||||||
if (node->next && node->next->ID != node->ID - 1)
|
if (node->next && node->next->ID != node->ID - 1)
|
||||||
@@ -512,7 +517,7 @@ void out_WriteObject(void)
|
|||||||
for (struct Section *sect = pSectionList; sect; sect = sect->next)
|
for (struct Section *sect = pSectionList; sect; sect = sect->next)
|
||||||
writesection(sect, f);
|
writesection(sect, f);
|
||||||
|
|
||||||
fputlong(countasserts(), f);
|
putlong(countAsserts(), f);
|
||||||
for (struct Assertion *assert = assertions; assert;
|
for (struct Assertion *assert = assertions; assert;
|
||||||
assert = assert->next)
|
assert = assert->next)
|
||||||
writeassert(assert, f);
|
writeassert(assert, f);
|
||||||
|
|||||||
1287
src/asm/parser.y
1287
src/asm/parser.y
File diff suppressed because it is too large
Load Diff
@@ -179,9 +179,9 @@ Enables literally every warning.
|
|||||||
.Pp
|
.Pp
|
||||||
The following warnings are actual warning flags; with each description, the corresponding warning flag is included.
|
The following warnings are actual warning flags; with each description, the corresponding warning flag is included.
|
||||||
Note that each of these flag also has a negation (for example,
|
Note that each of these flag also has a negation (for example,
|
||||||
.Fl Wempty-entry
|
.Fl Wcharmap-redef
|
||||||
enables the warning that
|
enables the warning that
|
||||||
.Fl Wno-empty-entry
|
.Fl Wno-charmap-redef
|
||||||
disables).
|
disables).
|
||||||
Only the non-default flag is listed here.
|
Only the non-default flag is listed here.
|
||||||
Ignoring the
|
Ignoring the
|
||||||
@@ -209,12 +209,16 @@ This warning is enabled by
|
|||||||
.Fl Wall .
|
.Fl Wall .
|
||||||
.It Fl Wdiv
|
.It Fl Wdiv
|
||||||
Warn when dividing the smallest negative integer by -1, which yields itself due to integer overflow.
|
Warn when dividing the smallest negative integer by -1, which yields itself due to integer overflow.
|
||||||
.It Fl Wempty-entry
|
.It Fl Wempty-macro-arg
|
||||||
Warn when an empty entry is encountered in a
|
Warn when a macro argument is empty.
|
||||||
.Ic db , dw , dl
|
|
||||||
list.
|
|
||||||
This warning is enabled by
|
This warning is enabled by
|
||||||
.Fl Wextra .
|
.Fl Wextra .
|
||||||
|
.It Fl Wempty-strrpl
|
||||||
|
Warn when
|
||||||
|
.Fn STRRPL
|
||||||
|
is called with an empty string as its second argument (the substring to replace).
|
||||||
|
This warning is enabled by
|
||||||
|
.Fl Wall .
|
||||||
.It Fl Wlarge-constant
|
.It Fl Wlarge-constant
|
||||||
Warn when a constant too large to fit in a signed 32-bit integer is encountered.
|
Warn when a constant too large to fit in a signed 32-bit integer is encountered.
|
||||||
This warning is enabled by
|
This warning is enabled by
|
||||||
@@ -223,12 +227,16 @@ This warning is enabled by
|
|||||||
Warn when a string too long to fit in internal buffers is encountered.
|
Warn when a string too long to fit in internal buffers is encountered.
|
||||||
This warning is enabled by
|
This warning is enabled by
|
||||||
.Fl Wall .
|
.Fl Wall .
|
||||||
|
.It Fl Wmacro-shift
|
||||||
|
Warn when shifting macro arguments past their limits.
|
||||||
|
This warning is enabled by
|
||||||
|
.Fl Wextra .
|
||||||
.It Fl Wno-obsolete
|
.It Fl Wno-obsolete
|
||||||
Warn when obsolete constructs such as the
|
Warn when obsolete constructs such as the
|
||||||
.Ic jp [hl]
|
.Ic _PI
|
||||||
instruction or
|
constant or
|
||||||
.Ic HOME
|
.Ic PRINTT
|
||||||
section type are encountered.
|
directive are encountered.
|
||||||
.It Fl Wshift
|
.It Fl Wshift
|
||||||
Warn when shifting right a negative value.
|
Warn when shifting right a negative value.
|
||||||
Use a division by 2^N instead.
|
Use a division by 2^N instead.
|
||||||
|
|||||||
610
src/asm/rgbasm.5
610
src/asm/rgbasm.5
@@ -29,7 +29,7 @@ but any program that processes RGB object files (described in
|
|||||||
.Xr rgbds 5 )
|
.Xr rgbds 5 )
|
||||||
can be used in its place.
|
can be used in its place.
|
||||||
.Sh SYNTAX
|
.Sh SYNTAX
|
||||||
The syntax is line‐based, just as in any other assembler, meaning that you do one instruction or pseudo‐op per line:
|
The syntax is line‐based, just as in any other assembler, meaning that you do one instruction or directive per line:
|
||||||
.Pp
|
.Pp
|
||||||
.Dl Oo Ar label Oc Oo Ar instruction Oc Oo Ar ;\ comment Oc
|
.Dl Oo Ar label Oc Oo Ar instruction Oc Oo Ar ;\ comment Oc
|
||||||
.Pp
|
.Pp
|
||||||
@@ -38,14 +38,15 @@ Example:
|
|||||||
John: ld a,87 ;Weee
|
John: ld a,87 ;Weee
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
All reserved keywords (pseudo‐ops, mnemonics, registers etc.) are case‐insensitive, all identifiers (symbol names) are case-sensitive.
|
All reserved keywords (directives, mnemonics, registers, etc.) are case‐insensitive;
|
||||||
|
all identifiers (symbol names) are case-sensitive.
|
||||||
.Pp
|
.Pp
|
||||||
Comments are used to give humans information about the code, such as explanations.
|
Comments are used to give humans information about the code, such as explanations.
|
||||||
The assembler
|
The assembler
|
||||||
.Em always
|
.Em always
|
||||||
ignores comments and their contents.
|
ignores comments and their contents.
|
||||||
.Pp
|
.Pp
|
||||||
There are three syntaxes for comments.
|
There are two syntaxes for comments.
|
||||||
The most common is that anything that follows a semicolon
|
The most common is that anything that follows a semicolon
|
||||||
.Ql \&;
|
.Ql \&;
|
||||||
not inside a string, is a comment until the end of the line.
|
not inside a string, is a comment until the end of the line.
|
||||||
@@ -58,10 +59,6 @@ It can be split across multiple lines, or occur in the middle of an expression:
|
|||||||
X = /* the value of x
|
X = /* the value of x
|
||||||
should be 3 */ 3
|
should be 3 */ 3
|
||||||
.Ed
|
.Ed
|
||||||
The third is that lines beginning with a
|
|
||||||
.Ql *
|
|
||||||
(not even spaces before it) are ignored.
|
|
||||||
This third syntax is deprecated (will be removed in a future version) and should be replaced with either of the first two.
|
|
||||||
.Pp
|
.Pp
|
||||||
Sometimes lines can be too long and it may be necessary to split them.
|
Sometimes lines can be too long and it may be necessary to split them.
|
||||||
To do so, put a backslash at the end of the line:
|
To do so, put a backslash at the end of the line:
|
||||||
@@ -105,6 +102,8 @@ There are a number of numeric formats.
|
|||||||
.It Gameboy graphics Ta \` Ta 0123
|
.It Gameboy graphics Ta \` Ta 0123
|
||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
|
Underscores are also accepted in numbers, except at the beginning of one.
|
||||||
|
.Pp
|
||||||
The "character constant" form yields the value the character maps to in the current charmap.
|
The "character constant" form yields the value the character maps to in the current charmap.
|
||||||
For example, by default
|
For example, by default
|
||||||
.Pq refer to Xr ascii 7
|
.Pq refer to Xr ascii 7
|
||||||
@@ -129,6 +128,7 @@ A great number of operators you can use in expressions are available (listed fro
|
|||||||
.It Sy Operator Ta Sy Meaning
|
.It Sy Operator Ta Sy Meaning
|
||||||
.It Li \&( \&) Ta Precedence override
|
.It Li \&( \&) Ta Precedence override
|
||||||
.It Li FUNC() Ta Built-in function call
|
.It Li FUNC() Ta Built-in function call
|
||||||
|
.It Li ** Ta Exponent
|
||||||
.It Li ~ + - Ta Unary complement/plus/minus
|
.It Li ~ + - Ta Unary complement/plus/minus
|
||||||
.It Li * / % Ta Multiply/divide/modulo
|
.It Li * / % Ta Multiply/divide/modulo
|
||||||
.It Li << >> Ta Shift left/right
|
.It Li << >> Ta Shift left/right
|
||||||
@@ -143,9 +143,16 @@ A great number of operators you can use in expressions are available (listed fro
|
|||||||
complements a value by inverting all its bits.
|
complements a value by inverting all its bits.
|
||||||
.Pp
|
.Pp
|
||||||
.Ic %
|
.Ic %
|
||||||
is used to get the remainder of the corresponding division.
|
is used to get the remainder of the corresponding division, so that
|
||||||
.Sq 5 % 2
|
.Sq a / b * b + a % b == a
|
||||||
is 1.
|
is always true.
|
||||||
|
The result has the same sign as the divisor.
|
||||||
|
This makes
|
||||||
|
.Sq a % b .
|
||||||
|
equal to
|
||||||
|
.Sq (a + b) % b
|
||||||
|
or
|
||||||
|
.Sq (a - b) % b .
|
||||||
.Pp
|
.Pp
|
||||||
Shifting works by shifting all bits in the left operand either left
|
Shifting works by shifting all bits in the left operand either left
|
||||||
.Pq Sq <<
|
.Pq Sq <<
|
||||||
@@ -168,7 +175,8 @@ still evaluates both operands of
|
|||||||
and
|
and
|
||||||
.Sq || .
|
.Sq || .
|
||||||
.Pp
|
.Pp
|
||||||
! returns 1 if the operand was 0, and 0 otherwise.
|
.Ic \&!
|
||||||
|
returns 1 if the operand was 0, and 0 otherwise.
|
||||||
.Ss Fixed‐point Expressions
|
.Ss Fixed‐point Expressions
|
||||||
Fixed-point numbers are basically normal (32-bit) integers, which count 65536th's instead of entire units, offering better precision than integers but limiting the range of values.
|
Fixed-point numbers are basically normal (32-bit) integers, which count 65536th's instead of entire units, offering better precision than integers but limiting the range of values.
|
||||||
The upper 16 bits are used for the integer part and the lower 16 bits are used for the fraction (65536ths).
|
The upper 16 bits are used for the integer part and the lower 16 bits are used for the fraction (65536ths).
|
||||||
@@ -188,29 +196,38 @@ delim $$
|
|||||||
.It Sy Name Ta Sy Operation
|
.It Sy Name Ta Sy Operation
|
||||||
.It Fn DIV x y Ta $x \[di] y$
|
.It Fn DIV x y Ta $x \[di] y$
|
||||||
.It Fn MUL x y Ta $x \[mu] y$
|
.It Fn MUL x y Ta $x \[mu] y$
|
||||||
.It Fn SIN x Ta $sin ( x )$
|
.It Fn POW x y Ta $x$ to the $y$ power
|
||||||
.It Fn COS x Ta $cos ( x )$
|
.It Fn LOG x y Ta Logarithm of $x$ to the base $y$
|
||||||
.It Fn TAN x Ta $tan ( x )$
|
.It Fn ROUND x Ta Round $x$ to the nearest integer
|
||||||
.It Fn ASIN x Ta $asin ( x )$
|
.It Fn CEIL x Ta Round $x$ up to an integer
|
||||||
.It Fn ACOS x Ta $acos ( x )$
|
.It Fn FLOOR x Ta Round $x$ down to an integer
|
||||||
.It Fn ATAN x Ta $atan ( x )$
|
.It Fn SIN x Ta Sine of $x$
|
||||||
|
.It Fn COS x Ta Cosine of $x$
|
||||||
|
.It Fn TAN x Ta Tangent of $x$
|
||||||
|
.It Fn ASIN x Ta Inverse sine of $x$
|
||||||
|
.It Fn ACOS x Ta Inverse cosine of $x$
|
||||||
|
.It Fn ATAN x Ta Inverse tangent of $x$
|
||||||
.It Fn ATAN2 x y Ta Angle between $( x , y )$ and $( 1 , 0 )$
|
.It Fn ATAN2 x y Ta Angle between $( x , y )$ and $( 1 , 0 )$
|
||||||
.El
|
.El
|
||||||
.EQ
|
.EQ
|
||||||
delim off
|
delim off
|
||||||
.EN
|
.EN
|
||||||
.Pp
|
.Pp
|
||||||
|
The trigonometry functions (
|
||||||
|
.Ic SIN ,
|
||||||
|
.Ic COS ,
|
||||||
|
.Ic TAN ,
|
||||||
|
etc) are defined in terms of a circle divided into 65535.0 degrees.
|
||||||
|
.Pp
|
||||||
These functions are useful for automatic generation of various tables.
|
These functions are useful for automatic generation of various tables.
|
||||||
Example: assuming a circle has 65536.0 degrees, and sine values are in range
|
For example:
|
||||||
.Bq -1.0 ;\ 1.0 :
|
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
;\ --
|
; Generate a 256-byte sine table with values in the range [0, 128]
|
||||||
;\ -- Generate a 256-byte sine table with values between 0 and 128
|
; (shifted and scaled from the range [-1.0, 1.0])
|
||||||
;\ --
|
|
||||||
ANGLE = 0.0
|
ANGLE = 0.0
|
||||||
REPT 256
|
REPT 256
|
||||||
db MUL(64.0, SIN(ANGLE) + 1.0) >> 16
|
db (MUL(64.0, SIN(ANGLE)) + 64.0) >> 16
|
||||||
ANGLE = ANGLE + 256.0 ; 256 = 65536 / table_len, with table_len = 256
|
ANGLE = ANGLE + 256.0 ; 256.0 = 65536 degrees / 256 entries
|
||||||
ENDR
|
ENDR
|
||||||
.Ed
|
.Ed
|
||||||
.Ss String Expressions
|
.Ss String Expressions
|
||||||
@@ -226,17 +243,25 @@ There are a number of escape sequences you can use within a string:
|
|||||||
.It Sy String Ta Sy Meaning
|
.It Sy String Ta Sy Meaning
|
||||||
.It Ql \[rs]\[rs] Ta Produces a backslash
|
.It Ql \[rs]\[rs] Ta Produces a backslash
|
||||||
.It Ql \[rs]" Ta Produces a double quote without terminating
|
.It Ql \[rs]" Ta Produces a double quote without terminating
|
||||||
.It Ql \[rs], Ta Comma
|
|
||||||
.It Ql \[rs]{ Ta Curly bracket left
|
.It Ql \[rs]{ Ta Curly bracket left
|
||||||
.It Ql \[rs]} Ta Curly bracket right
|
.It Ql \[rs]} Ta Curly bracket right
|
||||||
.It Ql \[rs]n Ta Newline ($0A)
|
.It Ql \[rs]n Ta Newline ($0A)
|
||||||
.It Ql \[rs]r Ta Carriage return ($0D)
|
.It Ql \[rs]r Ta Carriage return ($0D)
|
||||||
.It Ql \[rs]t Ta Tab ($09)
|
.It Ql \[rs]t Ta Tab ($09)
|
||||||
.It Qo \[rs]1 Qc \[en] Qo \[rs]9 Qc Ta Macro argument (Only the body of a macro, see Sx Invoking macros )
|
.It Qo \[rs]1 Qc \[en] Qo \[rs]9 Qc Ta Macro argument (Only in the body of a macro; see Sx Invoking macros )
|
||||||
.It Ql \[rs]@ Ta Label name suffix (Only in the body of macros and REPTs)
|
.It Ql \[rs]# Ta All Dv _NARG No macro arguments, separated by commas (Only in the body of a macro)
|
||||||
|
.It Ql \[rs]@ Ta Label name suffix (Only in the body of a macro or a Ic REPT No block)
|
||||||
.El
|
.El
|
||||||
(Note that some of those can be used outside of strings, when noted further in this document.)
|
(Note that some of those can be used outside of strings, when noted further in this document.)
|
||||||
.Pp
|
.Pp
|
||||||
|
Multi-line strings are contained in triple quotes
|
||||||
|
.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
|
||||||
|
.Ql \[rs]r
|
||||||
|
or
|
||||||
|
.Ql \[rs]n .
|
||||||
|
.Pp
|
||||||
A funky feature is
|
A funky feature is
|
||||||
.Ql {symbol}
|
.Ql {symbol}
|
||||||
within a string, called
|
within a string, called
|
||||||
@@ -249,55 +274,138 @@ If it's a numeric symbol, its value is converted to hexadecimal notation with a
|
|||||||
.Sq $
|
.Sq $
|
||||||
prepended.
|
prepended.
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
TOPIC equs "life, the universe, and everything"
|
TOPIC equs "life, the universe, and \[rs]"everything\[rs]""
|
||||||
ANSWER = 42
|
ANSWER = 42
|
||||||
;\ Prints "The answer to life, the universe, and everything is $2A"
|
;\ Prints "The answer to life, the universe, and "everything" is $2A"
|
||||||
PRINTT "The answer to {TOPIC} is {ANSWER}\[rs]n"
|
PRINTLN "The answer to {TOPIC} is {ANSWER}"
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
Symbol interpolations can be nested, too!
|
Symbol interpolations can be nested, too!
|
||||||
.Pp
|
.Pp
|
||||||
It's possible to change the way numeric symbols are converted by specifying a print type like so:
|
It's possible to change the way symbols are converted by specifying a print format like so:
|
||||||
.Ql {d:symbol} .
|
.Ql {fmt:symbol} .
|
||||||
|
The
|
||||||
|
.Ql fmt
|
||||||
|
specifier consists of parts
|
||||||
|
.Ql <sign><prefix><align><pad><width><frac><type> .
|
||||||
|
These parts are:
|
||||||
|
.Bl -column "<prefix>"
|
||||||
|
.It Sy Part Ta Sy Meaning
|
||||||
|
.It Ql <sign> Ta May be
|
||||||
|
.Ql +
|
||||||
|
or
|
||||||
|
.Ql \ .
|
||||||
|
If specified, prints this character in front of non-negative numbers.
|
||||||
|
.It Ql <prefix> Ta May be
|
||||||
|
.Ql # .
|
||||||
|
If specified, prints the appropriate prefix for numbers,
|
||||||
|
.Ql $ ,
|
||||||
|
.Ql & ,
|
||||||
|
or
|
||||||
|
.Ql % .
|
||||||
|
.It Ql <align> Ta May be
|
||||||
|
.Ql - .
|
||||||
|
If specified, aligns left instead of right.
|
||||||
|
.It Ql <pad> Ta May be
|
||||||
|
.Ql 0 .
|
||||||
|
If specified, pads right-aligned numbers with zeros instead of spaces.
|
||||||
|
.It Ql <width> Ta May be one or more
|
||||||
|
.Ql 0
|
||||||
|
\[en]
|
||||||
|
.Ql 9 .
|
||||||
|
If specified, pads the value to this width, right-aligned with spaces by default.
|
||||||
|
.It Ql <frac> Ta May be
|
||||||
|
.Ql \&.
|
||||||
|
followed by one or more
|
||||||
|
.Ql 0
|
||||||
|
\[en]
|
||||||
|
.Ql 9 .
|
||||||
|
If specified, prints this many digits of a fixed-point fraction.
|
||||||
|
Defaults to 5 digits.
|
||||||
|
.It Ql <type> Ta Specifies the type of value.
|
||||||
|
.El
|
||||||
|
.Pp
|
||||||
|
All the format specifier parts are optional except the
|
||||||
|
.Ql <type> .
|
||||||
Valid print types are:
|
Valid print types are:
|
||||||
.Bl -column -offset indent "Print type" "Lowercase hexadecimal" "Example"
|
.Bl -column -offset indent "Print type" "Lowercase hexadecimal" "Example"
|
||||||
.It Sy Print type Ta Sy Format Ta Sy Example
|
.It Sy Print type Ta Sy Format Ta Sy Example
|
||||||
.It Ql d Ta Decimal Ta 42
|
.It Ql d Ta Signed decimal Ta -42
|
||||||
|
.It Ql u Ta Unsigned decimal Ta 42
|
||||||
.It Ql x Ta Lowercase hexadecimal Ta 2a
|
.It Ql x Ta Lowercase hexadecimal Ta 2a
|
||||||
.It Ql X Ta Uppercase hexadecimal Ta 2A
|
.It Ql X Ta Uppercase hexadecimal Ta 2A
|
||||||
.It Ql b Ta Binary Ta 101010
|
.It Ql b Ta Binary Ta 101010
|
||||||
|
.It Ql o Ta Octal Ta 52
|
||||||
|
.It Ql f Ta Fixed-point Ta 1234.56789
|
||||||
|
.It Ql s Ta String Ta \&"example\&"
|
||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
Note that print types should only be used with numeric values, not strings.
|
Examples:
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
; Prints "%0010 + $3 == 5"
|
||||||
|
PRINTLN STRFMT("%#05b + %#x == %d", 2, 3, 2+3)
|
||||||
|
; Prints "32% of 20 = 6.40"
|
||||||
|
PRINTLN STRFMT("%d%% of %d = %.2f", 32, 20, MUL(20.0, 0.32))
|
||||||
|
; Prints "Hello world!"
|
||||||
|
PRINTLN STRFMT("Hello %s!", STRLWR("WORLD"))
|
||||||
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
HINT: The
|
HINT: The
|
||||||
.Ic {symbol}
|
.Ic {symbol}
|
||||||
construct can also be used outside strings.
|
construct can also be used outside strings.
|
||||||
The symbol's value is again inserted directly.
|
The symbol's value is again inserted directly.
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
NAME equs "ITEM"
|
||||||
|
FMT equs "d"
|
||||||
|
ZERO_NUM equ 0
|
||||||
|
ZERO_STR equs "0"
|
||||||
|
;\ Defines INDEX as 100
|
||||||
|
INDEX = 1{ZERO_STR}{{FMT}:ZERO_NUM}
|
||||||
|
;\ Defines ITEM_100 as "\[rs]"hundredth\[rs]""
|
||||||
|
{NAME}_{d:INDEX} equs "\[rs]"hundredth\[rs]""
|
||||||
|
;\ Prints "ITEM_100 is hundredth"
|
||||||
|
PRINTLN STRCAT("{NAME}_{d:INDEX} is ", {NAME}_{d:INDEX})
|
||||||
|
;\ Purges ITEM_100
|
||||||
|
PURGE {NAME}_{d:INDEX}
|
||||||
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
The following functions operate on string expressions.
|
The following functions operate on string expressions.
|
||||||
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!
|
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!
|
||||||
.Bl -column "STRSUB(str, pos, len)"
|
.Bl -column "STRSUB(str, pos, len)"
|
||||||
.It Sy Name Ta Sy Operation
|
.It Sy Name Ta Sy Operation
|
||||||
.It Fn STRLEN string Ta Returns the number of characters in Ar string .
|
.It Fn STRLEN str Ta Returns the number of characters in Ar str .
|
||||||
.It Fn STRCAT str1 str2 Ta Appends Ar str2 No to Ar str1 .
|
.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 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 position of Ar str2 No in Ar str1 No or zero if it's not present Pq first character is position 1 .
|
.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 Po first character is position 1 Pc and Ar len No characters long.
|
.It Fn STRSUB str pos len Ta Returns a substring from Ar str No starting at Ar pos Po first character is position 1 Pc and Ar len No characters long.
|
||||||
.It Fn STRUPR str Ta Converts all characters in Ar str No to capitals and returns the new string.
|
.It Fn STRUPR str Ta Returns Ar str No with all letters in uppercase.
|
||||||
.It Fn STRLWR str Ta Converts all characters in Ar str No to lower case and returns the new string.
|
.It Fn STRLWR str Ta Returns Ar str No with all letters in lowercase.
|
||||||
|
.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
|
||||||
|
.Ql %spec
|
||||||
|
pattern replaced by interpolating the format
|
||||||
|
.Ar spec
|
||||||
|
with its corresponding argument in
|
||||||
|
.Ar args
|
||||||
|
.Pq So %% Sc is replaced by the So % Sc character .
|
||||||
.El
|
.El
|
||||||
.Ss Character maps
|
.Ss Character maps
|
||||||
When writing text that is meant to be displayed in the Game Boy, the characters used in the source code may have a different encoding than the default of ASCII.
|
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.
|
||||||
For example, the tiles used for uppercase letters may be placed starting at tile index 128, which makes it difficult to add text strings to the ROM.
|
For example, the tiles used for uppercase letters may be placed starting at tile index 128, which differs from ASCII starting at 65.
|
||||||
.Pp
|
.Pp
|
||||||
Character maps allow mapping strings up to 16 characters long to an abitrary 8-bit value:
|
Character maps allow mapping strings to arbitrary 8-bit values:
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
CHARMAP "<LF>", 10
|
CHARMAP "<LF>", 10
|
||||||
CHARMAP "í", 20
|
CHARMAP "í", 20
|
||||||
CHARMAP "A", 128
|
CHARMAP "A", 128
|
||||||
.Ed
|
.Ed
|
||||||
By default, a character map contains ASCII encoding.
|
This would result in
|
||||||
|
.Ql db \(dqAmen<LF>\(dq
|
||||||
|
being equivalent to
|
||||||
|
.Ql db 128, 109, 101, 110, 10 .
|
||||||
|
.Pp
|
||||||
|
Any characters in a string without defined mappings will be copied directly, using the source file's encoding of characters to bytes.
|
||||||
.Pp
|
.Pp
|
||||||
It is possible to create multiple character maps and then switch between them as desired.
|
It is possible to create multiple character maps and then switch between them as desired.
|
||||||
This can be used to encode debug information in ASCII and use a different encoding for other purposes, for example.
|
This can be used to encode debug information in ASCII and use a different encoding for other purposes, for example.
|
||||||
@@ -307,23 +415,18 @@ and it is automatically selected as the current character map from the beginning
|
|||||||
There is also a character map stack that can be used to save and restore which character map is currently active.
|
There is also a character map stack that can be used to save and restore which character map is currently active.
|
||||||
.Bl -column "NEWCHARMAP name, basename"
|
.Bl -column "NEWCHARMAP name, basename"
|
||||||
.It Sy Command Ta Sy Meaning
|
.It Sy Command Ta Sy Meaning
|
||||||
.It Ic NEWCHARMAP Ar name Ta Creates a new, empty character map called Ar name .
|
.It Ic NEWCHARMAP Ar name Ta Creates a new, empty character map called Ar name No and switches to it.
|
||||||
.It Ic NEWCHARMAP Ar name , basename Ta Creates a new character map called Ar name , No copied from character map Ar basename .
|
.It Ic NEWCHARMAP Ar name , basename Ta Creates a new character map called Ar name , No copied from character map Ar basename , No and switches to it.
|
||||||
.It Ic SETCHARMAP Ar name Ta Switch to character map Ar name .
|
.It Ic SETCHARMAP Ar name Ta Switch to character map Ar name .
|
||||||
.It Ic PUSHC Ta Push the current character map onto the stack.
|
.It Ic PUSHC Ta Push the current character map onto the stack.
|
||||||
.It Ic POPC Ta Pop a character map off the stack and switch to it.
|
.It Ic POPC Ta Pop a character map off the stack and switch to it.
|
||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
.Sy Note:
|
.Sy Note:
|
||||||
Character maps affect all strings in the file from the point in which they are defined, until switching to a different character map.
|
Modifications to a character map take effect immediately from that point onward.
|
||||||
This means that any string that the code may want to print as debug information will also be affected by it.
|
|
||||||
.Pp
|
|
||||||
.Sy Note:
|
|
||||||
The output value of a mapping can be 0.
|
|
||||||
If this happens, the assembler will treat this as the end of the string and the rest of it will be trimmed.
|
|
||||||
.Ss Other functions
|
.Ss Other functions
|
||||||
There are a few other functions that do various useful things:
|
There are a few other functions that do various useful things:
|
||||||
.Bl -column "DEF(label)"
|
.Bl -column "DEF(symbol)"
|
||||||
.It Sy Name Ta Sy Operation
|
.It Sy Name Ta Sy Operation
|
||||||
.It Fn BANK arg Ta Returns a bank number.
|
.It Fn BANK arg Ta Returns a bank number.
|
||||||
If
|
If
|
||||||
@@ -340,8 +443,8 @@ is a label, it returns the bank number the label is in.
|
|||||||
The result may be constant if
|
The result may be constant if
|
||||||
.Nm
|
.Nm
|
||||||
is able to compute it.
|
is able to compute it.
|
||||||
.It Fn DEF label Ta Returns TRUE (1) if
|
.It Fn DEF symbol Ta Returns TRUE (1) if
|
||||||
.Ar label
|
.Ar symbol
|
||||||
has been defined, FALSE (0) otherwise.
|
has been defined, FALSE (0) otherwise.
|
||||||
String symbols are not expanded within the parentheses.
|
String symbols are not expanded within the parentheses.
|
||||||
.It Fn HIGH arg Ta Returns the top 8 bits of the operand if Ar arg No is a label or constant, or the top 8-bit register if it is a 16-bit register.
|
.It Fn HIGH arg Ta Returns the top 8 bits of the operand if Ar arg No is a label or constant, or the top 8-bit register if it is a 16-bit register.
|
||||||
@@ -623,6 +726,13 @@ The former is situated in ROM, where the code is stored, the latter in RAM, wher
|
|||||||
You cannot nest
|
You cannot nest
|
||||||
.Ic LOAD
|
.Ic LOAD
|
||||||
blocks, nor can you change the current section within them.
|
blocks, nor can you change the current section within them.
|
||||||
|
.Pp
|
||||||
|
.Ic LOAD
|
||||||
|
blocks can use the
|
||||||
|
.Ic UNION
|
||||||
|
or
|
||||||
|
.Ic FRAGMENT
|
||||||
|
modifiers, as described below.
|
||||||
.Ss Unionized Sections
|
.Ss Unionized Sections
|
||||||
When you're tight on RAM, you may want to define overlapping blocks of variables, as explained in the
|
When you're tight on RAM, you may want to define overlapping blocks of variables, as explained in the
|
||||||
.Sx Unions
|
.Sx Unions
|
||||||
@@ -729,7 +839,7 @@ A block of
|
|||||||
.Nm
|
.Nm
|
||||||
code that can be invoked later.
|
code that can be invoked later.
|
||||||
.It Sy String equate
|
.It Sy String equate
|
||||||
String symbol that can be evaluated, similarly to a macro.
|
A text string that can be expanded later, similarly to a macro.
|
||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
Symbol names can contain letters, numbers, underscores
|
Symbol names can contain letters, numbers, underscores
|
||||||
@@ -738,50 +848,89 @@ hashes
|
|||||||
.Sq #
|
.Sq #
|
||||||
and at signs
|
and at signs
|
||||||
.Sq @ .
|
.Sq @ .
|
||||||
However, they must begin with either a letter, or an underscore.
|
However, they must begin with either a letter or an underscore.
|
||||||
Periods
|
Periods
|
||||||
.Sq \&.
|
.Sq \&.
|
||||||
are allowed exclusively for labels, as described below.
|
are allowed exclusively in labels, as described below.
|
||||||
A symbol cannot have the same name as a reserved keyword.
|
A symbol cannot have the same name as a reserved keyword.
|
||||||
.Em \&In the line where a symbol is defined there must not be any whitespace before it ,
|
.Pp
|
||||||
|
Constants and string equates
|
||||||
|
.Em must not
|
||||||
|
have any whitespace before their name when they are defined;
|
||||||
otherwise
|
otherwise
|
||||||
.Nm
|
.Nm
|
||||||
will treat it as a macro invocation.
|
will treat them as a macro invocation.
|
||||||
|
Label and macro definitions may have whitespace before them, since a leading period or a following colon distinguishes them from invoking a macro.
|
||||||
.Bl -tag -width indent
|
.Bl -tag -width indent
|
||||||
.It Sy Label declaration
|
.It Sy Label declaration
|
||||||
One of the assembler's main tasks is to keep track of addresses for you, so you can work with meaningful names instead of "magic" numbers.
|
One of the assembler's main tasks is to keep track of addresses for you, so you can work with meaningful names instead of "magic" numbers.
|
||||||
.Pp
|
.Pp
|
||||||
This can be done in a number of ways:
|
This can be done in a number of ways:
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
GlobalLabel ;\ This syntax is deprecated,
|
GlobalLabel:
|
||||||
AnotherGlobal: ;\ please use this instead
|
AnotherGlobal:
|
||||||
\&.locallabel
|
\&.locallabel
|
||||||
\&.yet_a_local:
|
\&.another_local:
|
||||||
AnotherGlobal.with_another_local:
|
AnotherGlobal.with_another_local:
|
||||||
ThisWillBeExported:: ;\ Note the two colons
|
ThisWillBeExported:: ;\ Note the two colons
|
||||||
ThisWillBeExported.too::
|
ThisWillBeExported.too::
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
Declaring a label (global or local) with
|
Any label whose name does not contain a period is a global label.
|
||||||
|
Declaring a global label sets it as the current scoped label, until the next global one.
|
||||||
|
Global labels must be followed by one or two colons.
|
||||||
|
.Pp
|
||||||
|
Any label whose name contains a single period is a local label.
|
||||||
|
Label names cannot contain more than one period.
|
||||||
|
If the period is the first character, it will have the current scoped label's name implicitly prepended.
|
||||||
|
Local labels may optionally be followed by one or two colons.
|
||||||
|
Local labels can be declared as
|
||||||
|
.Ql scoped.local
|
||||||
|
or simply as
|
||||||
|
.Ql .local .
|
||||||
|
If the former notation is used, then
|
||||||
|
.Ql scoped
|
||||||
|
must actually be the current scoped label.
|
||||||
|
.Pp
|
||||||
|
Declaring a label (global or local) with two colons
|
||||||
.Ql ::
|
.Ql ::
|
||||||
does an
|
will
|
||||||
.Ic EXPORT
|
.Ic EXPORT
|
||||||
at the same time.
|
and define it at the same time.
|
||||||
(See
|
(See
|
||||||
.Sx Exporting and importing symbols
|
.Sx Exporting and importing symbols
|
||||||
below).
|
below).
|
||||||
.Pp
|
.Pp
|
||||||
Any label whose name does not contain a period is a global label, others are locals.
|
.Sy Anonymous labels
|
||||||
Declaring a global label sets it as the current label scope until the next one; any local label whose first character is a period will have the global label's name implicitly prepended.
|
are useful for short blocks of code.
|
||||||
Local labels can be declared as
|
They are defined like normal labels, but without a name before the colon.
|
||||||
.Ql scope.local:
|
Anonymous labels are independent of label scoping, so defining one does not change the scoped label, and referencing one is not affected by the current scoped label.
|
||||||
or simply as as
|
|
||||||
.Ql .local: .
|
|
||||||
If the former notation is used, then
|
|
||||||
.Ql scope
|
|
||||||
must be the actual current scope.
|
|
||||||
.Pp
|
.Pp
|
||||||
Local labels may have whitespace before their declaration as the only exception to the rule.
|
Anonymous labels are referenced using a colon
|
||||||
|
.Ql \&:
|
||||||
|
followed by pluses
|
||||||
|
.Ql +
|
||||||
|
or minuses
|
||||||
|
.Ql - .
|
||||||
|
Thus
|
||||||
|
.Ic :+
|
||||||
|
references the next one after the expression,
|
||||||
|
.Ic :++
|
||||||
|
the one after that;
|
||||||
|
.Ic :-
|
||||||
|
references the one before the expression;
|
||||||
|
and so on.
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
ld hl, :++
|
||||||
|
: ld a, [hli] ; referenced by "jr nz"
|
||||||
|
ldh [c], a
|
||||||
|
dec c
|
||||||
|
jr nz, :-
|
||||||
|
ret
|
||||||
|
|
||||||
|
: ; referenced by "ld hl"
|
||||||
|
dw $7FFF, $1061, $03E0, $58A5
|
||||||
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
A label's location (and thus value) is usually not determined until the linking stage, so labels usually cannot be used as constants.
|
A label's location (and thus value) is usually not determined until the linking stage, so labels usually cannot be used as constants.
|
||||||
However, if the section in which the label is declared has a fixed base address, its value is known at assembly time.
|
However, if the section in which the label is declared has a fixed base address, its value is known at assembly time.
|
||||||
@@ -809,7 +958,7 @@ or its synonym
|
|||||||
.Ic = ,
|
.Ic = ,
|
||||||
defines constant symbols like
|
defines constant symbols like
|
||||||
.Ic EQU ,
|
.Ic EQU ,
|
||||||
but those constants can be re-defined.
|
but those constants can be redefined.
|
||||||
This is useful for variables in macros, for counters, etc.
|
This is useful for variables in macros, for counters, etc.
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
ARRAY_SIZE EQU 4
|
ARRAY_SIZE EQU 4
|
||||||
@@ -848,7 +997,6 @@ There are five commands in the RS group of commands:
|
|||||||
.It Ic RB Ar constexpr Ta Sets the preceding symbol to Ic _RS No and adds Ar constexpr No to Ic _RS .
|
.It Ic RB Ar constexpr Ta Sets the preceding symbol to Ic _RS No and adds Ar constexpr No to Ic _RS .
|
||||||
.It Ic RW Ar constexpr Ta Sets the preceding symbol to Ic _RS No and adds Ar constexpr No * 2 to Ic _RS .
|
.It Ic RW Ar constexpr Ta Sets the preceding symbol to Ic _RS No and adds Ar constexpr No * 2 to Ic _RS .
|
||||||
.It Ic RL Ar constexpr Ta Sets the preceding symbol to Ic _RS No and adds Ar constexpr No * 4 to Ic _RS .
|
.It Ic RL Ar constexpr Ta Sets the preceding symbol to Ic _RS No and adds Ar constexpr No * 4 to Ic _RS .
|
||||||
(In practice, this one cannot be used due to a bug).
|
|
||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
If the argument to
|
If the argument to
|
||||||
@@ -888,8 +1036,23 @@ pusha EQUS "push af\[rs]npush bc\[rs]npush de\[rs]npush hl\[rs]n"
|
|||||||
Note that colons
|
Note that colons
|
||||||
.Ql \&:
|
.Ql \&:
|
||||||
following the name are not allowed.
|
following the name are not allowed.
|
||||||
|
.Pp
|
||||||
String equates can't be exported or imported.
|
String equates can't be exported or imported.
|
||||||
.Pp
|
.Pp
|
||||||
|
String equates, like
|
||||||
|
.Ic EQU
|
||||||
|
constants, cannot be redefined.
|
||||||
|
However, the
|
||||||
|
.Ic REDEF
|
||||||
|
keyword will define or redefine a string symbol.
|
||||||
|
For example:
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
s EQUS "Hello, "
|
||||||
|
REDEF s EQUS "{s}world!"
|
||||||
|
; prints "Hello, world!"
|
||||||
|
PRINTT "{s}\n"
|
||||||
|
.Ed
|
||||||
|
.Pp
|
||||||
.Sy Important note :
|
.Sy Important note :
|
||||||
An
|
An
|
||||||
.Ic EQUS
|
.Ic EQUS
|
||||||
@@ -913,37 +1076,48 @@ Macros can be called with arguments, and can react depending on input using
|
|||||||
.Ic IF
|
.Ic IF
|
||||||
constructs.
|
constructs.
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
MyMacro: MACRO
|
MACRO MyMacro
|
||||||
ld a, 80
|
ld a, 80
|
||||||
call MyFunc
|
call MyFunc
|
||||||
ENDM
|
ENDM
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
Note that a single colon
|
The example above defines
|
||||||
|
.Ql MyMacro
|
||||||
|
as a new macro.
|
||||||
|
You may use the older syntax
|
||||||
|
.Ql MyMacro: MACRO
|
||||||
|
instead of
|
||||||
|
.Ql MACRO MyMacro ,
|
||||||
|
with a single colon
|
||||||
.Ql \&:
|
.Ql \&:
|
||||||
following the macro's name is required.
|
following the macro's name.
|
||||||
Macros can't be exported or imported.
|
Macros can't be exported or imported.
|
||||||
.Pp
|
.Pp
|
||||||
Plainly nesting macro definitions is not allowed, but this can be worked around using
|
Plainly nesting macro definitions is not allowed, but this can be worked around using
|
||||||
.Ic EQUS .
|
.Ic EQUS .
|
||||||
This won't work:
|
So this won't work:
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
outer: MACRO
|
MACRO outer
|
||||||
inner: MACRO
|
MACRO inner
|
||||||
PRINTT "Hello!\[rs]n"
|
PRINTLN "Hello!"
|
||||||
ENDM
|
ENDM
|
||||||
ENDM
|
ENDM
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
But this will:
|
But this will:
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
outer: MACRO
|
MACRO outer
|
||||||
definition equs "inner: MACRO\[rs]nPRINTT \[rs]"Hello!\[rs]\[rs]n\[rs]"\[rs]nENDM"
|
definition EQUS "MACRO inner\[rs]nPRINTLN \[rs]"Hello!\[rs]"\[rs]nENDM"
|
||||||
definition
|
definition
|
||||||
PURGE definition
|
PURGE definition
|
||||||
ENDM
|
ENDM
|
||||||
.Ed
|
.Ed
|
||||||
.El
|
.El
|
||||||
|
.Pp
|
||||||
|
Macro arguments support all the escape sequences of strings, as well as
|
||||||
|
.Ql \[rs],
|
||||||
|
to escape commas, since those otherwise separate arguments.
|
||||||
.Ss Exporting and importing symbols
|
.Ss Exporting and importing symbols
|
||||||
Importing and exporting of symbols is a feature that is very useful when your project spans many source files and, for example, you need to jump to a routine defined in another file.
|
Importing and exporting of symbols is a feature that is very useful when your project spans many source files and, for example, you need to jump to a routine defined in another file.
|
||||||
.Pp
|
.Pp
|
||||||
@@ -1001,10 +1175,6 @@ Linking failed with 1 error
|
|||||||
Note also that only exported symbols will appear in symbol and map files produced by
|
Note also that only exported symbols will appear in symbol and map files produced by
|
||||||
.Xr rgblink 1 .
|
.Xr rgblink 1 .
|
||||||
.Pp
|
.Pp
|
||||||
.Ic GLOBAL
|
|
||||||
is a deprecated synonym for
|
|
||||||
.Ic EXPORT ,
|
|
||||||
do not use it.
|
|
||||||
.Ss Purging symbols
|
.Ss Purging symbols
|
||||||
.Ic PURGE
|
.Ic PURGE
|
||||||
allows you to completely remove a symbol from the symbol table as if it had never existed.
|
allows you to completely remove a symbol from the symbol table as if it had never existed.
|
||||||
@@ -1026,27 +1196,33 @@ command
|
|||||||
.Ss Predeclared Symbols
|
.Ss Predeclared Symbols
|
||||||
The following symbols are defined by the assembler:
|
The following symbols are defined by the assembler:
|
||||||
.Bl -column -offset indent "EQUS" "__ISO_8601_LOCAL__"
|
.Bl -column -offset indent "EQUS" "__ISO_8601_LOCAL__"
|
||||||
.It Sy Type Ta Sy Name Ta Sy Contents
|
.It Sy Name Ta Sy Type Ta Sy Contents
|
||||||
.It Ic EQU Ta Dv @ Ta PC value (essentially, the current memory address)
|
.It Dv @ Ta Ic EQU Ta PC value (essentially, the current memory address)
|
||||||
.It Ic EQU Ta Dv _PI Ta Fixed point \[*p]
|
.It Dv _RS Ta Ic SET Ta _RS Counter
|
||||||
.It Ic SET Ta Dv _RS Ta _RS Counter
|
.It Dv _NARG Ta Ic EQU Ta Number of arguments passed to macro, updated by Ic SHIFT
|
||||||
.It Ic EQU Ta Dv _NARG Ta Number of arguments passed to macro, updated by Ic SHIFT
|
.It Dv __LINE__ Ta Ic EQU Ta The current line number
|
||||||
.It Ic EQU Ta Dv __LINE__ Ta The current line number
|
.It Dv __FILE__ Ta Ic EQUS Ta The current filename
|
||||||
.It Ic EQUS Ta Dv __FILE__ Ta The current filename
|
.It Dv __DATE__ Ta Ic EQUS Ta Today's date
|
||||||
.It Ic EQUS Ta Dv __DATE__ Ta Today's date
|
.It Dv __TIME__ Ta Ic EQUS Ta The current time
|
||||||
.It Ic EQUS Ta Dv __TIME__ Ta The current time
|
.It Dv __ISO_8601_LOCAL__ Ta Ic EQUS Ta ISO 8601 timestamp (local)
|
||||||
.It Ic EQUS Ta Dv __ISO_8601_LOCAL__ Ta ISO 8601 timestamp (local)
|
.It Dv __ISO_8601_UTC__ Ta Ic EQUS Ta ISO 8601 timestamp (UTC)
|
||||||
.It Ic EQUS Ta Dv __ISO_8601_UTC__ Ta ISO 8601 timestamp (UTC)
|
.It Dv __UTC_YEAR__ Ta Ic EQU Ta Today's year
|
||||||
.It Ic EQU Ta Dv __UTC_YEAR__ Ta Today's year
|
.It Dv __UTC_MONTH__ Ta Ic EQU Ta Today's month number, 1\[en]12
|
||||||
.It Ic EQU Ta Dv __UTC_MONTH__ Ta Today's month number, 1\[en]12
|
.It Dv __UTC_DAY__ Ta Ic EQU Ta Today's day of the month, 1\[en]31
|
||||||
.It Ic EQU Ta Dv __UTC_DAY__ Ta Today's day of the month, 1\[en]31
|
.It Dv __UTC_HOUR__ Ta Ic EQU Ta Current hour, 0\[en]23
|
||||||
.It Ic EQU Ta Dv __UTC_HOUR__ Ta Current hour, 0\[en]23
|
.It Dv __UTC_MINUTE__ Ta Ic EQU Ta Current minute, 0\[en]59
|
||||||
.It Ic EQU Ta Dv __UTC_MINUTE__ Ta Current minute, 0\[en]59
|
.It Dv __UTC_SECOND__ Ta Ic EQU Ta Current second, 0\[en]59
|
||||||
.It Ic EQU Ta Dv __UTC_SECOND__ Ta Current second, 0\[en]59
|
.It Dv __RGBDS_MAJOR__ Ta Ic EQU Ta Major version number of RGBDS
|
||||||
.It Ic EQU Ta Dv __RGBDS_MAJOR__ Ta Major version number of RGBDS
|
.It Dv __RGBDS_MINOR__ Ta Ic EQU Ta Minor version number of RGBDS
|
||||||
.It Ic EQU Ta Dv __RGBDS_MINOR__ Ta Minor version number of RGBDS
|
.It Dv __RGBDS_PATCH__ Ta Ic EQU Ta Patch version number of RGBDS
|
||||||
.It Ic EQU Ta Dv __RGBDS_PATCH__ Ta Patch version number of RGBDS
|
.It Dv __RGBDS_RC__ Ta Ic EQU Ta Release candidate ID of RGBDS, not defined for final releases
|
||||||
.El
|
.El
|
||||||
|
.Pp
|
||||||
|
The current time values will be taken from the
|
||||||
|
.Dv SOURCE_DATE_EPOCH
|
||||||
|
environment variable if that is defined as a UNIX timestamp.
|
||||||
|
Refer to the spec at
|
||||||
|
.Lk https://reproducible-builds.org/docs/source-date-epoch/ .
|
||||||
.Sh DEFINING DATA
|
.Sh DEFINING DATA
|
||||||
.Ss Declaring variables in a RAM section
|
.Ss Declaring variables in a RAM section
|
||||||
.Ic DS
|
.Ic DS
|
||||||
@@ -1072,33 +1248,42 @@ command-line option, except when using overlays with
|
|||||||
.Ic DB
|
.Ic DB
|
||||||
defines a list of bytes that will be stored in the final image.
|
defines a list of bytes that will be stored in the final image.
|
||||||
Ideal for tables and text.
|
Ideal for tables and text.
|
||||||
Note that strings are not zero-terminated!
|
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
DB 1,2,3,4,"This is a string"
|
DB 1,2,3,4,"This is a string"
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
.Ic DS
|
|
||||||
can also be used to fill a region of memory with some value.
|
|
||||||
The following produces 42 times the byte $FF:
|
|
||||||
.Bd -literal -offset indent
|
|
||||||
DS 42, $FF
|
|
||||||
.Ed
|
|
||||||
.Pp
|
|
||||||
Alternatively, you can use
|
Alternatively, you can use
|
||||||
.Ic DW
|
.Ic DW
|
||||||
to store a list of words (16-bit) or
|
to store a list of words (16-bit) or
|
||||||
.Ic DL
|
.Ic DL
|
||||||
to store a list of double-words/longs (32-bit).
|
to store a list of double-words/longs (32-bit).
|
||||||
Strings are not allowed as arguments to
|
.Pp
|
||||||
.Ic DW
|
Strings are handled a little specially: they first undergo charmap conversion (see
|
||||||
and
|
.Sx Character maps ) ,
|
||||||
.Ic DL .
|
then each resulting character is output individually.
|
||||||
|
For example, under the default charmap, the following two lines are identical:
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
DW "Hello!"
|
||||||
|
DW "H", "e", "l", "l", "o", "!"
|
||||||
|
.Ed
|
||||||
|
.Pp
|
||||||
|
If you do not want this special handling, enclose the string in parentheses.
|
||||||
|
.Pp
|
||||||
|
.Ic DS
|
||||||
|
can also be used to fill a region of memory with some repeated values.
|
||||||
|
For example:
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
; outputs 3 bytes: $AA, $AA, $AA
|
||||||
|
DS 3, $AA
|
||||||
|
; outputs 7 bytes: $BB, $CC, $BB, $CC, $BB, $CC, $BB
|
||||||
|
DS 7, $BB, $CC
|
||||||
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
You can also use
|
You can also use
|
||||||
.Ic DB , DW
|
.Ic DB , DW
|
||||||
and
|
and
|
||||||
.Ic DL
|
.Ic DL
|
||||||
without arguments, or leaving empty elements at any point in the list.
|
without arguments.
|
||||||
This works exactly like
|
This works exactly like
|
||||||
.Ic DS 1 , DS 2
|
.Ic DS 1 , DS 2
|
||||||
and
|
and
|
||||||
@@ -1216,7 +1401,7 @@ it will insert the macro definition (the code enclosed in
|
|||||||
.Pp
|
.Pp
|
||||||
Suppose your macro contains a loop.
|
Suppose your macro contains a loop.
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
LoopyMacro: MACRO
|
MACRO LoopyMacro
|
||||||
xor a,a
|
xor a,a
|
||||||
\&.loop ld [hl+],a
|
\&.loop ld [hl+],a
|
||||||
dec c
|
dec c
|
||||||
@@ -1234,7 +1419,7 @@ also works in
|
|||||||
.Ic REPT
|
.Ic REPT
|
||||||
blocks.
|
blocks.
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
LoopyMacro: MACRO
|
MACRO LoopyMacro
|
||||||
xor a,a
|
xor a,a
|
||||||
\&.loop\[rs]@ ld [hl+],a
|
\&.loop\[rs]@ ld [hl+],a
|
||||||
dec c
|
dec c
|
||||||
@@ -1263,7 +1448,7 @@ through
|
|||||||
.Ic \[rs]9 , \[rs]1
|
.Ic \[rs]9 , \[rs]1
|
||||||
being the first argument specified on the macro invocation.
|
being the first argument specified on the macro invocation.
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
LoopyMacro: MACRO
|
MACRO LoopyMacro
|
||||||
ld hl,\[rs]1
|
ld hl,\[rs]1
|
||||||
ld c,\[rs]2
|
ld c,\[rs]2
|
||||||
xor a,a
|
xor a,a
|
||||||
@@ -1288,36 +1473,33 @@ to
|
|||||||
if you perform further calculations on them.
|
if you perform further calculations on them.
|
||||||
For instance, consider the following:
|
For instance, consider the following:
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
print_double: MACRO
|
MACRO print_double
|
||||||
PRINTV \[rs]1 * 2
|
PRINTLN \[rs]1 * 2
|
||||||
ENDM
|
ENDM
|
||||||
print_double 1 + 2
|
print_double 1 + 2
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
The
|
The
|
||||||
.Ic PRINTV
|
.Ic PRINTLN
|
||||||
statement will expand to
|
statement will expand to
|
||||||
.Ql PRINTV 1 + 2 * 2 ,
|
.Ql PRINTLN 1 + 2 * 2 ,
|
||||||
which will print 5 and not 6 as you might have expected.
|
which will print 5 and not 6 as you might have expected.
|
||||||
.Pp
|
.Pp
|
||||||
Line continuations work as usual inside macros or lists of macro arguments.
|
Line continuations work as usual inside macros or lists of macro arguments.
|
||||||
However, some characters need to be escaped, as in the following example:
|
However, some characters need to be escaped, as in the following example:
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
PrintMacro: MACRO
|
MACRO PrintMacro
|
||||||
PRINTT \[rs]1
|
PRINT \[rs]1
|
||||||
ENDM
|
ENDM
|
||||||
|
|
||||||
PrintMacro STRCAT("Hello "\[rs], \[rs]
|
PrintMacro STRCAT("Hello "\[rs], \[rs]
|
||||||
"world\[rs]\[rs]n")
|
"world\[rs]n")
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
The comma needs to be escaped to avoid it being treated as separating the macro's arguments.
|
The comma needs to be escaped to avoid it being treated as separating the macro's arguments.
|
||||||
The backslash
|
The backslash in
|
||||||
.Sq \[rs]
|
.Ql \[rs]n
|
||||||
.Pq from Sq \[rs]n
|
does not need to be escaped because string literals also work as usual inside macro arguments.
|
||||||
also needs to be escaped because of the way
|
|
||||||
.Nm
|
|
||||||
processes macro arguments.
|
|
||||||
.Pp
|
.Pp
|
||||||
In reality, up to 256 arguments can be passed to a macro, but you can only use the first 9 like this.
|
In reality, up to 256 arguments can be passed to a macro, but you can only use the first 9 like this.
|
||||||
If you want to use the rest, you need to use the
|
If you want to use the rest, you need to use the
|
||||||
@@ -1343,32 +1525,33 @@ This is the only way of accessing the value of arguments from 10 to 256.
|
|||||||
.Pp
|
.Pp
|
||||||
.Ic SHIFT
|
.Ic SHIFT
|
||||||
can optionally be given an integer parameter, and will apply the above shifting that number of times.
|
can optionally be given an integer parameter, and will apply the above shifting that number of times.
|
||||||
|
A negative parameter will shift the arguments in reverse.
|
||||||
.Ss Printing things during assembly
|
.Ss Printing things during assembly
|
||||||
The next four commands print text and values to the standard output.
|
The
|
||||||
|
.Ic PRINT
|
||||||
|
and
|
||||||
|
.Ic PRINTLN
|
||||||
|
commands print text and values to the standard output.
|
||||||
Useful for debugging macros, or wherever you may feel the need to tell yourself some important information.
|
Useful for debugging macros, or wherever you may feel the need to tell yourself some important information.
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
PRINTT "I'm the greatest programmer in the whole wide world\[rs]n"
|
PRINT "Hello world!\[rs]n"
|
||||||
PRINTI (2 + 3) / 5
|
PRINTLN "Hello world!"
|
||||||
PRINTV $FF00 + $F0
|
PRINT _NARG, " arguments\[rs]n"
|
||||||
PRINTF MUL(3.14, 3987.0)
|
PRINTLN "sum: ", 2+3, " product: ", 2*3
|
||||||
|
PRINTLN "Line #", __LINE__
|
||||||
|
PRINTLN STRFMT("E = %f", 2.718)
|
||||||
.Ed
|
.Ed
|
||||||
.Bl -inset
|
.Bl -inset
|
||||||
.It Ic PRINTT
|
.It Ic PRINT
|
||||||
prints out a string.
|
prints out each of its comma-separated arguments.
|
||||||
Be careful to add a line feed
|
Numbers are printed as unsigned uppercase hexadecimal with a leading
|
||||||
.Pq Qq \[rs]n
|
.Ic $ .
|
||||||
at the end, as it is not added automatically.
|
For different formats, use
|
||||||
.It Ic PRINTV
|
.Ic STRFMT .
|
||||||
prints out an integer value in hexadecimal or, as in the example, the result of a calculation.
|
.It Ic PRINTLN
|
||||||
Unsurprisingly, you can also print out a constant symbol's value.
|
prints out each of its comma-separated arguments, if any, followed by a line feed
|
||||||
.It Ic PRINTI
|
.Pq Ql \[rs]n .
|
||||||
prints out a signed integer value.
|
|
||||||
.It Ic PRINTF
|
|
||||||
prints out a fixed point value.
|
|
||||||
.El
|
.El
|
||||||
.Pp
|
|
||||||
Be careful that none of those automatically print a line feed; if you need one, use
|
|
||||||
.Ic PRINTT "\[rs]n" .
|
|
||||||
.Ss Automatically repeating blocks of code
|
.Ss Automatically repeating blocks of code
|
||||||
Suppose you want to unroll a time consuming loop without copy-pasting it.
|
Suppose you want to unroll a time consuming loop without copy-pasting it.
|
||||||
.Ic REPT
|
.Ic REPT
|
||||||
@@ -1391,13 +1574,12 @@ You can also use
|
|||||||
.Ic REPT
|
.Ic REPT
|
||||||
to generate tables on the fly:
|
to generate tables on the fly:
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
;\ --
|
; Generate a 256-byte sine table with values in the range [0, 128]
|
||||||
;\ -- Generate a 256 byte sine table with values between 0 and 128
|
; (shifted and scaled from the range [-1.0, 1.0])
|
||||||
;\ --
|
|
||||||
ANGLE = 0.0
|
ANGLE = 0.0
|
||||||
REPT 256
|
REPT 256
|
||||||
db (MUL(64.0, SIN(ANGLE)) + 64.0) >> 16
|
db (MUL(64.0, SIN(ANGLE)) + 64.0) >> 16
|
||||||
ANGLE = ANGLE+256.0
|
ANGLE = ANGLE + 256.0 ; 256.0 = 65536 degrees / 256 entries
|
||||||
ENDR
|
ENDR
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
@@ -1405,6 +1587,102 @@ As in macros, you can also use the escape sequence
|
|||||||
.Ic \[rs]@ .
|
.Ic \[rs]@ .
|
||||||
.Ic REPT
|
.Ic REPT
|
||||||
blocks can be nested.
|
blocks can be nested.
|
||||||
|
.Pp
|
||||||
|
A common pattern is to repeat a block for each value in some range.
|
||||||
|
.Ic FOR
|
||||||
|
is simpler than
|
||||||
|
.Ic REPT
|
||||||
|
for that purpose.
|
||||||
|
Everything between
|
||||||
|
.Ic FOR
|
||||||
|
and the matching
|
||||||
|
.Ic ENDR
|
||||||
|
will be repeated for each value of a given symbol.
|
||||||
|
For example, this code will produce a table of squared values from 0 to 255:
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
FOR N, 256
|
||||||
|
dw N * N
|
||||||
|
ENDR
|
||||||
|
.Ed
|
||||||
|
.Pp
|
||||||
|
It acts just as if you had done:
|
||||||
|
.Bd -literal -offset ident
|
||||||
|
N = 0
|
||||||
|
dw N * N
|
||||||
|
N = 1
|
||||||
|
dw N * N
|
||||||
|
N = 2
|
||||||
|
dw N * N
|
||||||
|
; ...
|
||||||
|
N = 255
|
||||||
|
dw N * N
|
||||||
|
N = 256
|
||||||
|
.Ed
|
||||||
|
.Pp
|
||||||
|
You can customize the range of
|
||||||
|
.Ic FOR
|
||||||
|
values:
|
||||||
|
.Bl -column "FOR V, start, stop, step"
|
||||||
|
.It Sy Code Ta Sy Range
|
||||||
|
.It Ic FOR Ar V , stop Ta Ar V No increments from 0 to Ar stop No
|
||||||
|
.It Ic FOR Ar V , start , stop Ta Ar V No increments from Ar start No to Ar stop No
|
||||||
|
.It Ic FOR Ar V , start , stop , step Ta Ar V No goes from Ar start No to Ar stop No by Ar step No
|
||||||
|
.El
|
||||||
|
.Pp
|
||||||
|
The
|
||||||
|
.Ic FOR
|
||||||
|
value will be updated by
|
||||||
|
.Ar step
|
||||||
|
until it reaches or exceeds
|
||||||
|
.Ar stop.
|
||||||
|
For example:
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
FOR V, 4, 25, 5
|
||||||
|
PRINT "{d:V} "
|
||||||
|
ENDR
|
||||||
|
PRINTLN "done {d:V}"
|
||||||
|
.Ed
|
||||||
|
This will print:
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
4 9 14 19 24 done 29
|
||||||
|
.Ed
|
||||||
|
.Pp
|
||||||
|
Just like with
|
||||||
|
.Ic REPT
|
||||||
|
blocks, you can use the escape sequence
|
||||||
|
.Ic \[rs]@
|
||||||
|
inside of
|
||||||
|
.Ic FOR
|
||||||
|
blocks, and they can be nested.
|
||||||
|
.Pp
|
||||||
|
You can stop a repeating block with the
|
||||||
|
.Ic BREAK
|
||||||
|
command.
|
||||||
|
A
|
||||||
|
.Ic BREAK
|
||||||
|
inside of a
|
||||||
|
.Ic REPT
|
||||||
|
or
|
||||||
|
.Ic FOR
|
||||||
|
block will interrupt the current iteration and not repeat any more.
|
||||||
|
It will continue running code after the block's
|
||||||
|
.Ic ENDR .
|
||||||
|
For example:
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
FOR V, 1, 100
|
||||||
|
PRINT "{d:V}"
|
||||||
|
IF V == 5
|
||||||
|
PRINT " stop! "
|
||||||
|
BREAK
|
||||||
|
ENDC
|
||||||
|
PRINT ", "
|
||||||
|
ENDR
|
||||||
|
PRINTLN "done {d:V}"
|
||||||
|
.Ed
|
||||||
|
This will print:
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
1, 2, 3, 4, 5 stop! done 5
|
||||||
|
.Ed
|
||||||
.Ss Aborting the assembly process
|
.Ss Aborting the assembly process
|
||||||
.Ic FAIL
|
.Ic FAIL
|
||||||
and
|
and
|
||||||
@@ -1501,11 +1779,11 @@ skip over parts of your code depending on a condition.
|
|||||||
This is a powerful feature commonly used in macros.
|
This is a powerful feature commonly used in macros.
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
IF NUM < 0
|
IF NUM < 0
|
||||||
PRINTT "NUM < 0\[rs]n"
|
PRINTLN "NUM < 0"
|
||||||
ELIF NUM == 0
|
ELIF NUM == 0
|
||||||
PRINTT "NUM == 0\[rs]n"
|
PRINTLN "NUM == 0"
|
||||||
ELSE
|
ELSE
|
||||||
PRINTT "NUM > 0\[rs]n"
|
PRINTLN "NUM > 0"
|
||||||
ENDC
|
ENDC
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
|
|||||||
@@ -15,15 +15,18 @@
|
|||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "asm/asm.h"
|
|
||||||
#include "asm/main.h"
|
#include "asm/main.h"
|
||||||
|
#include "asm/output.h"
|
||||||
#include "asm/rpn.h"
|
#include "asm/rpn.h"
|
||||||
#include "asm/section.h"
|
#include "asm/section.h"
|
||||||
#include "asm/symbol.h"
|
#include "asm/symbol.h"
|
||||||
#include "asm/warning.h"
|
#include "asm/warning.h"
|
||||||
|
|
||||||
|
#include "opmath.h"
|
||||||
|
|
||||||
/* Makes an expression "not known", also setting its error message */
|
/* Makes an expression "not known", also setting its error message */
|
||||||
#define makeUnknown(expr_, ...) do { \
|
#define makeUnknown(expr_, ...) do { \
|
||||||
struct Expression *_expr = expr_; \
|
struct Expression *_expr = expr_; \
|
||||||
@@ -124,16 +127,6 @@ void rpn_Symbol(struct Expression *expr, char const *tzSym)
|
|||||||
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
|
||||||
*ptr++ = RPN_SYM;
|
*ptr++ = RPN_SYM;
|
||||||
memcpy(ptr, sym->name, nameLen);
|
memcpy(ptr, sym->name, nameLen);
|
||||||
|
|
||||||
/* RGBLINK assumes PC is at the byte being computed... */
|
|
||||||
if (sym_IsPC(sym) && nPCOffset) {
|
|
||||||
struct Expression pc = *expr, offset;
|
|
||||||
|
|
||||||
rpn_Number(&offset, nPCOffset);
|
|
||||||
rpn_BinaryOp(RPN_SUB, expr, &pc, &offset);
|
|
||||||
if (!rpn_isKnown(expr))
|
|
||||||
expr->isSymbol = true;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
rpn_Number(expr, sym_GetConstantValue(tzSym));
|
rpn_Number(expr, sym_GetConstantValue(tzSym));
|
||||||
}
|
}
|
||||||
@@ -253,45 +246,6 @@ void rpn_LOGNOT(struct Expression *expr, const struct Expression *src)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int32_t shift(int32_t shiftee, int32_t amount)
|
|
||||||
{
|
|
||||||
if (amount >= 0) {
|
|
||||||
// Left shift
|
|
||||||
if (amount >= 32) {
|
|
||||||
warning(WARNING_SHIFT_AMOUNT, "Shifting left by large amount %"
|
|
||||||
PRId32 "\n", amount);
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
* Use unsigned to force a bitwise shift
|
|
||||||
* Casting back is OK because the types implement two's
|
|
||||||
* complement behavior
|
|
||||||
*/
|
|
||||||
return (uint32_t)shiftee << amount;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Right shift
|
|
||||||
amount = -amount;
|
|
||||||
if (amount >= 32) {
|
|
||||||
warning(WARNING_SHIFT_AMOUNT,
|
|
||||||
"Shifting right by large amount %" PRId32 "\n", amount);
|
|
||||||
return shiftee < 0 ? -1 : 0;
|
|
||||||
|
|
||||||
} else if (shiftee >= 0) {
|
|
||||||
return shiftee >> amount;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
* The C standard leaves shifting right negative values
|
|
||||||
* undefined, so use a left shift manually sign-extended
|
|
||||||
*/
|
|
||||||
return (uint32_t)shiftee >> amount
|
|
||||||
| -(UINT32_C(1) << (32 - amount));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Symbol const *rpn_SymbolOf(struct Expression const *expr)
|
struct Symbol const *rpn_SymbolOf(struct Expression const *expr)
|
||||||
{
|
{
|
||||||
if (!rpn_isSymbol(expr))
|
if (!rpn_isSymbol(expr))
|
||||||
@@ -377,11 +331,17 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
|
|||||||
"Shifting left by negative amount %" PRId32 "\n",
|
"Shifting left by negative amount %" PRId32 "\n",
|
||||||
src2->nVal);
|
src2->nVal);
|
||||||
|
|
||||||
expr->nVal = shift(src1->nVal, src2->nVal);
|
if (src2->nVal >= 32)
|
||||||
|
warning(WARNING_SHIFT_AMOUNT,
|
||||||
|
"Shifting left by large amount %" PRId32 "\n",
|
||||||
|
src2->nVal);
|
||||||
|
|
||||||
|
expr->nVal = op_shift_left(src1->nVal, src2->nVal);
|
||||||
break;
|
break;
|
||||||
case RPN_SHR:
|
case RPN_SHR:
|
||||||
if (src1->nVal < 0)
|
if (src1->nVal < 0)
|
||||||
warning(WARNING_SHIFT, "Shifting negative value %" PRId32 "\n",
|
warning(WARNING_SHIFT, "Shifting right negative value %"
|
||||||
|
PRId32 "\n",
|
||||||
src1->nVal);
|
src1->nVal);
|
||||||
|
|
||||||
if (src2->nVal < 0)
|
if (src2->nVal < 0)
|
||||||
@@ -389,7 +349,12 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
|
|||||||
"Shifting right by negative amount %" PRId32 "\n",
|
"Shifting right by negative amount %" PRId32 "\n",
|
||||||
src2->nVal);
|
src2->nVal);
|
||||||
|
|
||||||
expr->nVal = shift(src1->nVal, -src2->nVal);
|
if (src2->nVal >= 32)
|
||||||
|
warning(WARNING_SHIFT_AMOUNT,
|
||||||
|
"Shifting right by large amount %" PRId32 "\n",
|
||||||
|
src2->nVal);
|
||||||
|
|
||||||
|
expr->nVal = op_shift_right(src1->nVal, src2->nVal);
|
||||||
break;
|
break;
|
||||||
case RPN_MUL:
|
case RPN_MUL:
|
||||||
expr->nVal = uleft * uright;
|
expr->nVal = uleft * uright;
|
||||||
@@ -403,17 +368,26 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
|
|||||||
PRId32 "\n", INT32_MIN, INT32_MIN);
|
PRId32 "\n", INT32_MIN, INT32_MIN);
|
||||||
expr->nVal = INT32_MIN;
|
expr->nVal = INT32_MIN;
|
||||||
} else {
|
} else {
|
||||||
expr->nVal = src1->nVal / src2->nVal;
|
expr->nVal = op_divide(src1->nVal, src2->nVal);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case RPN_MOD:
|
case RPN_MOD:
|
||||||
if (src2->nVal == 0)
|
if (src2->nVal == 0)
|
||||||
fatalerror("Division by zero\n");
|
fatalerror("Modulo by zero\n");
|
||||||
|
|
||||||
if (src1->nVal == INT32_MIN && src2->nVal == -1)
|
if (src1->nVal == INT32_MIN && src2->nVal == -1)
|
||||||
expr->nVal = 0;
|
expr->nVal = 0;
|
||||||
else
|
else
|
||||||
expr->nVal = src1->nVal % src2->nVal;
|
expr->nVal = op_modulo(src1->nVal, src2->nVal);
|
||||||
|
break;
|
||||||
|
case RPN_EXP:
|
||||||
|
if (src2->nVal < 0)
|
||||||
|
fatalerror("Exponentiation by negative power\n");
|
||||||
|
|
||||||
|
if (src1->nVal == INT32_MIN && src2->nVal == -1)
|
||||||
|
expr->nVal = 0;
|
||||||
|
else
|
||||||
|
expr->nVal = op_exponent(src1->nVal, src2->nVal);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RPN_UNSUB:
|
case RPN_UNSUB:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
|
|
||||||
|
#include <assert.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
@@ -11,11 +12,14 @@
|
|||||||
#include "asm/output.h"
|
#include "asm/output.h"
|
||||||
#include "asm/rpn.h"
|
#include "asm/rpn.h"
|
||||||
#include "asm/section.h"
|
#include "asm/section.h"
|
||||||
|
#include "asm/symbol.h"
|
||||||
#include "asm/warning.h"
|
#include "asm/warning.h"
|
||||||
|
|
||||||
#include "extern/err.h"
|
#include "extern/err.h"
|
||||||
#include "platform.h" // strdup
|
#include "platform.h" // strdup
|
||||||
|
|
||||||
|
uint8_t fillByte;
|
||||||
|
|
||||||
struct SectionStackEntry {
|
struct SectionStackEntry {
|
||||||
struct Section *section;
|
struct Section *section;
|
||||||
char const *scope; /* Section's symbol scope */
|
char const *scope; /* Section's symbol scope */
|
||||||
@@ -26,7 +30,7 @@ struct SectionStackEntry {
|
|||||||
struct SectionStackEntry *sectionStack;
|
struct SectionStackEntry *sectionStack;
|
||||||
uint32_t curOffset; /* Offset into the current section (see sect_GetSymbolOffset) */
|
uint32_t curOffset; /* Offset into the current section (see sect_GetSymbolOffset) */
|
||||||
static struct Section *currentLoadSection = NULL;
|
static struct Section *currentLoadSection = NULL;
|
||||||
uint32_t loadOffset; /* The offset of the LOAD section within its parent */
|
int32_t loadOffset; /* Offset into the LOAD section's parent (see sect_GetOutputOffset) */
|
||||||
|
|
||||||
struct UnionStackEntry {
|
struct UnionStackEntry {
|
||||||
uint32_t start;
|
uint32_t start;
|
||||||
@@ -83,48 +87,247 @@ static inline void reserveSpace(uint32_t delta_size)
|
|||||||
|
|
||||||
struct Section *out_FindSectionByName(const char *name)
|
struct Section *out_FindSectionByName(const char *name)
|
||||||
{
|
{
|
||||||
struct Section *sect = pSectionList;
|
for (struct Section *sect = pSectionList; sect; sect = sect->next) {
|
||||||
|
|
||||||
while (sect) {
|
|
||||||
if (strcmp(name, sect->name) == 0)
|
if (strcmp(name, sect->name) == 0)
|
||||||
return sect;
|
return sect;
|
||||||
|
|
||||||
sect = sect->next;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
#define mask(align) ((1U << (align)) - 1)
|
||||||
* Find a section by name and type. If it doesn't exist, create it
|
#define fail(...) \
|
||||||
*/
|
do { \
|
||||||
static struct Section *getSection(char const *name, enum SectionType type,
|
error(__VA_ARGS__); \
|
||||||
uint32_t org, struct SectionSpec const *attrs,
|
nbSectErrors++; \
|
||||||
enum SectionModifier mod)
|
} while (0)
|
||||||
|
|
||||||
|
static unsigned int mergeSectUnion(struct Section *sect, enum SectionType type, uint32_t org,
|
||||||
|
uint8_t alignment, uint16_t alignOffset)
|
||||||
|
{
|
||||||
|
assert(alignment < 16); // Should be ensured by the caller
|
||||||
|
unsigned int nbSectErrors = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Unionized sections only need "compatible" constraints, and they end up with the strictest
|
||||||
|
* combination of both.
|
||||||
|
*/
|
||||||
|
if (sect_HasData(type))
|
||||||
|
fail("Cannot declare ROM sections as UNION\n");
|
||||||
|
|
||||||
|
if (org != -1) {
|
||||||
|
/* If both are fixed, they must be the same */
|
||||||
|
if (sect->org != -1 && sect->org != org)
|
||||||
|
fail("Section already declared as fixed at different address $%04"
|
||||||
|
PRIx32 "\n", sect->org);
|
||||||
|
else if (sect->align != 0 && (mask(sect->align) & (org - sect->alignOfs)))
|
||||||
|
fail("Section already declared as aligned to %u bytes (offset %"
|
||||||
|
PRIu16 ")\n", 1U << sect->align, sect->alignOfs);
|
||||||
|
else
|
||||||
|
/* Otherwise, just override */
|
||||||
|
sect->org = org;
|
||||||
|
|
||||||
|
} else if (alignment != 0) {
|
||||||
|
/* Make sure any fixed address given is compatible */
|
||||||
|
if (sect->org != -1) {
|
||||||
|
if ((sect->org - alignOffset) & mask(alignment))
|
||||||
|
fail("Section already declared as fixed at incompatible address $%04"
|
||||||
|
PRIx32 "\n", sect->org);
|
||||||
|
/* Check if alignment offsets are compatible */
|
||||||
|
} else if ((alignOffset & mask(sect->align))
|
||||||
|
!= (sect->alignOfs & mask(alignment))) {
|
||||||
|
fail("Section already declared with incompatible %" PRIu8
|
||||||
|
"-byte alignment (offset %" PRIu16 ")\n",
|
||||||
|
sect->align, sect->alignOfs);
|
||||||
|
} else if (alignment > sect->align) {
|
||||||
|
// If the section is not fixed, its alignment is the largest of both
|
||||||
|
sect->align = alignment;
|
||||||
|
sect->alignOfs = alignOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nbSectErrors;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned int mergeFragments(struct Section *sect, enum SectionType type, uint32_t org,
|
||||||
|
uint8_t alignment, uint16_t alignOffset)
|
||||||
|
{
|
||||||
|
(void)type;
|
||||||
|
assert(alignment < 16); // Should be ensured by the caller
|
||||||
|
unsigned int nbSectErrors = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fragments only need "compatible" constraints, and they end up with the strictest
|
||||||
|
* combination of both.
|
||||||
|
* The merging is however performed at the *end* of the original section!
|
||||||
|
*/
|
||||||
|
if (org != -1) {
|
||||||
|
uint16_t curOrg = org - sect->size;
|
||||||
|
|
||||||
|
/* If both are fixed, they must be the same */
|
||||||
|
if (sect->org != -1 && sect->org != curOrg)
|
||||||
|
fail("Section already declared as fixed at incompatible address $%04"
|
||||||
|
PRIx32 " (cur addr = %04" PRIx32 ")\n",
|
||||||
|
sect->org, sect->org + sect->size);
|
||||||
|
else if (sect->align != 0 && (mask(sect->align) & (curOrg - sect->alignOfs)))
|
||||||
|
fail("Section already declared as aligned to %u bytes (offset %"
|
||||||
|
PRIu16 ")\n", 1U << sect->align, sect->alignOfs);
|
||||||
|
else
|
||||||
|
/* Otherwise, just override */
|
||||||
|
sect->org = curOrg;
|
||||||
|
|
||||||
|
} else if (alignment != 0) {
|
||||||
|
int32_t curOfs = (alignOffset - sect->size) % (1U << alignment);
|
||||||
|
|
||||||
|
if (curOfs < 0)
|
||||||
|
curOfs += 1U << alignment;
|
||||||
|
|
||||||
|
/* Make sure any fixed address given is compatible */
|
||||||
|
if (sect->org != -1) {
|
||||||
|
if ((sect->org - curOfs) & mask(alignment))
|
||||||
|
fail("Section already declared as fixed at incompatible address $%04"
|
||||||
|
PRIx32 "\n", sect->org);
|
||||||
|
/* Check if alignment offsets are compatible */
|
||||||
|
} else if ((curOfs & mask(sect->align)) != (sect->alignOfs & mask(alignment))) {
|
||||||
|
fail("Section already declared with incompatible %" PRIu8
|
||||||
|
"-byte alignment (offset %" PRIu16 ")\n",
|
||||||
|
sect->align, sect->alignOfs);
|
||||||
|
} else if (alignment > sect->align) {
|
||||||
|
// If the section is not fixed, its alignment is the largest of both
|
||||||
|
sect->align = alignment;
|
||||||
|
sect->alignOfs = curOfs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nbSectErrors;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mergeSections(struct Section *sect, enum SectionType type, uint32_t org, uint32_t bank,
|
||||||
|
uint8_t alignment, uint16_t alignOffset, enum SectionModifier mod)
|
||||||
|
{
|
||||||
|
unsigned int nbSectErrors = 0;
|
||||||
|
|
||||||
|
if (type != sect->type)
|
||||||
|
fail("Section already exists but with type %s\n", typeNames[sect->type]);
|
||||||
|
|
||||||
|
if (sect->modifier != mod) {
|
||||||
|
fail("Section already declared as %s section\n", sectionModNames[sect->modifier]);
|
||||||
|
} else {
|
||||||
|
switch (mod) {
|
||||||
|
case SECTION_UNION:
|
||||||
|
case SECTION_FRAGMENT:
|
||||||
|
nbSectErrors += (mod == SECTION_UNION ? mergeSectUnion : mergeFragments)
|
||||||
|
(sect, type, org, alignment, alignOffset);
|
||||||
|
|
||||||
|
// Common checks
|
||||||
|
|
||||||
|
/* If the section's bank is unspecified, override it */
|
||||||
|
if (sect->bank == -1)
|
||||||
|
sect->bank = bank;
|
||||||
|
/* If both specify a bank, it must be the same one */
|
||||||
|
else if (bank != -1 && sect->bank != bank)
|
||||||
|
fail("Section already declared with different bank %" PRIu32 "\n",
|
||||||
|
sect->bank);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SECTION_NORMAL:
|
||||||
|
fail("Section already defined previously at ");
|
||||||
|
fstk_Dump(sect->src, sect->fileLine);
|
||||||
|
putc('\n', stderr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nbSectErrors)
|
||||||
|
fatalerror("Cannot create section \"%s\" (%u error%s)\n",
|
||||||
|
sect->name, nbSectErrors, nbSectErrors == 1 ? "" : "s");
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef fail
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a new section, not yet in the list.
|
||||||
|
*/
|
||||||
|
static struct Section *createSection(char const *name, enum SectionType type,
|
||||||
|
uint32_t org, uint32_t bank, uint8_t alignment,
|
||||||
|
uint16_t alignOffset, enum SectionModifier mod)
|
||||||
|
{
|
||||||
|
struct Section *sect = malloc(sizeof(*sect));
|
||||||
|
|
||||||
|
if (sect == NULL)
|
||||||
|
fatalerror("Not enough memory for section: %s\n", strerror(errno));
|
||||||
|
|
||||||
|
sect->name = strdup(name);
|
||||||
|
if (sect->name == NULL)
|
||||||
|
fatalerror("Not enough memory for section name: %s\n", strerror(errno));
|
||||||
|
|
||||||
|
sect->type = type;
|
||||||
|
sect->modifier = mod;
|
||||||
|
sect->src = fstk_GetFileStack();
|
||||||
|
sect->fileLine = lexer_GetLineNo();
|
||||||
|
sect->size = 0;
|
||||||
|
sect->org = org;
|
||||||
|
sect->bank = bank;
|
||||||
|
sect->align = alignment;
|
||||||
|
sect->alignOfs = alignOffset;
|
||||||
|
sect->next = NULL;
|
||||||
|
sect->patches = NULL;
|
||||||
|
|
||||||
|
/* It is only needed to allocate memory for ROM sections. */
|
||||||
|
if (sect_HasData(type)) {
|
||||||
|
sect->data = malloc(maxsize[type]);
|
||||||
|
if (sect->data == NULL)
|
||||||
|
fatalerror("Not enough memory for section: %s\n", strerror(errno));
|
||||||
|
} else {
|
||||||
|
sect->data = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find a section by name and type. If it doesn't exist, create it.
|
||||||
|
*/
|
||||||
|
static struct Section *getSection(char const *name, enum SectionType type, uint32_t org,
|
||||||
|
struct SectionSpec const *attrs, enum SectionModifier mod)
|
||||||
{
|
{
|
||||||
#define mask(align) ((1 << (align)) - 1)
|
|
||||||
uint32_t bank = attrs->bank;
|
uint32_t bank = attrs->bank;
|
||||||
uint8_t alignment = attrs->alignment;
|
uint8_t alignment = attrs->alignment;
|
||||||
uint16_t alignOffset = attrs->alignOfs;
|
uint16_t alignOffset = attrs->alignOfs;
|
||||||
|
|
||||||
|
// First, validate parameters, and normalize them if applicable
|
||||||
|
|
||||||
if (bank != -1) {
|
if (bank != -1) {
|
||||||
if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM
|
if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM
|
||||||
&& type != SECTTYPE_SRAM && type != SECTTYPE_WRAMX)
|
&& type != SECTTYPE_SRAM && type != SECTTYPE_WRAMX)
|
||||||
error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n");
|
error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n");
|
||||||
else if (bank < bankranges[type][0]
|
else if (bank < bankranges[type][0]
|
||||||
|| bank > bankranges[type][1])
|
|| bank > bankranges[type][1])
|
||||||
error("%s bank value $%" PRIx32 " out of range ($%" PRIx32 " to $%"
|
error("%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04"
|
||||||
PRIx32 ")\n", typeNames[type], bank,
|
PRIx32 ")\n", typeNames[type], bank,
|
||||||
bankranges[type][0], bankranges[type][1]);
|
bankranges[type][0], bankranges[type][1]);
|
||||||
|
} else if (nbbanks(type) == 1) {
|
||||||
|
// If the section type only has a single bank, implicitly force it
|
||||||
|
bank = bankranges[type][0];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alignOffset >= 1 << alignment) {
|
if (alignOffset >= 1 << alignment) {
|
||||||
error("Alignment offset must not be greater than alignment (%" PRIu16 " < %u)\n",
|
error("Alignment offset (%" PRIu16 ") must be smaller than alignment size (%u)\n",
|
||||||
alignOffset, 1U << alignment);
|
alignOffset, 1U << alignment);
|
||||||
alignOffset = 0;
|
alignOffset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (org != -1) {
|
||||||
|
if (org < startaddr[type] || org > endaddr(type))
|
||||||
|
error("Section \"%s\"'s fixed address %#" PRIx32
|
||||||
|
" is outside of range [%#" PRIx16 "; %#" PRIx16 "]\n",
|
||||||
|
name, org, startaddr[type], endaddr(type));
|
||||||
|
}
|
||||||
|
|
||||||
if (alignment != 0) {
|
if (alignment != 0) {
|
||||||
|
if (alignment > 16) {
|
||||||
|
error("Alignment must be between 0 and 16, not %u\n", alignment);
|
||||||
|
alignment = 16;
|
||||||
|
}
|
||||||
/* It doesn't make sense to have both alignment and org set */
|
/* It doesn't make sense to have both alignment and org set */
|
||||||
uint32_t mask = mask(alignment);
|
uint32_t mask = mask(alignment);
|
||||||
|
|
||||||
@@ -136,169 +339,28 @@ static struct Section *getSection(char const *name, enum SectionType type,
|
|||||||
} else if (startaddr[type] & mask) {
|
} else if (startaddr[type] & mask) {
|
||||||
error("Section \"%s\"'s alignment cannot be attained in %s\n",
|
error("Section \"%s\"'s alignment cannot be attained in %s\n",
|
||||||
name, typeNames[type]);
|
name, typeNames[type]);
|
||||||
|
} else if (alignment == 16) {
|
||||||
|
// Treat an alignment of 16 as being fixed at address 0
|
||||||
|
alignment = 0;
|
||||||
|
org = 0;
|
||||||
|
// The address is known to be valid, since the alignment is
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (org != -1) {
|
// Check if another section exists with the same name; merge if yes, otherwise create one
|
||||||
if (org < startaddr[type] || org > endaddr(type))
|
|
||||||
error("Section \"%s\"'s fixed address %#" PRIx32
|
|
||||||
" is outside of range [%#" PRIx16 "; %#" PRIx16 "]\n",
|
|
||||||
name, org, startaddr[type], endaddr(type));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nbbanks(type) == 1)
|
|
||||||
bank = bankranges[type][0];
|
|
||||||
|
|
||||||
struct Section *sect = out_FindSectionByName(name);
|
struct Section *sect = out_FindSectionByName(name);
|
||||||
|
|
||||||
if (sect) {
|
if (sect) {
|
||||||
unsigned int nbSectErrors = 0;
|
mergeSections(sect, type, org, bank, alignment, alignOffset, mod);
|
||||||
#define fail(...) \
|
|
||||||
do { \
|
|
||||||
error(__VA_ARGS__); \
|
|
||||||
nbSectErrors++; \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
if (type != sect->type)
|
|
||||||
fail("Section \"%s\" already exists but with type %s\n",
|
|
||||||
sect->name, typeNames[sect->type]);
|
|
||||||
|
|
||||||
if (sect->modifier != mod)
|
|
||||||
fail("Section \"%s\" already declared as %s section\n",
|
|
||||||
sect->name, sectionModNames[sect->modifier]);
|
|
||||||
/*
|
|
||||||
* Normal sections need to have exactly identical constraints;
|
|
||||||
* but unionized sections only need "compatible" constraints,
|
|
||||||
* and they end up with the strictest combination of both
|
|
||||||
*/
|
|
||||||
if (mod == SECTION_UNION) {
|
|
||||||
/*
|
|
||||||
* WARNING: see comment about assumption in
|
|
||||||
* `EndLoadSection` if modifying the following check!
|
|
||||||
*/
|
|
||||||
if (sect_HasData(type))
|
|
||||||
fail("Cannot declare ROM sections as UNION\n");
|
|
||||||
if (org != -1) {
|
|
||||||
/* If both are fixed, they must be the same */
|
|
||||||
if (sect->org != -1 && sect->org != org)
|
|
||||||
fail("Section \"%s\" already declared as fixed at different address $%"
|
|
||||||
PRIx32 "\n",
|
|
||||||
sect->name, sect->org);
|
|
||||||
else if (sect->align != 0
|
|
||||||
&& (mask(sect->align)
|
|
||||||
& (org - sect->alignOfs)))
|
|
||||||
fail("Section \"%s\" already declared as aligned to %u bytes (offset %"
|
|
||||||
PRIu16 ")\n", sect->name, 1U << sect->align, sect->alignOfs);
|
|
||||||
else
|
|
||||||
/* Otherwise, just override */
|
|
||||||
sect->org = org;
|
|
||||||
} else if (alignment != 0) {
|
|
||||||
/* Make sure any fixed address is compatible */
|
|
||||||
if (sect->org != -1) {
|
|
||||||
if ((sect->org - alignOffset)
|
|
||||||
& mask(alignment))
|
|
||||||
fail("Section \"%s\" already declared as fixed at incompatible address $%"
|
|
||||||
PRIx32 "\n", sect->name, sect->org);
|
|
||||||
/* Check if alignment offsets are compatible */
|
|
||||||
} else if ((alignOffset & mask(sect->align))
|
|
||||||
!= (sect->alignOfs
|
|
||||||
& mask(alignment))) {
|
|
||||||
fail("Section \"%s\" already declared with incompatible %"
|
|
||||||
PRIu8 "-byte alignment (offset %" PRIu16 ")\n",
|
|
||||||
sect->name, sect->align, sect->alignOfs);
|
|
||||||
} else if (alignment > sect->align) {
|
|
||||||
/*
|
|
||||||
* If the section is not fixed,
|
|
||||||
* its alignment is the largest of both
|
|
||||||
*/
|
|
||||||
sect->align = alignment;
|
|
||||||
sect->alignOfs = alignOffset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* If the section's bank is unspecified, override it */
|
|
||||||
if (sect->bank == -1)
|
|
||||||
sect->bank = bank;
|
|
||||||
/* If both specify a bank, it must be the same one */
|
|
||||||
else if (bank != -1 && sect->bank != bank)
|
|
||||||
fail("Section \"%s\" already declared with different bank %"
|
|
||||||
PRIu32 "\n", sect->name, sect->bank);
|
|
||||||
} else { /* Section fragments are handled identically in RGBASM */
|
|
||||||
/* However, concaternating non-fragments will be made an error */
|
|
||||||
if (sect->modifier != SECTION_FRAGMENT || mod != SECTION_FRAGMENT)
|
|
||||||
warning(WARNING_OBSOLETE,
|
|
||||||
"Concatenation of non-fragment sections is deprecated\n");
|
|
||||||
|
|
||||||
if (org != sect->org) {
|
|
||||||
if (sect->org == -1)
|
|
||||||
fail("Section \"%s\" already declared as floating\n",
|
|
||||||
sect->name);
|
|
||||||
else
|
|
||||||
fail("Section \"%s\" already declared as fixed at $%"
|
|
||||||
PRIx32 "\n", sect->name, sect->org);
|
|
||||||
}
|
|
||||||
if (bank != sect->bank) {
|
|
||||||
if (sect->bank == -1)
|
|
||||||
fail("Section \"%s\" already declared as floating bank\n",
|
|
||||||
sect->name);
|
|
||||||
else
|
|
||||||
fail("Section \"%s\" already declared as fixed at bank %"
|
|
||||||
PRIu32 "\n", sect->name, sect->bank);
|
|
||||||
}
|
|
||||||
if (alignment != sect->align) {
|
|
||||||
if (sect->align == 0)
|
|
||||||
fail("Section \"%s\" already declared as unaligned\n",
|
|
||||||
sect->name);
|
|
||||||
else
|
|
||||||
fail("Section \"%s\" already declared as aligned to %u bytes\n",
|
|
||||||
sect->name, 1U << sect->align);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nbSectErrors)
|
|
||||||
fatalerror("Cannot create section \"%s\" (%u errors)\n",
|
|
||||||
sect->name, nbSectErrors);
|
|
||||||
#undef fail
|
|
||||||
return sect;
|
|
||||||
}
|
|
||||||
|
|
||||||
sect = malloc(sizeof(*sect));
|
|
||||||
if (sect == NULL)
|
|
||||||
fatalerror("Not enough memory for section: %s\n", strerror(errno));
|
|
||||||
|
|
||||||
sect->name = strdup(name);
|
|
||||||
if (sect->name == NULL)
|
|
||||||
fatalerror("Not enough memory for section name: %s\n", strerror(errno));
|
|
||||||
|
|
||||||
sect->type = type;
|
|
||||||
sect->modifier = mod;
|
|
||||||
sect->size = 0;
|
|
||||||
sect->org = org;
|
|
||||||
sect->bank = bank;
|
|
||||||
sect->align = alignment;
|
|
||||||
sect->alignOfs = alignOffset;
|
|
||||||
sect->next = pSectionList;
|
|
||||||
sect->patches = NULL;
|
|
||||||
|
|
||||||
/* It is only needed to allocate memory for ROM sections. */
|
|
||||||
if (sect_HasData(type)) {
|
|
||||||
uint32_t sectsize;
|
|
||||||
|
|
||||||
sectsize = maxsize[type];
|
|
||||||
sect->data = malloc(sectsize);
|
|
||||||
if (sect->data == NULL)
|
|
||||||
fatalerror("Not enough memory for section: %s\n", strerror(errno));
|
|
||||||
} else {
|
} else {
|
||||||
sect->data = NULL;
|
sect = createSection(name, type, org, bank, alignment, alignOffset, mod);
|
||||||
|
// Add the new section to the list (order doesn't matter)
|
||||||
|
sect->next = pSectionList;
|
||||||
|
pSectionList = sect;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Add the new section to the list
|
|
||||||
* at the beginning because order doesn't matter
|
|
||||||
*/
|
|
||||||
pSectionList = sect;
|
|
||||||
|
|
||||||
return sect;
|
return sect;
|
||||||
#undef mask
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -321,6 +383,11 @@ void out_NewSection(char const *name, uint32_t type, uint32_t org,
|
|||||||
if (currentLoadSection)
|
if (currentLoadSection)
|
||||||
fatalerror("Cannot change the section within a `LOAD` block\n");
|
fatalerror("Cannot change the section within a `LOAD` block\n");
|
||||||
|
|
||||||
|
for (struct SectionStackEntry *stack = sectionStack; stack; stack = stack->next) {
|
||||||
|
if (stack->section && !strcmp(name, stack->section->name))
|
||||||
|
fatalerror("Section '%s' is already on the stack\n", name);
|
||||||
|
}
|
||||||
|
|
||||||
struct Section *sect = getSection(name, type, org, attribs, mod);
|
struct Section *sect = getSection(name, type, org, attribs, mod);
|
||||||
|
|
||||||
changeSection();
|
changeSection();
|
||||||
@@ -332,18 +399,22 @@ void out_NewSection(char const *name, uint32_t type, uint32_t org,
|
|||||||
* Set the current section by name and type
|
* Set the current section by name and type
|
||||||
*/
|
*/
|
||||||
void out_SetLoadSection(char const *name, uint32_t type, uint32_t org,
|
void out_SetLoadSection(char const *name, uint32_t type, uint32_t org,
|
||||||
struct SectionSpec const *attribs)
|
struct SectionSpec const *attribs,
|
||||||
|
enum SectionModifier mod)
|
||||||
{
|
{
|
||||||
checkcodesection();
|
checkcodesection();
|
||||||
|
|
||||||
if (currentLoadSection)
|
if (currentLoadSection)
|
||||||
fatalerror("`LOAD` blocks cannot be nested\n");
|
fatalerror("`LOAD` blocks cannot be nested\n");
|
||||||
|
|
||||||
struct Section *sect = getSection(name, type, org, attribs, false);
|
if (sect_HasData(type))
|
||||||
|
error("`LOAD` blocks cannot create a ROM section\n");
|
||||||
|
|
||||||
|
struct Section *sect = getSection(name, type, org, attribs, mod);
|
||||||
|
|
||||||
loadOffset = curOffset;
|
|
||||||
curOffset = 0; /* curOffset -= loadOffset; */
|
|
||||||
changeSection();
|
changeSection();
|
||||||
|
loadOffset = curOffset - (mod == SECTION_UNION ? 0 : sect->size);
|
||||||
|
curOffset -= loadOffset;
|
||||||
currentLoadSection = sect;
|
currentLoadSection = sect;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,11 +422,11 @@ void out_EndLoadSection(void)
|
|||||||
{
|
{
|
||||||
if (!currentLoadSection)
|
if (!currentLoadSection)
|
||||||
error("Found `ENDL` outside of a `LOAD` block\n");
|
error("Found `ENDL` outside of a `LOAD` block\n");
|
||||||
currentLoadSection = NULL;
|
|
||||||
|
|
||||||
changeSection();
|
changeSection();
|
||||||
curOffset += loadOffset;
|
curOffset += loadOffset;
|
||||||
loadOffset = 0;
|
loadOffset = 0;
|
||||||
|
currentLoadSection = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Section *sect_GetSymbolSection(void)
|
struct Section *sect_GetSymbolSection(void)
|
||||||
@@ -380,24 +451,24 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset)
|
|||||||
{
|
{
|
||||||
checksection();
|
checksection();
|
||||||
struct Section *sect = sect_GetSymbolSection();
|
struct Section *sect = sect_GetSymbolSection();
|
||||||
|
uint16_t alignSize = 1 << alignment; // Size of an aligned "block"
|
||||||
|
|
||||||
if (sect->org != -1) {
|
if (sect->org != -1) {
|
||||||
if ((sym_GetPCValue() - offset) % (1 << alignment))
|
if ((sym_GetPCValue() - offset) % alignSize)
|
||||||
error("Section's fixed address fails required alignment (PC = $%04"
|
error("Section's fixed address fails required alignment (PC = $%04" PRIx32
|
||||||
PRIx32 ")\n", sym_GetPCValue());
|
")\n", sym_GetPCValue());
|
||||||
} else if (sect->align != 0) {
|
} else if (sect->align != 0) {
|
||||||
if ((((sect->alignOfs + curOffset) % (1 << sect->align))
|
if ((((sect->alignOfs + curOffset) % (1 << sect->align)) - offset) % alignSize) {
|
||||||
- offset) % (1 << alignment)) {
|
|
||||||
error("Section's alignment fails required alignment (offset from section start = $%04"
|
error("Section's alignment fails required alignment (offset from section start = $%04"
|
||||||
PRIx32 ")\n", curOffset);
|
PRIx32 ")\n", curOffset);
|
||||||
} else if (alignment > sect->align) {
|
} else if (alignment > sect->align) {
|
||||||
sect->align = alignment;
|
sect->align = alignment;
|
||||||
sect->alignOfs =
|
sect->alignOfs = (offset - curOffset) % alignSize;
|
||||||
(offset - curOffset) % (1 << alignment);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sect->align = alignment;
|
sect->align = alignment;
|
||||||
sect->alignOfs = offset;
|
// We need `(sect->alignOfs + curOffset) % alignSize == offset
|
||||||
|
sect->alignOfs = (offset - curOffset) % alignSize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,10 +501,10 @@ static inline void writelong(uint32_t b)
|
|||||||
writebyte(b >> 24);
|
writebyte(b >> 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void createPatch(enum PatchType type,
|
static inline void createPatch(enum PatchType type, struct Expression const *expr,
|
||||||
struct Expression const *expr)
|
uint32_t pcShift)
|
||||||
{
|
{
|
||||||
out_CreatePatch(type, expr, sect_GetOutputOffset());
|
out_CreatePatch(type, expr, sect_GetOutputOffset(), pcShift);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sect_StartUnion(void)
|
void sect_StartUnion(void)
|
||||||
@@ -483,7 +554,7 @@ void sect_EndUnion(void)
|
|||||||
void sect_CheckUnionClosed(void)
|
void sect_CheckUnionClosed(void)
|
||||||
{
|
{
|
||||||
if (unionStack)
|
if (unionStack)
|
||||||
fatalerror("Unterminated UNION construct!\n");
|
error("Unterminated UNION construct!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -506,6 +577,24 @@ void out_AbsByteGroup(uint8_t const *s, int32_t length)
|
|||||||
writebyte(*s++);
|
writebyte(*s++);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void out_AbsWordGroup(uint8_t const *s, int32_t length)
|
||||||
|
{
|
||||||
|
checkcodesection();
|
||||||
|
reserveSpace(length * 2);
|
||||||
|
|
||||||
|
while (length--)
|
||||||
|
writeword(*s++);
|
||||||
|
}
|
||||||
|
|
||||||
|
void out_AbsLongGroup(uint8_t const *s, int32_t length)
|
||||||
|
{
|
||||||
|
checkcodesection();
|
||||||
|
reserveSpace(length * 4);
|
||||||
|
|
||||||
|
while (length--)
|
||||||
|
writelong(*s++);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Skip this many bytes
|
* Skip this many bytes
|
||||||
*/
|
*/
|
||||||
@@ -515,14 +604,15 @@ void out_Skip(int32_t skip, bool ds)
|
|||||||
reserveSpace(skip);
|
reserveSpace(skip);
|
||||||
|
|
||||||
if (!ds && sect_HasData(pCurrentSection->type))
|
if (!ds && sect_HasData(pCurrentSection->type))
|
||||||
warning(WARNING_EMPTY_DATA_DIRECTIVE, "db/dw/dl directive without data in ROM\n");
|
warning(WARNING_EMPTY_DATA_DIRECTIVE, "%s directive without data in ROM\n",
|
||||||
|
(skip == 4) ? "DL" : (skip == 2) ? "DW" : "DB");
|
||||||
|
|
||||||
if (!sect_HasData(pCurrentSection->type)) {
|
if (!sect_HasData(pCurrentSection->type)) {
|
||||||
growSection(skip);
|
growSection(skip);
|
||||||
} else {
|
} else {
|
||||||
checkcodesection();
|
checkcodesection();
|
||||||
while (skip--)
|
while (skip--)
|
||||||
writebyte(CurrentOptions.fillchar);
|
writebyte(fillByte);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -542,13 +632,13 @@ void out_String(char const *s)
|
|||||||
* Output a relocatable byte. Checking will be done to see if it
|
* Output a relocatable byte. Checking will be done to see if it
|
||||||
* is an absolute value in disguise.
|
* is an absolute value in disguise.
|
||||||
*/
|
*/
|
||||||
void out_RelByte(struct Expression *expr)
|
void out_RelByte(struct Expression *expr, uint32_t pcShift)
|
||||||
{
|
{
|
||||||
checkcodesection();
|
checkcodesection();
|
||||||
reserveSpace(1);
|
reserveSpace(1);
|
||||||
|
|
||||||
if (!rpn_isKnown(expr)) {
|
if (!rpn_isKnown(expr)) {
|
||||||
createPatch(PATCHTYPE_BYTE, expr);
|
createPatch(PATCHTYPE_BYTE, expr, pcShift);
|
||||||
writebyte(0);
|
writebyte(0);
|
||||||
} else {
|
} else {
|
||||||
writebyte(expr->nVal);
|
writebyte(expr->nVal);
|
||||||
@@ -560,33 +650,37 @@ void out_RelByte(struct Expression *expr)
|
|||||||
* Output several copies of a relocatable byte. Checking will be done to see if
|
* Output several copies of a relocatable byte. Checking will be done to see if
|
||||||
* it is an absolute value in disguise.
|
* it is an absolute value in disguise.
|
||||||
*/
|
*/
|
||||||
void out_RelBytes(struct Expression *expr, uint32_t n)
|
void out_RelBytes(uint32_t n, struct Expression *exprs, size_t size)
|
||||||
{
|
{
|
||||||
checkcodesection();
|
checkcodesection();
|
||||||
reserveSpace(n);
|
reserveSpace(n);
|
||||||
|
|
||||||
while (n--) {
|
for (uint32_t i = 0; i < n; i++) {
|
||||||
|
struct Expression *expr = &exprs[i % size];
|
||||||
|
|
||||||
if (!rpn_isKnown(expr)) {
|
if (!rpn_isKnown(expr)) {
|
||||||
createPatch(PATCHTYPE_BYTE, expr);
|
createPatch(PATCHTYPE_BYTE, expr, i);
|
||||||
writebyte(0);
|
writebyte(0);
|
||||||
} else {
|
} else {
|
||||||
writebyte(expr->nVal);
|
writebyte(expr->nVal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rpn_Free(expr);
|
|
||||||
|
for (size_t i = 0; i < size; i++)
|
||||||
|
rpn_Free(&exprs[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Output a relocatable word. Checking will be done to see if
|
* Output a relocatable word. Checking will be done to see if
|
||||||
* it's an absolute value in disguise.
|
* it's an absolute value in disguise.
|
||||||
*/
|
*/
|
||||||
void out_RelWord(struct Expression *expr)
|
void out_RelWord(struct Expression *expr, uint32_t pcShift)
|
||||||
{
|
{
|
||||||
checkcodesection();
|
checkcodesection();
|
||||||
reserveSpace(2);
|
reserveSpace(2);
|
||||||
|
|
||||||
if (!rpn_isKnown(expr)) {
|
if (!rpn_isKnown(expr)) {
|
||||||
createPatch(PATCHTYPE_WORD, expr);
|
createPatch(PATCHTYPE_WORD, expr, pcShift);
|
||||||
writeword(0);
|
writeword(0);
|
||||||
} else {
|
} else {
|
||||||
writeword(expr->nVal);
|
writeword(expr->nVal);
|
||||||
@@ -598,13 +692,13 @@ void out_RelWord(struct Expression *expr)
|
|||||||
* Output a relocatable longword. Checking will be done to see if
|
* Output a relocatable longword. Checking will be done to see if
|
||||||
* is an absolute value in disguise.
|
* is an absolute value in disguise.
|
||||||
*/
|
*/
|
||||||
void out_RelLong(struct Expression *expr)
|
void out_RelLong(struct Expression *expr, uint32_t pcShift)
|
||||||
{
|
{
|
||||||
checkcodesection();
|
checkcodesection();
|
||||||
reserveSpace(2);
|
reserveSpace(2);
|
||||||
|
|
||||||
if (!rpn_isKnown(expr)) {
|
if (!rpn_isKnown(expr)) {
|
||||||
createPatch(PATCHTYPE_LONG, expr);
|
createPatch(PATCHTYPE_LONG, expr, pcShift);
|
||||||
writelong(0);
|
writelong(0);
|
||||||
} else {
|
} else {
|
||||||
writelong(expr->nVal);
|
writelong(expr->nVal);
|
||||||
@@ -616,14 +710,14 @@ void out_RelLong(struct Expression *expr)
|
|||||||
* Output a PC-relative relocatable byte. Checking will be done to see if it
|
* Output a PC-relative relocatable byte. Checking will be done to see if it
|
||||||
* is an absolute value in disguise.
|
* is an absolute value in disguise.
|
||||||
*/
|
*/
|
||||||
void out_PCRelByte(struct Expression *expr)
|
void out_PCRelByte(struct Expression *expr, uint32_t pcShift)
|
||||||
{
|
{
|
||||||
checkcodesection();
|
checkcodesection();
|
||||||
reserveSpace(1);
|
reserveSpace(1);
|
||||||
struct Symbol const *pc = sym_GetPC();
|
struct Symbol const *pc = sym_GetPC();
|
||||||
|
|
||||||
if (!rpn_IsDiffConstant(expr, pc)) {
|
if (!rpn_IsDiffConstant(expr, pc)) {
|
||||||
createPatch(PATCHTYPE_JR, expr);
|
createPatch(PATCHTYPE_JR, expr, pcShift);
|
||||||
writebyte(0);
|
writebyte(0);
|
||||||
} else {
|
} else {
|
||||||
struct Symbol const *sym = rpn_SymbolOf(expr);
|
struct Symbol const *sym = rpn_SymbolOf(expr);
|
||||||
@@ -670,7 +764,8 @@ void out_BinaryFile(char const *s, int32_t startPos)
|
|||||||
oFailedOnMissingInclude = true;
|
oFailedOnMissingInclude = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fatalerror("Error opening INCBIN file '%s': %s\n", s, strerror(errno));
|
error("Error opening INCBIN file '%s': %s\n", s, strerror(errno));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t fsize = -1;
|
int32_t fsize = -1;
|
||||||
@@ -680,7 +775,7 @@ void out_BinaryFile(char const *s, int32_t startPos)
|
|||||||
if (fseek(f, 0, SEEK_END) != -1) {
|
if (fseek(f, 0, SEEK_END) != -1) {
|
||||||
fsize = ftell(f);
|
fsize = ftell(f);
|
||||||
|
|
||||||
if (startPos >= fsize) {
|
if (startPos > fsize) {
|
||||||
error("Specified start position is greater than length of file\n");
|
error("Specified start position is greater than length of file\n");
|
||||||
fclose(f);
|
fclose(f);
|
||||||
return;
|
return;
|
||||||
@@ -736,7 +831,8 @@ void out_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
|
|||||||
oFailedOnMissingInclude = true;
|
oFailedOnMissingInclude = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fatalerror("Error opening INCBIN file '%s': %s\n", s, strerror(errno));
|
error("Error opening INCBIN file '%s': %s\n", s, strerror(errno));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkcodesection();
|
checkcodesection();
|
||||||
@@ -747,13 +843,16 @@ void out_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
|
|||||||
if (fseek(f, 0, SEEK_END) != -1) {
|
if (fseek(f, 0, SEEK_END) != -1) {
|
||||||
fsize = ftell(f);
|
fsize = ftell(f);
|
||||||
|
|
||||||
if (start_pos >= fsize) {
|
if (start_pos > fsize) {
|
||||||
error("Specified start position is greater than length of file\n");
|
error("Specified start position is greater than length of file\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((start_pos + length) > fsize)
|
if ((start_pos + length) > fsize) {
|
||||||
fatalerror("Specified range in INCBIN is out of bounds\n");
|
error("Specified range in INCBIN is out of bounds (%" PRIu32 " + %" PRIu32
|
||||||
|
" > %" PRIu32 ")\n", start_pos, length, fsize);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
fseek(f, start_pos, SEEK_SET);
|
fseek(f, start_pos, SEEK_SET);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
163
src/asm/symbol.c
163
src/asm/symbol.c
@@ -13,16 +13,16 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
|
#include <limits.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
#include "asm/asm.h"
|
#include "asm/fixpoint.h"
|
||||||
#include "asm/fstack.h"
|
#include "asm/fstack.h"
|
||||||
#include "asm/macro.h"
|
#include "asm/macro.h"
|
||||||
#include "asm/main.h"
|
#include "asm/main.h"
|
||||||
#include "asm/mymath.h"
|
|
||||||
#include "asm/output.h"
|
#include "asm/output.h"
|
||||||
#include "asm/section.h"
|
#include "asm/section.h"
|
||||||
#include "asm/symbol.h"
|
#include "asm/symbol.h"
|
||||||
@@ -153,10 +153,12 @@ int32_t sym_GetValue(struct Symbol const *sym)
|
|||||||
|
|
||||||
static void dumpFilename(struct Symbol const *sym)
|
static void dumpFilename(struct Symbol const *sym)
|
||||||
{
|
{
|
||||||
if (!sym->src)
|
if (sym->src)
|
||||||
fputs("<builtin>", stderr);
|
|
||||||
else
|
|
||||||
fstk_Dump(sym->src, sym->fileLine);
|
fstk_Dump(sym->src, sym->fileLine);
|
||||||
|
else if (sym->fileLine == 0)
|
||||||
|
fputs("<command-line>", stderr);
|
||||||
|
else
|
||||||
|
fputs("<builtin>", stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -165,7 +167,7 @@ static void dumpFilename(struct Symbol const *sym)
|
|||||||
static void setSymbolFilename(struct Symbol *sym)
|
static void setSymbolFilename(struct Symbol *sym)
|
||||||
{
|
{
|
||||||
sym->src = fstk_GetFileStack();
|
sym->src = fstk_GetFileStack();
|
||||||
sym->fileLine = lexer_GetLineNo();
|
sym->fileLine = sym->src ? lexer_GetLineNo() : 0; // This is (NULL, 1) for built-ins
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -177,7 +179,7 @@ static void updateSymbolFilename(struct Symbol *sym)
|
|||||||
|
|
||||||
setSymbolFilename(sym);
|
setSymbolFilename(sym);
|
||||||
/* If the old node was referenced, ensure the new one is */
|
/* If the old node was referenced, ensure the new one is */
|
||||||
if (oldSrc->referenced && oldSrc->ID != -1)
|
if (oldSrc && oldSrc->referenced && oldSrc->ID != -1)
|
||||||
out_RegisterNode(sym->src);
|
out_RegisterNode(sym->src);
|
||||||
/* TODO: unref the old node, and use `out_ReplaceNode` instead if deleting it */
|
/* TODO: unref the old node, and use `out_ReplaceNode` instead if deleting it */
|
||||||
}
|
}
|
||||||
@@ -222,6 +224,19 @@ static void fullSymbolName(char *output, size_t outputSize,
|
|||||||
fatalerror("Symbol name is too long: '%s%s'\n", scopeName, localName);
|
fatalerror("Symbol name is too long: '%s%s'\n", scopeName, localName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void assignStringSymbol(struct Symbol *sym, char const *value)
|
||||||
|
{
|
||||||
|
char *string = strdup(value);
|
||||||
|
|
||||||
|
if (string == NULL)
|
||||||
|
fatalerror("No memory for string equate: %s\n", strerror(errno));
|
||||||
|
|
||||||
|
sym->type = SYM_EQUS;
|
||||||
|
/* TODO: use other fields */
|
||||||
|
sym->macro = string;
|
||||||
|
sym->macroSize = strlen(string);
|
||||||
|
}
|
||||||
|
|
||||||
struct Symbol *sym_FindExactSymbol(char const *name)
|
struct Symbol *sym_FindExactSymbol(char const *name)
|
||||||
{
|
{
|
||||||
return hash_GetElement(symbols, name);
|
return hash_GetElement(symbols, name);
|
||||||
@@ -283,6 +298,11 @@ void sym_Purge(char const *symName)
|
|||||||
if (symbol->name == labelScope)
|
if (symbol->name == labelScope)
|
||||||
labelScope = NULL;
|
labelScope = NULL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FIXME: this leaks symbol->macro for SYM_EQUS and SYM_MACRO, but this can't
|
||||||
|
* free(symbol->macro) because the expansion may be purging itself.
|
||||||
|
*/
|
||||||
|
|
||||||
hash_RemoveElement(symbols, symbol->name);
|
hash_RemoveElement(symbols, symbol->name);
|
||||||
/* TODO: ideally, also unref the file stack nodes */
|
/* TODO: ideally, also unref the file stack nodes */
|
||||||
free(symbol);
|
free(symbol);
|
||||||
@@ -346,8 +366,10 @@ void sym_SetCurrentSymbolScope(char const *newScope)
|
|||||||
* Create a symbol that will be non-relocatable and ensure that it
|
* Create a symbol that will be non-relocatable and ensure that it
|
||||||
* hasn't already been defined or referenced in a context that would
|
* hasn't already been defined or referenced in a context that would
|
||||||
* require that it be relocatable
|
* require that it be relocatable
|
||||||
|
* @param symbolName The name of the symbol to create
|
||||||
|
* @param numeric If false, the symbol may not have been referenced earlier
|
||||||
*/
|
*/
|
||||||
static struct Symbol *createNonrelocSymbol(char const *symbolName)
|
static struct Symbol *createNonrelocSymbol(char const *symbolName, bool numeric)
|
||||||
{
|
{
|
||||||
struct Symbol *symbol = sym_FindExactSymbol(symbolName);
|
struct Symbol *symbol = sym_FindExactSymbol(symbolName);
|
||||||
|
|
||||||
@@ -357,6 +379,12 @@ static struct Symbol *createNonrelocSymbol(char const *symbolName)
|
|||||||
error("'%s' already defined at ", symbolName);
|
error("'%s' already defined at ", symbolName);
|
||||||
dumpFilename(symbol);
|
dumpFilename(symbol);
|
||||||
putc('\n', stderr);
|
putc('\n', stderr);
|
||||||
|
} else if (!numeric) {
|
||||||
|
// The symbol has already been referenced, but it's not allowed
|
||||||
|
error("'%s' already referenced at ", symbolName);
|
||||||
|
dumpFilename(symbol);
|
||||||
|
putc('\n', stderr);
|
||||||
|
return NULL; // Don't allow overriding the symbol, that'd be bad!
|
||||||
}
|
}
|
||||||
|
|
||||||
return symbol;
|
return symbol;
|
||||||
@@ -367,7 +395,10 @@ static struct Symbol *createNonrelocSymbol(char const *symbolName)
|
|||||||
*/
|
*/
|
||||||
struct Symbol *sym_AddEqu(char const *symName, int32_t value)
|
struct Symbol *sym_AddEqu(char const *symName, int32_t value)
|
||||||
{
|
{
|
||||||
struct Symbol *sym = createNonrelocSymbol(symName);
|
struct Symbol *sym = createNonrelocSymbol(symName, true);
|
||||||
|
|
||||||
|
if (!sym)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
sym->type = SYM_EQU;
|
sym->type = SYM_EQU;
|
||||||
sym->value = value;
|
sym->value = value;
|
||||||
@@ -389,18 +420,33 @@ struct Symbol *sym_AddEqu(char const *symName, int32_t value)
|
|||||||
*/
|
*/
|
||||||
struct Symbol *sym_AddString(char const *symName, char const *value)
|
struct Symbol *sym_AddString(char const *symName, char const *value)
|
||||||
{
|
{
|
||||||
struct Symbol *sym = createNonrelocSymbol(symName);
|
struct Symbol *sym = createNonrelocSymbol(symName, false);
|
||||||
size_t len = strlen(value);
|
|
||||||
char *string = malloc(len + 1);
|
|
||||||
|
|
||||||
if (string == NULL)
|
if (!sym)
|
||||||
fatalerror("No memory for string equate: %s\n", strerror(errno));
|
return NULL;
|
||||||
strcpy(string, value);
|
|
||||||
|
|
||||||
sym->type = SYM_EQUS;
|
assignStringSymbol(sym, value);
|
||||||
/* TODO: use other fields */
|
return sym;
|
||||||
sym->macroSize = len;
|
}
|
||||||
sym->macro = string;
|
|
||||||
|
struct Symbol *sym_RedefString(char const *symName, char const *value)
|
||||||
|
{
|
||||||
|
struct Symbol *sym = sym_FindExactSymbol(symName);
|
||||||
|
|
||||||
|
if (!sym) {
|
||||||
|
sym = createsymbol(symName);
|
||||||
|
} else if (sym->type != SYM_EQUS) {
|
||||||
|
error("'%s' already defined as non-EQUS at ", symName);
|
||||||
|
dumpFilename(sym);
|
||||||
|
putc('\n', stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FIXME: this leaks the previous sym->macro value, but this can't
|
||||||
|
* free(sym->macro) because the expansion may be redefining itself.
|
||||||
|
*/
|
||||||
|
|
||||||
|
assignStringSymbol(sym, value);
|
||||||
|
|
||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
@@ -492,8 +538,10 @@ struct Symbol *sym_AddLocalLabel(char const *name)
|
|||||||
* Check that `labelScope[i]` ended the check, guaranteeing that `name` is at least
|
* Check that `labelScope[i]` ended the check, guaranteeing that `name` is at least
|
||||||
* as long, and then that this was the entirety of the `Parent` part of `name`.
|
* as long, and then that this was the entirety of the `Parent` part of `name`.
|
||||||
*/
|
*/
|
||||||
if (labelScope[i] != '\0' || name[i] != '.')
|
if (labelScope[i] != '\0' || name[i] != '.') {
|
||||||
error("Not currently in the scope of '%.*s'\n", parentLen, name);
|
assert(parentLen <= INT_MAX);
|
||||||
|
error("Not currently in the scope of '%.*s'\n", (int)parentLen, name);
|
||||||
|
}
|
||||||
if (strchr(&name[parentLen + 1], '.')) /* There will at least be a terminator */
|
if (strchr(&name[parentLen + 1], '.')) /* There will at least be a terminator */
|
||||||
fatalerror("'%s' is a nonsensical reference to a nested local label\n",
|
fatalerror("'%s' is a nonsensical reference to a nested local label\n",
|
||||||
name);
|
name);
|
||||||
@@ -515,11 +563,60 @@ struct Symbol *sym_AddLabel(char const *name)
|
|||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint32_t anonLabelID;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add an anonymous label
|
||||||
|
*/
|
||||||
|
struct Symbol *sym_AddAnonLabel(void)
|
||||||
|
{
|
||||||
|
if (anonLabelID == UINT32_MAX) {
|
||||||
|
error("Only %" PRIu32 " anonymous labels can be created!", anonLabelID);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
char name[MAXSYMLEN + 1];
|
||||||
|
|
||||||
|
sym_WriteAnonLabelName(name, 0, true); // The direction is important!!
|
||||||
|
anonLabelID++;
|
||||||
|
return addLabel(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write an anonymous label's name to a buffer
|
||||||
|
*/
|
||||||
|
void sym_WriteAnonLabelName(char buf[MIN_NB_ELMS(MAXSYMLEN + 1)], uint32_t ofs, bool neg)
|
||||||
|
{
|
||||||
|
uint32_t id = 0;
|
||||||
|
|
||||||
|
if (neg) {
|
||||||
|
if (ofs > anonLabelID)
|
||||||
|
error("Reference to anonymous label %" PRIu32 " before, when only %" PRIu32
|
||||||
|
" ha%s been created so far\n",
|
||||||
|
ofs, anonLabelID, anonLabelID == 1 ? "s" : "ve");
|
||||||
|
else
|
||||||
|
id = anonLabelID - ofs;
|
||||||
|
} else {
|
||||||
|
ofs--; // We're referencing symbols that haven't been created yet...
|
||||||
|
if (ofs > UINT32_MAX - anonLabelID)
|
||||||
|
error("Reference to anonymous label %" PRIu32 " after, when only %" PRIu32
|
||||||
|
" may still be created\n", ofs + 1, UINT32_MAX - anonLabelID);
|
||||||
|
else
|
||||||
|
id = anonLabelID + ofs;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprintf(buf, "!%u", id);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Export a symbol
|
* Export a symbol
|
||||||
*/
|
*/
|
||||||
void sym_Export(char const *symName)
|
void sym_Export(char const *symName)
|
||||||
{
|
{
|
||||||
|
if (symName[0] == '!') {
|
||||||
|
error("Anonymous labels cannot be exported\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
struct Symbol *sym = sym_FindScopedSymbol(symName);
|
struct Symbol *sym = sym_FindScopedSymbol(symName);
|
||||||
|
|
||||||
/* If the symbol doesn't exist, create a ref that can be purged */
|
/* If the symbol doesn't exist, create a ref that can be purged */
|
||||||
@@ -533,7 +630,10 @@ void sym_Export(char const *symName)
|
|||||||
*/
|
*/
|
||||||
struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body, size_t size)
|
struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body, size_t size)
|
||||||
{
|
{
|
||||||
struct Symbol *sym = createNonrelocSymbol(symName);
|
struct Symbol *sym = createNonrelocSymbol(symName, false);
|
||||||
|
|
||||||
|
if (!sym)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
sym->type = SYM_MACRO;
|
sym->type = SYM_MACRO;
|
||||||
sym->macroSize = size;
|
sym->macroSize = size;
|
||||||
@@ -599,14 +699,14 @@ static inline struct Symbol *createBuiltinSymbol(char const *name)
|
|||||||
sym->isBuiltin = true;
|
sym->isBuiltin = true;
|
||||||
sym->hasCallback = true;
|
sym->hasCallback = true;
|
||||||
sym->src = NULL;
|
sym->src = NULL;
|
||||||
sym->fileLine = 0;
|
sym->fileLine = 1; // This is 0 for CLI-defined symbols
|
||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Initialize the symboltable
|
* Initialize the symboltable
|
||||||
*/
|
*/
|
||||||
void sym_Init(void)
|
void sym_Init(time_t now)
|
||||||
{
|
{
|
||||||
PCSymbol = createBuiltinSymbol("@");
|
PCSymbol = createBuiltinSymbol("@");
|
||||||
struct Symbol *_NARGSymbol = createBuiltinSymbol("_NARG");
|
struct Symbol *_NARGSymbol = createBuiltinSymbol("_NARG");
|
||||||
@@ -627,8 +727,9 @@ void sym_Init(void)
|
|||||||
sym_AddEqu("__RGBDS_MAJOR__", PACKAGE_VERSION_MAJOR)->isBuiltin = true;
|
sym_AddEqu("__RGBDS_MAJOR__", PACKAGE_VERSION_MAJOR)->isBuiltin = true;
|
||||||
sym_AddEqu("__RGBDS_MINOR__", PACKAGE_VERSION_MINOR)->isBuiltin = true;
|
sym_AddEqu("__RGBDS_MINOR__", PACKAGE_VERSION_MINOR)->isBuiltin = true;
|
||||||
sym_AddEqu("__RGBDS_PATCH__", PACKAGE_VERSION_PATCH)->isBuiltin = true;
|
sym_AddEqu("__RGBDS_PATCH__", PACKAGE_VERSION_PATCH)->isBuiltin = true;
|
||||||
|
#ifdef PACKAGE_VERSION_RC
|
||||||
time_t now = time(NULL);
|
sym_AddEqu("__RGBDS_RC__", PACKAGE_VERSION_RC)->isBuiltin = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (now == (time_t)-1) {
|
if (now == (time_t)-1) {
|
||||||
warn("Couldn't determine current time");
|
warn("Couldn't determine current time");
|
||||||
@@ -671,6 +772,14 @@ void sym_Init(void)
|
|||||||
#undef addString
|
#undef addString
|
||||||
|
|
||||||
labelScope = NULL;
|
labelScope = NULL;
|
||||||
|
anonLabelID = 0;
|
||||||
|
|
||||||
math_DefinePI();
|
/* _PI is deprecated */
|
||||||
|
struct Symbol *_PISymbol = createBuiltinSymbol("_PI");
|
||||||
|
|
||||||
|
_PISymbol->type = SYM_EQU;
|
||||||
|
_PISymbol->src = NULL;
|
||||||
|
_PISymbol->fileLine = 0;
|
||||||
|
_PISymbol->hasCallback = true;
|
||||||
|
_PISymbol->numCallback = fix_Callback_PI;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,9 @@
|
|||||||
#include "extern/utf8decoder.h"
|
#include "extern/utf8decoder.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Calculate the hash value for a string
|
* Calculate the hash value for a string.
|
||||||
|
* Uses the djb2 algorithm (xor version).
|
||||||
|
* http://www.cse.yorku.ca/~oz/hash.html
|
||||||
*/
|
*/
|
||||||
uint32_t calchash(const char *s)
|
uint32_t calchash(const char *s)
|
||||||
{
|
{
|
||||||
@@ -70,7 +72,7 @@ size_t readUTF8Char(uint8_t *dest, char const *src)
|
|||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (decode(&state, &codep, src[i]) == 1)
|
if (decode(&state, &codep, src[i]) == 1)
|
||||||
fatalerror("invalid UTF-8 character\n");
|
return 0;
|
||||||
|
|
||||||
dest[i] = src[i];
|
dest[i] = src[i];
|
||||||
i++;
|
i++;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "asm/fstack.h"
|
#include "asm/fstack.h"
|
||||||
@@ -33,9 +34,11 @@ static enum WarningState const defaultWarnings[NB_WARNINGS] = {
|
|||||||
[WARNING_CHARMAP_REDEF] = WARNING_DISABLED,
|
[WARNING_CHARMAP_REDEF] = WARNING_DISABLED,
|
||||||
[WARNING_DIV] = WARNING_DISABLED,
|
[WARNING_DIV] = WARNING_DISABLED,
|
||||||
[WARNING_EMPTY_DATA_DIRECTIVE] = WARNING_DISABLED,
|
[WARNING_EMPTY_DATA_DIRECTIVE] = WARNING_DISABLED,
|
||||||
[WARNING_EMPTY_ENTRY] = WARNING_DISABLED,
|
[WARNING_EMPTY_MACRO_ARG] = WARNING_DISABLED,
|
||||||
|
[WARNING_EMPTY_STRRPL] = WARNING_DISABLED,
|
||||||
[WARNING_LARGE_CONSTANT] = WARNING_DISABLED,
|
[WARNING_LARGE_CONSTANT] = WARNING_DISABLED,
|
||||||
[WARNING_LONG_STR] = WARNING_DISABLED,
|
[WARNING_LONG_STR] = WARNING_DISABLED,
|
||||||
|
[WARNING_MACRO_SHIFT] = WARNING_DISABLED,
|
||||||
[WARNING_NESTED_COMMENT] = WARNING_ENABLED,
|
[WARNING_NESTED_COMMENT] = WARNING_ENABLED,
|
||||||
[WARNING_OBSOLETE] = WARNING_ENABLED,
|
[WARNING_OBSOLETE] = WARNING_ENABLED,
|
||||||
[WARNING_SHIFT] = WARNING_DISABLED,
|
[WARNING_SHIFT] = WARNING_DISABLED,
|
||||||
@@ -73,9 +76,11 @@ static char const *warningFlags[NB_WARNINGS_ALL] = {
|
|||||||
"charmap-redef",
|
"charmap-redef",
|
||||||
"div",
|
"div",
|
||||||
"empty-data-directive",
|
"empty-data-directive",
|
||||||
"empty-entry",
|
"empty-macro-arg",
|
||||||
|
"empty-strrpl",
|
||||||
"large-constant",
|
"large-constant",
|
||||||
"long-string",
|
"long-string",
|
||||||
|
"macro-shift",
|
||||||
"nested-comment",
|
"nested-comment",
|
||||||
"obsolete",
|
"obsolete",
|
||||||
"shift",
|
"shift",
|
||||||
@@ -98,6 +103,7 @@ static uint8_t const _wallCommands[] = {
|
|||||||
WARNING_BUILTIN_ARG,
|
WARNING_BUILTIN_ARG,
|
||||||
WARNING_CHARMAP_REDEF,
|
WARNING_CHARMAP_REDEF,
|
||||||
WARNING_EMPTY_DATA_DIRECTIVE,
|
WARNING_EMPTY_DATA_DIRECTIVE,
|
||||||
|
WARNING_EMPTY_STRRPL,
|
||||||
WARNING_LARGE_CONSTANT,
|
WARNING_LARGE_CONSTANT,
|
||||||
WARNING_LONG_STR,
|
WARNING_LONG_STR,
|
||||||
META_WARNING_DONE
|
META_WARNING_DONE
|
||||||
@@ -105,7 +111,8 @@ static uint8_t const _wallCommands[] = {
|
|||||||
|
|
||||||
/* Warnings that are less likely to indicate an error */
|
/* Warnings that are less likely to indicate an error */
|
||||||
static uint8_t const _wextraCommands[] = {
|
static uint8_t const _wextraCommands[] = {
|
||||||
WARNING_EMPTY_ENTRY,
|
WARNING_EMPTY_MACRO_ARG,
|
||||||
|
WARNING_MACRO_SHIFT,
|
||||||
WARNING_NESTED_COMMENT,
|
WARNING_NESTED_COMMENT,
|
||||||
META_WARNING_DONE
|
META_WARNING_DONE
|
||||||
};
|
};
|
||||||
@@ -115,9 +122,11 @@ static uint8_t const _weverythingCommands[] = {
|
|||||||
WARNING_BUILTIN_ARG,
|
WARNING_BUILTIN_ARG,
|
||||||
WARNING_DIV,
|
WARNING_DIV,
|
||||||
WARNING_EMPTY_DATA_DIRECTIVE,
|
WARNING_EMPTY_DATA_DIRECTIVE,
|
||||||
WARNING_EMPTY_ENTRY,
|
WARNING_EMPTY_MACRO_ARG,
|
||||||
|
WARNING_EMPTY_STRRPL,
|
||||||
WARNING_LARGE_CONSTANT,
|
WARNING_LARGE_CONSTANT,
|
||||||
WARNING_LONG_STR,
|
WARNING_LONG_STR,
|
||||||
|
WARNING_MACRO_SHIFT,
|
||||||
WARNING_NESTED_COMMENT,
|
WARNING_NESTED_COMMENT,
|
||||||
WARNING_OBSOLETE,
|
WARNING_OBSOLETE,
|
||||||
WARNING_SHIFT,
|
WARNING_SHIFT,
|
||||||
|
|||||||
7
src/check_bison_ver.sh
Executable file
7
src/check_bison_ver.sh
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
bison -V | awk -v major="$1" -v minor="$2" '
|
||||||
|
/^bison.*[0-9]+(\.[0-9]+)(\.[0-9]+)?$/ {
|
||||||
|
match($0, /[0-9]+(\.[0-9]+)(\.[0-9]+)?$/);
|
||||||
|
split(substr($0, RSTART), ver, ".");
|
||||||
|
if (ver[1] == major && ver[2] >= minor) { exit 0 } else { exit 1 }
|
||||||
|
}'
|
||||||
183
src/extern/getopt.c
vendored
183
src/extern/getopt.c
vendored
@@ -26,21 +26,16 @@
|
|||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#ifndef _MSC_VER
|
|
||||||
# include <unistd.h>
|
|
||||||
#endif
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <wchar.h>
|
#include <wchar.h>
|
||||||
|
|
||||||
#include "extern/getopt.h"
|
#include "extern/getopt.h"
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
char *musl_optarg;
|
||||||
char *optarg;
|
int musl_optind = 1, musl_opterr = 1, musl_optopt;
|
||||||
int optind=1, opterr=1, optopt;
|
int musl_optreset = 0;
|
||||||
#endif
|
static int musl_optpos;
|
||||||
int optreset=0;
|
|
||||||
static int optpos;
|
|
||||||
|
|
||||||
static void musl_getopt_msg(const char *a, const char *b, const char *c, size_t l)
|
static void musl_getopt_msg(const char *a, const char *b, const char *c, size_t l)
|
||||||
{
|
{
|
||||||
@@ -52,7 +47,6 @@ static void musl_getopt_msg(const char *a, const char *b, const char *c, size_t
|
|||||||
putc('\n', f);
|
putc('\n', f);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
static int getopt(int argc, char *argv[], const char *optstring)
|
static int getopt(int argc, char *argv[], const char *optstring)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
@@ -60,40 +54,42 @@ static int getopt(int argc, char *argv[], const char *optstring)
|
|||||||
int k, l;
|
int k, l;
|
||||||
char *optchar;
|
char *optchar;
|
||||||
|
|
||||||
if (!optind || optreset) {
|
if (!musl_optind || musl_optreset) {
|
||||||
optreset = 0;
|
musl_optreset = 0;
|
||||||
optpos = 0;
|
musl_optpos = 0;
|
||||||
optind = 1;
|
musl_optind = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (optind >= argc || !argv[optind])
|
if (musl_optind >= argc || !argv[musl_optind])
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (argv[optind][0] != '-') {
|
if (argv[musl_optind][0] != '-') {
|
||||||
if (optstring[0] == '-') {
|
if (optstring[0] == '-') {
|
||||||
optarg = argv[optind++];
|
musl_optarg = argv[musl_optind++];
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!argv[optind][1])
|
if (!argv[musl_optind][1])
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (argv[optind][1] == '-' && !argv[optind][2])
|
if (argv[musl_optind][1] == '-' && !argv[musl_optind][2])
|
||||||
return optind++, -1;
|
return musl_optind++, -1;
|
||||||
|
|
||||||
if (!optpos) optpos++;
|
if (!musl_optpos)
|
||||||
if ((k = mbtowc(&c, argv[optind]+optpos, MB_LEN_MAX)) < 0) {
|
musl_optpos++;
|
||||||
|
k = mbtowc(&c, argv[musl_optind] + musl_optpos, MB_LEN_MAX);
|
||||||
|
if (k < 0) {
|
||||||
k = 1;
|
k = 1;
|
||||||
c = 0xfffd; /* replacement char */
|
c = 0xfffd; /* replacement char */
|
||||||
}
|
}
|
||||||
optchar = argv[optind]+optpos;
|
optchar = argv[musl_optind] + musl_optpos;
|
||||||
optpos += k;
|
musl_optpos += k;
|
||||||
|
|
||||||
if (!argv[optind][optpos]) {
|
if (!argv[musl_optind][musl_optpos]) {
|
||||||
optind++;
|
musl_optind++;
|
||||||
optpos = 0;
|
musl_optpos = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (optstring[0] == '-' || optstring[0] == '+')
|
if (optstring[0] == '-' || optstring[0] == '+')
|
||||||
@@ -103,38 +99,42 @@ static int getopt(int argc, char *argv[], const char *optstring)
|
|||||||
d = 0;
|
d = 0;
|
||||||
do {
|
do {
|
||||||
l = mbtowc(&d, optstring+i, MB_LEN_MAX);
|
l = mbtowc(&d, optstring+i, MB_LEN_MAX);
|
||||||
if (l>0) i+=l; else i++;
|
if (l > 0)
|
||||||
|
i += l;
|
||||||
|
else
|
||||||
|
i++;
|
||||||
} while (l && d != c);
|
} while (l && d != c);
|
||||||
|
|
||||||
if (d != c || c == ':') {
|
if (d != c || c == ':') {
|
||||||
optopt = c;
|
musl_optopt = c;
|
||||||
if (optstring[0] != ':' && opterr)
|
if (optstring[0] != ':' && musl_opterr)
|
||||||
musl_getopt_msg(argv[0], ": unrecognized option: ", optchar, k);
|
musl_getopt_msg(argv[0], ": unrecognized option: ", optchar, k);
|
||||||
return '?';
|
return '?';
|
||||||
}
|
}
|
||||||
if (optstring[i] == ':') {
|
if (optstring[i] == ':') {
|
||||||
optarg = 0;
|
musl_optarg = 0;
|
||||||
if (optstring[i+1] != ':' || optpos) {
|
if (optstring[i + 1] != ':' || musl_optpos) {
|
||||||
optarg = argv[optind++] + optpos;
|
musl_optarg = argv[musl_optind++] + musl_optpos;
|
||||||
optpos = 0;
|
musl_optpos = 0;
|
||||||
}
|
}
|
||||||
if (optind > argc) {
|
if (musl_optind > argc) {
|
||||||
optopt = c;
|
musl_optopt = c;
|
||||||
if (optstring[0] == ':') return ':';
|
if (optstring[0] == ':')
|
||||||
if (opterr) musl_getopt_msg(argv[0],
|
return ':';
|
||||||
": option requires an argument: ",
|
if (musl_opterr)
|
||||||
|
musl_getopt_msg(argv[0], ": option requires an argument: ",
|
||||||
optchar, k);
|
optchar, k);
|
||||||
return '?';
|
return '?';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
#endif /* _MSC_VER */
|
|
||||||
|
|
||||||
static void permute(char **argv, int dest, int src)
|
static void permute(char **argv, int dest, int src)
|
||||||
{
|
{
|
||||||
char *tmp = argv[src];
|
char *tmp = argv[src];
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = src; i > dest; i--)
|
for (i = src; i > dest; i--)
|
||||||
argv[i] = argv[i-1];
|
argv[i] = argv[i-1];
|
||||||
argv[dest] = tmp;
|
argv[dest] = tmp;
|
||||||
@@ -145,49 +145,61 @@ static int musl_getopt_long_core(int argc, char **argv, const char *optstring, c
|
|||||||
static int musl_getopt_long(int argc, char **argv, const char *optstring, const struct option *longopts, int *idx, int longonly)
|
static int musl_getopt_long(int argc, char **argv, const char *optstring, const struct option *longopts, int *idx, int longonly)
|
||||||
{
|
{
|
||||||
int ret, skipped, resumed;
|
int ret, skipped, resumed;
|
||||||
if (!optind || optreset) {
|
|
||||||
optreset = 0;
|
if (!musl_optind || musl_optreset) {
|
||||||
optpos = 0;
|
musl_optreset = 0;
|
||||||
optind = 1;
|
musl_optpos = 0;
|
||||||
|
musl_optind = 1;
|
||||||
}
|
}
|
||||||
if (optind >= argc || !argv[optind]) return -1;
|
|
||||||
skipped = optind;
|
if (musl_optind >= argc || !argv[musl_optind])
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
skipped = musl_optind;
|
||||||
if (optstring[0] != '+' && optstring[0] != '-') {
|
if (optstring[0] != '+' && optstring[0] != '-') {
|
||||||
int i;
|
int i;
|
||||||
for (i=optind; ; i++) {
|
for (i = musl_optind; ; i++) {
|
||||||
if (i >= argc || !argv[i]) return -1;
|
if (i >= argc || !argv[i])
|
||||||
if (argv[i][0] == '-' && argv[i][1]) break;
|
return -1;
|
||||||
|
if (argv[i][0] == '-' && argv[i][1])
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
optind = i;
|
musl_optind = i;
|
||||||
}
|
}
|
||||||
resumed = optind;
|
resumed = musl_optind;
|
||||||
ret = musl_getopt_long_core(argc, argv, optstring, longopts, idx, longonly);
|
ret = musl_getopt_long_core(argc, argv, optstring, longopts, idx, longonly);
|
||||||
if (resumed > skipped) {
|
if (resumed > skipped) {
|
||||||
int i, cnt = optind-resumed;
|
int i, cnt = musl_optind - resumed;
|
||||||
|
|
||||||
for (i = 0; i < cnt; i++)
|
for (i = 0; i < cnt; i++)
|
||||||
permute(argv, skipped, optind-1);
|
permute(argv, skipped, musl_optind - 1);
|
||||||
optind = skipped + cnt;
|
musl_optind = skipped + cnt;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int musl_getopt_long_core(int argc, char **argv, const char *optstring, const struct option *longopts, int *idx, int longonly)
|
static int musl_getopt_long_core(int argc, char **argv, const char *optstring, const struct option *longopts, int *idx, int longonly)
|
||||||
{
|
{
|
||||||
optarg = 0;
|
musl_optarg = 0;
|
||||||
if (longopts && argv[optind][0] == '-' &&
|
if (longopts && argv[musl_optind][0] == '-' &&
|
||||||
((longonly && argv[optind][1] && argv[optind][1] != '-') ||
|
((longonly && argv[musl_optind][1] && argv[musl_optind][1] != '-') ||
|
||||||
(argv[optind][1] == '-' && argv[optind][2])))
|
(argv[musl_optind][1] == '-' && argv[musl_optind][2]))) {
|
||||||
{
|
|
||||||
int colon = optstring[optstring[0] == '+' || optstring[0] == '-'] == ':';
|
int colon = optstring[optstring[0] == '+' || optstring[0] == '-'] == ':';
|
||||||
int i, cnt, match = 0;
|
int i, cnt, match = 0;
|
||||||
char *arg = 0, *opt, *start = argv[optind]+1;
|
char *arg = 0, *opt, *start = argv[musl_optind] + 1;
|
||||||
|
|
||||||
for (cnt = i = 0; longopts[i].name; i++) {
|
for (cnt = i = 0; longopts[i].name; i++) {
|
||||||
const char *name = longopts[i].name;
|
const char *name = longopts[i].name;
|
||||||
|
|
||||||
opt = start;
|
opt = start;
|
||||||
if (*opt == '-') opt++;
|
if (*opt == '-')
|
||||||
while (*opt && *opt != '=' && *opt == *name)
|
opt++;
|
||||||
name++, opt++;
|
while (*opt && *opt != '=' && *opt == *name) {
|
||||||
if (*opt && *opt != '=') continue;
|
name++;
|
||||||
|
opt++;
|
||||||
|
}
|
||||||
|
if (*opt && *opt != '=')
|
||||||
|
continue;
|
||||||
arg = opt;
|
arg = opt;
|
||||||
match = i;
|
match = i;
|
||||||
if (!*name) {
|
if (!*name) {
|
||||||
@@ -198,9 +210,12 @@ static int musl_getopt_long_core(int argc, char **argv, const char *optstring, c
|
|||||||
}
|
}
|
||||||
if (cnt == 1 && longonly && arg - start == mblen(start, MB_LEN_MAX)) {
|
if (cnt == 1 && longonly && arg - start == mblen(start, MB_LEN_MAX)) {
|
||||||
int l = arg - start;
|
int l = arg - start;
|
||||||
|
|
||||||
for (i = 0; optstring[i]; i++) {
|
for (i = 0; optstring[i]; i++) {
|
||||||
int j;
|
int j = 0;
|
||||||
for (j=0; j<l && start[j]==optstring[i+j]; j++);
|
|
||||||
|
while (j < l && start[j] == optstring[i + j])
|
||||||
|
j++;
|
||||||
if (j == l) {
|
if (j == l) {
|
||||||
cnt++;
|
cnt++;
|
||||||
break;
|
break;
|
||||||
@@ -210,11 +225,11 @@ static int musl_getopt_long_core(int argc, char **argv, const char *optstring, c
|
|||||||
if (cnt == 1) {
|
if (cnt == 1) {
|
||||||
i = match;
|
i = match;
|
||||||
opt = arg;
|
opt = arg;
|
||||||
optind++;
|
musl_optind++;
|
||||||
if (*opt == '=') {
|
if (*opt == '=') {
|
||||||
if (!longopts[i].has_arg) {
|
if (!longopts[i].has_arg) {
|
||||||
optopt = longopts[i].val;
|
musl_optopt = longopts[i].val;
|
||||||
if (colon || !opterr)
|
if (colon || !musl_opterr)
|
||||||
return '?';
|
return '?';
|
||||||
musl_getopt_msg(argv[0],
|
musl_getopt_msg(argv[0],
|
||||||
": option does not take an argument: ",
|
": option does not take an argument: ",
|
||||||
@@ -222,36 +237,40 @@ static int musl_getopt_long_core(int argc, char **argv, const char *optstring, c
|
|||||||
strlen(longopts[i].name));
|
strlen(longopts[i].name));
|
||||||
return '?';
|
return '?';
|
||||||
}
|
}
|
||||||
optarg = opt+1;
|
musl_optarg = opt + 1;
|
||||||
} else if (longopts[i].has_arg == required_argument) {
|
} else if (longopts[i].has_arg == required_argument) {
|
||||||
if (!(optarg = argv[optind])) {
|
musl_optarg = argv[musl_optind];
|
||||||
optopt = longopts[i].val;
|
if (!musl_optarg) {
|
||||||
if (colon) return ':';
|
musl_optopt = longopts[i].val;
|
||||||
if (!opterr) return '?';
|
if (colon)
|
||||||
|
return ':';
|
||||||
|
if (!musl_opterr)
|
||||||
|
return '?';
|
||||||
musl_getopt_msg(argv[0],
|
musl_getopt_msg(argv[0],
|
||||||
": option requires an argument: ",
|
": option requires an argument: ",
|
||||||
longopts[i].name,
|
longopts[i].name,
|
||||||
strlen(longopts[i].name));
|
strlen(longopts[i].name));
|
||||||
return '?';
|
return '?';
|
||||||
}
|
}
|
||||||
optind++;
|
musl_optind++;
|
||||||
}
|
}
|
||||||
if (idx) *idx = i;
|
if (idx)
|
||||||
|
*idx = i;
|
||||||
if (longopts[i].flag) {
|
if (longopts[i].flag) {
|
||||||
*longopts[i].flag = longopts[i].val;
|
*longopts[i].flag = longopts[i].val;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return longopts[i].val;
|
return longopts[i].val;
|
||||||
}
|
}
|
||||||
if (argv[optind][1] == '-') {
|
if (argv[musl_optind][1] == '-') {
|
||||||
optopt = 0;
|
musl_optopt = 0;
|
||||||
if (!colon && opterr)
|
if (!colon && musl_opterr)
|
||||||
musl_getopt_msg(argv[0], cnt ?
|
musl_getopt_msg(argv[0], cnt ?
|
||||||
": option is ambiguous: " :
|
": option is ambiguous: " :
|
||||||
": unrecognized option: ",
|
": unrecognized option: ",
|
||||||
argv[optind]+2,
|
argv[musl_optind] + 2,
|
||||||
strlen(argv[optind]+2));
|
strlen(argv[musl_optind] + 2));
|
||||||
optind++;
|
musl_optind++;
|
||||||
return '?';
|
return '?';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1429
src/fix/main.c
1429
src/fix/main.c
File diff suppressed because it is too large
Load Diff
115
src/fix/rgbfix.1
115
src/fix/rgbfix.1
@@ -24,39 +24,48 @@
|
|||||||
.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
|
||||||
.Ar file
|
.Op Ar
|
||||||
.Sh DESCRIPTION
|
.Sh DESCRIPTION
|
||||||
The
|
The
|
||||||
.Nm
|
.Nm
|
||||||
program changes headers of Game Boy ROM images.
|
program changes headers of Game Boy ROM images, typically generated by
|
||||||
|
.Xr rgblink 1 ,
|
||||||
|
though it will work with
|
||||||
|
.Em any
|
||||||
|
Game Boy ROM.
|
||||||
It also performs other correctness operations, such as padding.
|
It also performs other correctness operations, such as padding.
|
||||||
|
.Nm
|
||||||
|
only changes the fields for which it has values specified.
|
||||||
|
Developers are advised to fill those fields with 0x00 bytes in their source code before running
|
||||||
|
.Nm ,
|
||||||
|
and to have already populated whichever fields they don't specify using
|
||||||
|
.Nm .
|
||||||
.Pp
|
.Pp
|
||||||
Note that options can be abbreviated as long as the abbreviation is unambiguous:
|
Note that options can be abbreviated as long as the abbreviation is unambiguous:
|
||||||
.Fl Fl verb
|
.Fl Fl color-o
|
||||||
is
|
is
|
||||||
.Fl Fl verbose ,
|
.Fl Fl color-only ,
|
||||||
but
|
but
|
||||||
.Fl Fl ver
|
.Fl Fl color
|
||||||
is invalid because it could also be
|
is invalid because it could also be
|
||||||
.Fl Fl version .
|
.Fl Fl color-compatible .
|
||||||
The arguments are as follows:
|
Options later in the command line override those set earlier.
|
||||||
|
Accepted options are as follows:
|
||||||
.Bl -tag -width Ds
|
.Bl -tag -width Ds
|
||||||
.It Fl C , Fl Fl color-only
|
.It Fl C , Fl Fl color-only
|
||||||
Set the Game Boy Color\(enonly flag:
|
Set the Game Boy Color\(enonly flag
|
||||||
.Ad 0x143
|
.Pq Ad 0x143
|
||||||
= 0xC0.
|
to 0xC0.
|
||||||
If both this and the
|
This overrides
|
||||||
.Fl c
|
.Fl c
|
||||||
flag are set, this takes precedence.
|
if it was set prior.
|
||||||
.It Fl c , Fl Fl color-compatible
|
.It Fl c , Fl Fl color-compatible
|
||||||
Set the Game Boy Color\(encompatible flag:
|
Set the Game Boy Color\(encompatible flag:
|
||||||
.Ad 0x143
|
.Pq Ad 0x143
|
||||||
= 0x80.
|
to 0x80.
|
||||||
If both this and the
|
This overrides
|
||||||
.Fl C
|
.Fl c
|
||||||
flag are set,
|
if it was set prior.
|
||||||
.Fl C
|
|
||||||
takes precedence.
|
|
||||||
.It Fl f Ar fix_spec , Fl Fl fix-spec Ar fix_spec
|
.It Fl f Ar fix_spec , Fl Fl fix-spec Ar fix_spec
|
||||||
Fix certain header values that the Game Boy checks for correctness.
|
Fix certain header values that the Game Boy checks for correctness.
|
||||||
Alternatively, intentionally trash these values by writing their binary inverse instead.
|
Alternatively, intentionally trash these values by writing their binary inverse instead.
|
||||||
@@ -83,54 +92,63 @@ Trash the global checksum.
|
|||||||
.It Fl i Ar game_id , Fl Fl game-id Ar game_id
|
.It Fl i Ar game_id , Fl Fl game-id Ar game_id
|
||||||
Set the game ID string
|
Set the game ID string
|
||||||
.Pq Ad 0x13F Ns \(en Ns Ad 0x142
|
.Pq Ad 0x13F Ns \(en Ns Ad 0x142
|
||||||
to a given string of exactly 4 characters.
|
to a given string.
|
||||||
If both this and the title are set, the game ID will overwrite the overlapping portion of the title.
|
If it's longer than 4 chars, it will be truncated, and a warning emitted.
|
||||||
.It Fl j , Fl Fl non-japanese
|
.It Fl j , Fl Fl non-japanese
|
||||||
Set the non-Japanese region flag:
|
Set the non-Japanese region flag
|
||||||
.Ad 0x14A
|
.Pq Ad 0x14A
|
||||||
= 1.
|
to 0x01.
|
||||||
.It Fl k Ar licensee_str , Fl Fl new-licensee Ar licensee_str
|
.It Fl k Ar licensee_str , Fl Fl new-licensee Ar licensee_str
|
||||||
Set the new licensee string
|
Set the new licensee string
|
||||||
.Pq Ad 0x144 Ns \(en Ns Ad 0x145
|
.Pq Ad 0x144 Ns \(en Ns Ad 0x145
|
||||||
to a given string, truncated to at most two characters.
|
to a given string.
|
||||||
|
If it's longer than 2 chars, it will be truncated, and a warning emitted.
|
||||||
.It Fl l Ar licensee_id , Fl Fl old-licensee Ar licensee_id
|
.It Fl l Ar licensee_id , Fl Fl old-licensee Ar licensee_id
|
||||||
Set the old licensee code,
|
Set the old licensee code
|
||||||
.Ad 0x14B ,
|
.Pq Ad 0x14B
|
||||||
to a given value from 0 to 0xFF.
|
to a given value from 0 to 0xFF.
|
||||||
This value is deprecated and should be set to 0x33 in all new software.
|
This value is deprecated and should be set to 0x33 in all new software.
|
||||||
.It Fl m Ar mbc_type , Fl Fl mbc-type Ar mbc_type
|
.It Fl m Ar mbc_type , Fl Fl mbc-type Ar mbc_type
|
||||||
Set the MBC type,
|
Set the MBC type
|
||||||
.Ad 0x147 ,
|
.Pq Ad 0x147
|
||||||
to a given value from 0 to 0xFF.
|
to a given value from 0 to 0xFF.
|
||||||
|
This value may also be an MBC name from the Pan Docs.
|
||||||
.It Fl n Ar rom_version , Fl Fl rom-version Ar rom_version
|
.It Fl n Ar rom_version , Fl Fl rom-version Ar rom_version
|
||||||
Set the ROM version,
|
Set the ROM version
|
||||||
.Ad 0x14C ,
|
.Pq Ad 0x14C
|
||||||
to a given value from 0 to 0xFF.
|
to a given value from 0 to 0xFF.
|
||||||
.It Fl p Ar pad_value , Fl Fl pad-value Ar pad_value
|
.It Fl p Ar pad_value , Fl Fl pad-value Ar pad_value
|
||||||
Pad the image to a valid size with a given pad value from 0 to 0xFF.
|
Pad the ROM image to a valid size with a given pad value from 0 to 255 (0xFF).
|
||||||
.Nm
|
.Nm
|
||||||
will automatically pick a size from 32 KiB, 64 KiB, 128 KiB, ..., 8192 KiB.
|
will automatically pick a size from 32 KiB, 64 KiB, 128 KiB, ..., 8192 KiB.
|
||||||
The cartridge size byte
|
The cartridge size byte
|
||||||
.Pq Ad 0x148
|
.Pq Ad 0x148
|
||||||
will be changed to reflect this new size.
|
will be changed to reflect this new size.
|
||||||
|
The recommended padding value is 0xFF, to speed up writing the ROM to flash chips, and to avoid "nop slides" into VRAM.
|
||||||
.It Fl r Ar ram_size , Fl Fl ram-size Ar ram_size
|
.It Fl r Ar ram_size , Fl Fl ram-size Ar ram_size
|
||||||
Set the RAM size,
|
Set the RAM size
|
||||||
.Ad 0x149 ,
|
.Pq Ad 0x149
|
||||||
to a given value from 0 to 0xFF.
|
to a given value from 0 to 0xFF.
|
||||||
.It Fl s , Fl Fl sgb-compatible
|
.It Fl s , Fl Fl sgb-compatible
|
||||||
Set the SGB flag:
|
Set the SGB flag
|
||||||
.Ad 0x146
|
.Pq Ad 0x146
|
||||||
= 3. This flag will be ignored by the SGB unless the old licensee code is 0x33!
|
to 0x03.
|
||||||
|
This flag will be ignored by the SGB unless the old licensee code is 0x33!
|
||||||
|
If this is given as well as
|
||||||
|
.Fl l ,
|
||||||
|
but is not set to 0x33, a warning will be printed.
|
||||||
.It Fl t Ar title , Fl Fl title Ar title
|
.It Fl t Ar title , Fl Fl title Ar title
|
||||||
Set the title string
|
Set the title string
|
||||||
.Pq Ad 0x134 Ns \(en Ns Ad 0x143
|
.Pq Ad 0x134 Ns \(en Ns Ad 0x143
|
||||||
to a given string, truncated to at most 16 characters.
|
to a given string.
|
||||||
It is recommended to use 15 characters instead, to avoid clashing with the CGB flag
|
If the title is longer than the max length, it will be truncated, and a warning emitted.
|
||||||
.Po Fl c
|
The max length is 11 characters if the game ID
|
||||||
|
.Pq Fl i
|
||||||
|
is specified, 15 characters if the CGB flag
|
||||||
|
.Fl ( c
|
||||||
or
|
or
|
||||||
.Fl C
|
.Fl C )
|
||||||
.Pc .
|
is specified but the game ID is not, and 16 characters otherwise.
|
||||||
If both this and the game ID are set, the game ID will overwrite the overlapping portion of the title.
|
|
||||||
.It Fl V , Fl Fl version
|
.It Fl V , Fl Fl version
|
||||||
Print the version of the program and exit.
|
Print the version of the program and exit.
|
||||||
.It Fl v , Fl Fl validate
|
.It Fl v , Fl Fl validate
|
||||||
@@ -138,7 +156,7 @@ Equivalent to
|
|||||||
.Fl f Cm lhg .
|
.Fl f Cm lhg .
|
||||||
.El
|
.El
|
||||||
.Sh EXAMPLES
|
.Sh EXAMPLES
|
||||||
Most values in the ROM header are only cosmetic.
|
Most values in the ROM header do not matter to the actual console, and most are seldom useful anyway.
|
||||||
The bare minimum requirements for a workable program are the header checksum, the Nintendo logo, and (if needed) the CGB/SGB flags.
|
The bare minimum requirements for a workable program are the header checksum, the Nintendo logo, and (if needed) the CGB/SGB flags.
|
||||||
It is a good idea to pad the image to a valid size as well
|
It is a good idea to pad the image to a valid size as well
|
||||||
.Pq Do valid Dc meaning a power of 2, times 32 KiB .
|
.Pq Do valid Dc meaning a power of 2, times 32 KiB .
|
||||||
@@ -151,14 +169,13 @@ a valid size:
|
|||||||
The following will make a SGB-enabled, color-enabled game with a title of
|
The following will make a SGB-enabled, color-enabled game with a title of
|
||||||
.Dq foobar ,
|
.Dq foobar ,
|
||||||
and pad it to a valid size.
|
and pad it to a valid size.
|
||||||
.Po
|
.Pq The Game Boy itself does not use the title, but some emulators or ROM managers do.
|
||||||
The Game Boy itself does not use the title, but some emulators or ROM managers do.
|
|
||||||
.Pc
|
|
||||||
.Pp
|
.Pp
|
||||||
.D1 $ rgbfix -vcs -l 0x33 -p 255 -t foobar baz.gb
|
.D1 $ rgbfix -vcs -l 0x33 -p 255 -t foobar baz.gb
|
||||||
.Pp
|
.Pp
|
||||||
The following will duplicate the header (sans global checksum) of the game
|
The following will duplicate the header of the game
|
||||||
.Dq Survival Kids :
|
.Dq Survival Kids ,
|
||||||
|
sans global checksum:
|
||||||
.Pp
|
.Pp
|
||||||
.D1 $ rgbfix -cjsv -k A4 -l 0x33 -m 0x1B -p 0xFF -r 3 -t SURVIVALKIDAVKE \
|
.D1 $ rgbfix -cjsv -k A4 -l 0x33 -m 0x1B -p 0xFF -r 3 -t SURVIVALKIDAVKE \
|
||||||
SurvivalKids.gbc
|
SurvivalKids.gbc
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ int main(int argc, char *argv[])
|
|||||||
opts.attrmapout = true;
|
opts.attrmapout = true;
|
||||||
break;
|
break;
|
||||||
case 'a':
|
case 'a':
|
||||||
opts.attrmapfile = optarg;
|
opts.attrmapfile = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'C':
|
case 'C':
|
||||||
opts.colorcurve = true;
|
opts.colorcurve = true;
|
||||||
@@ -105,7 +105,7 @@ int main(int argc, char *argv[])
|
|||||||
opts.debug = true;
|
opts.debug = true;
|
||||||
break;
|
break;
|
||||||
case 'd':
|
case 'd':
|
||||||
depth = strtoul(optarg, NULL, 0);
|
depth = strtoul(musl_optarg, NULL, 0);
|
||||||
break;
|
break;
|
||||||
case 'F':
|
case 'F':
|
||||||
opts.hardfix = true;
|
opts.hardfix = true;
|
||||||
@@ -121,19 +121,19 @@ int main(int argc, char *argv[])
|
|||||||
opts.unique = true;
|
opts.unique = true;
|
||||||
break;
|
break;
|
||||||
case 'o':
|
case 'o':
|
||||||
opts.outfile = optarg;
|
opts.outfile = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'P':
|
case 'P':
|
||||||
opts.palout = true;
|
opts.palout = true;
|
||||||
break;
|
break;
|
||||||
case 'p':
|
case 'p':
|
||||||
opts.palfile = optarg;
|
opts.palfile = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'T':
|
case 'T':
|
||||||
opts.tilemapout = true;
|
opts.tilemapout = true;
|
||||||
break;
|
break;
|
||||||
case 't':
|
case 't':
|
||||||
opts.tilemapfile = optarg;
|
opts.tilemapfile = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'u':
|
case 'u':
|
||||||
opts.unique = true;
|
opts.unique = true;
|
||||||
@@ -145,15 +145,15 @@ int main(int argc, char *argv[])
|
|||||||
opts.verbose = true;
|
opts.verbose = true;
|
||||||
break;
|
break;
|
||||||
case 'x':
|
case 'x':
|
||||||
opts.trim = strtoul(optarg, NULL, 0);
|
opts.trim = strtoul(musl_optarg, NULL, 0);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
print_usage();
|
print_usage();
|
||||||
/* NOTREACHED */
|
/* NOTREACHED */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
argc -= optind;
|
argc -= musl_optind;
|
||||||
argv += optind;
|
argv += musl_optind;
|
||||||
|
|
||||||
if (argc == 0) {
|
if (argc == 0) {
|
||||||
fputs("FATAL: no input files\n", stderr);
|
fputs("FATAL: no input files\n", stderr);
|
||||||
|
|||||||
@@ -217,23 +217,23 @@ int main(int argc, char *argv[])
|
|||||||
isWRA0Mode = true;
|
isWRA0Mode = true;
|
||||||
break;
|
break;
|
||||||
case 'l':
|
case 'l':
|
||||||
linkerScriptName = optarg;
|
linkerScriptName = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'm':
|
case 'm':
|
||||||
mapFileName = optarg;
|
mapFileName = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'n':
|
case 'n':
|
||||||
symFileName = optarg;
|
symFileName = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'O':
|
case 'O':
|
||||||
overlayFileName = optarg;
|
overlayFileName = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'o':
|
case 'o':
|
||||||
outputFileName = optarg;
|
outputFileName = musl_optarg;
|
||||||
break;
|
break;
|
||||||
case 'p':
|
case 'p':
|
||||||
value = strtoul(optarg, &endptr, 0);
|
value = strtoul(musl_optarg, &endptr, 0);
|
||||||
if (optarg[0] == '\0' || *endptr != '\0') {
|
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||||
error(NULL, 0, "Invalid argument for option 'p'");
|
error(NULL, 0, "Invalid argument for option 'p'");
|
||||||
value = 0xFF;
|
value = 0xFF;
|
||||||
}
|
}
|
||||||
@@ -245,7 +245,7 @@ int main(int argc, char *argv[])
|
|||||||
break;
|
break;
|
||||||
case 's':
|
case 's':
|
||||||
/* FIXME: nobody knows what this does, figure it out */
|
/* FIXME: nobody knows what this does, figure it out */
|
||||||
(void)optarg;
|
(void)musl_optarg;
|
||||||
warning(NULL, 0, "Nobody has any idea what `-s` does");
|
warning(NULL, 0, "Nobody has any idea what `-s` does");
|
||||||
break;
|
break;
|
||||||
case 't':
|
case 't':
|
||||||
@@ -271,7 +271,7 @@ int main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int curArgIndex = optind;
|
int curArgIndex = musl_optind;
|
||||||
|
|
||||||
/* If no input files were specified, the user must have screwed up */
|
/* If no input files were specified, the user must have screwed up */
|
||||||
if (curArgIndex == argc) {
|
if (curArgIndex == argc) {
|
||||||
|
|||||||
@@ -260,11 +260,11 @@ static void readSymbol(FILE *file, struct Symbol *symbol,
|
|||||||
* @param fileName The filename to report in errors
|
* @param fileName The filename to report in errors
|
||||||
* @param i The number of the patch to report in errors
|
* @param i The number of the patch to report in errors
|
||||||
*/
|
*/
|
||||||
static void readPatch(FILE *file, struct Patch *patch, char const *fileName,
|
static void readPatch(FILE *file, struct Patch *patch, char const *fileName, char const *sectName,
|
||||||
char const *sectName, uint32_t i,
|
uint32_t i, struct Section *fileSections[], struct FileStackNode fileNodes[])
|
||||||
struct Section *fileSections[], struct FileStackNode fileNodes[])
|
|
||||||
{
|
{
|
||||||
uint32_t nodeID;
|
uint32_t nodeID;
|
||||||
|
uint8_t type;
|
||||||
|
|
||||||
tryReadlong(nodeID, file,
|
tryReadlong(nodeID, file,
|
||||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s node ID: %s",
|
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s node ID: %s",
|
||||||
@@ -279,14 +279,14 @@ static void readPatch(FILE *file, struct Patch *patch, char const *fileName,
|
|||||||
tryReadlong(patch->pcSectionID, file,
|
tryReadlong(patch->pcSectionID, file,
|
||||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s",
|
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s",
|
||||||
fileName, sectName, i);
|
fileName, sectName, i);
|
||||||
patch->pcSection = patch->pcSectionID == -1 ? NULL
|
patch->pcSection = patch->pcSectionID == -1 ? NULL : fileSections[patch->pcSectionID];
|
||||||
: fileSections[patch->pcSectionID];
|
|
||||||
tryReadlong(patch->pcOffset, file,
|
tryReadlong(patch->pcOffset, file,
|
||||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s",
|
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s",
|
||||||
fileName, sectName, i);
|
fileName, sectName, i);
|
||||||
tryGetc(patch->type, file,
|
tryGetc(type, file,
|
||||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s type: %s",
|
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s type: %s",
|
||||||
fileName, sectName, i);
|
fileName, sectName, i);
|
||||||
|
patch->type = type;
|
||||||
tryReadlong(patch->rpnSize, file,
|
tryReadlong(patch->rpnSize, file,
|
||||||
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s RPN size: %s",
|
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s RPN size: %s",
|
||||||
fileName, sectName, i);
|
fileName, sectName, i);
|
||||||
@@ -349,6 +349,8 @@ static void readSection(FILE *file, struct Section *section, char const *fileNam
|
|||||||
section->bank = tmp;
|
section->bank = tmp;
|
||||||
tryGetc(byte, file, "%s: Cannot read \"%s\"'s alignment: %s",
|
tryGetc(byte, file, "%s: Cannot read \"%s\"'s alignment: %s",
|
||||||
fileName, section->name);
|
fileName, section->name);
|
||||||
|
if (byte > 16)
|
||||||
|
byte = 16;
|
||||||
section->isAlignFixed = byte != 0;
|
section->isAlignFixed = byte != 0;
|
||||||
section->alignMask = (1 << byte) - 1;
|
section->alignMask = (1 << byte) - 1;
|
||||||
tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s alignment offset: %s",
|
tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s alignment offset: %s",
|
||||||
@@ -435,7 +437,7 @@ static void readAssertion(FILE *file, struct Assertion *assert,
|
|||||||
char const *fileName, uint32_t i,
|
char const *fileName, uint32_t i,
|
||||||
struct Section *fileSections[], struct FileStackNode fileNodes[])
|
struct Section *fileSections[], struct FileStackNode fileNodes[])
|
||||||
{
|
{
|
||||||
char assertName[sizeof("Assertion #" EXPAND_AND_STR(UINT32_MAX))];
|
char assertName[sizeof("Assertion #4294967295")]; // UINT32_MAX
|
||||||
|
|
||||||
snprintf(assertName, sizeof(assertName), "Assertion #%" PRIu32, i);
|
snprintf(assertName, sizeof(assertName), "Assertion #%" PRIu32, i);
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,12 @@
|
|||||||
|
|
||||||
#include "extern/err.h"
|
#include "extern/err.h"
|
||||||
|
|
||||||
|
#include "linkdefs.h"
|
||||||
|
|
||||||
|
#include "platform.h" // MIN_NB_ELMS
|
||||||
|
|
||||||
|
#define BANK_SIZE 0x4000
|
||||||
|
|
||||||
FILE *outputFile;
|
FILE *outputFile;
|
||||||
FILE *overlayFile;
|
FILE *overlayFile;
|
||||||
FILE *symFile;
|
FILE *symFile;
|
||||||
@@ -35,6 +41,18 @@ static struct {
|
|||||||
} *banks;
|
} *banks;
|
||||||
} sections[SECTTYPE_INVALID];
|
} sections[SECTTYPE_INVALID];
|
||||||
|
|
||||||
|
/* Defines the order in which types are output to the sym and map files */
|
||||||
|
static enum SectionType typeMap[SECTTYPE_INVALID] = {
|
||||||
|
SECTTYPE_ROM0,
|
||||||
|
SECTTYPE_ROMX,
|
||||||
|
SECTTYPE_VRAM,
|
||||||
|
SECTTYPE_SRAM,
|
||||||
|
SECTTYPE_WRAM0,
|
||||||
|
SECTTYPE_WRAMX,
|
||||||
|
SECTTYPE_OAM,
|
||||||
|
SECTTYPE_HRAM
|
||||||
|
};
|
||||||
|
|
||||||
void out_AddSection(struct Section const *section)
|
void out_AddSection(struct Section const *section)
|
||||||
{
|
{
|
||||||
static uint32_t maxNbBanks[] = {
|
static uint32_t maxNbBanks[] = {
|
||||||
@@ -60,8 +78,7 @@ void out_AddSection(struct Section const *section)
|
|||||||
sections[section->type].banks =
|
sections[section->type].banks =
|
||||||
realloc(sections[section->type].banks,
|
realloc(sections[section->type].banks,
|
||||||
sizeof(*sections[0].banks) * minNbBanks);
|
sizeof(*sections[0].banks) * minNbBanks);
|
||||||
for (uint32_t i = sections[section->type].nbBanks;
|
for (uint32_t i = sections[section->type].nbBanks; i < minNbBanks; i++) {
|
||||||
i < minNbBanks; i++) {
|
|
||||||
sections[section->type].banks[i].sections = NULL;
|
sections[section->type].banks[i].sections = NULL;
|
||||||
sections[section->type].banks[i].zeroLenSections = NULL;
|
sections[section->type].banks[i].zeroLenSections = NULL;
|
||||||
}
|
}
|
||||||
@@ -103,43 +120,63 @@ struct Section const *out_OverlappingSection(struct Section const *section)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs sanity checks on the overlay file.
|
* Performs sanity checks on the overlay file.
|
||||||
|
* @return The number of ROM banks in the overlay file
|
||||||
*/
|
*/
|
||||||
static void checkOverlay(void)
|
static uint32_t checkOverlaySize(void)
|
||||||
{
|
{
|
||||||
if (!overlayFile)
|
if (!overlayFile)
|
||||||
return;
|
return 0;
|
||||||
|
|
||||||
if (fseek(overlayFile, 0, SEEK_END) != 0) {
|
if (fseek(overlayFile, 0, SEEK_END) != 0) {
|
||||||
warnx("Overlay file is not seekable, cannot check if properly formed");
|
warnx("Overlay file is not seekable, cannot check if properly formed");
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
long overlaySize = ftell(overlayFile);
|
long overlaySize = ftell(overlayFile);
|
||||||
|
|
||||||
if (overlaySize % 0x4000)
|
|
||||||
errx(1, "Overlay file must have a size multiple of 0x4000");
|
|
||||||
|
|
||||||
/* Reset back to beginning */
|
/* Reset back to beginning */
|
||||||
fseek(overlayFile, 0, SEEK_SET);
|
fseek(overlayFile, 0, SEEK_SET);
|
||||||
|
|
||||||
uint32_t nbOverlayBanks = overlaySize / 0x4000 - 1;
|
if (overlaySize % BANK_SIZE)
|
||||||
|
errx(1, "Overlay file must have a size multiple of 0x4000");
|
||||||
|
|
||||||
if (nbOverlayBanks < 1)
|
uint32_t nbOverlayBanks = overlaySize / BANK_SIZE;
|
||||||
|
|
||||||
|
if (is32kMode && nbOverlayBanks != 2)
|
||||||
|
errx(1, "Overlay must be exactly 0x8000 bytes large");
|
||||||
|
|
||||||
|
if (nbOverlayBanks < 2)
|
||||||
errx(1, "Overlay must be at least 0x8000 bytes large");
|
errx(1, "Overlay must be at least 0x8000 bytes large");
|
||||||
|
|
||||||
if (nbOverlayBanks > sections[SECTTYPE_ROMX].nbBanks) {
|
return nbOverlayBanks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand sections[SECTTYPE_ROMX].banks to cover all the overlay banks.
|
||||||
|
* This ensures that writeROM will output each bank, even if some are not
|
||||||
|
* covered by any sections.
|
||||||
|
* @param nbOverlayBanks The number of banks in the overlay file
|
||||||
|
*/
|
||||||
|
static void coverOverlayBanks(uint32_t nbOverlayBanks)
|
||||||
|
{
|
||||||
|
/* 2 if is32kMode, 1 otherwise */
|
||||||
|
uint32_t nbRom0Banks = maxsize[SECTTYPE_ROM0] / BANK_SIZE;
|
||||||
|
/* Discount ROM0 banks to avoid outputting too much */
|
||||||
|
uint32_t nbUncoveredBanks = nbOverlayBanks - nbRom0Banks > sections[SECTTYPE_ROMX].nbBanks
|
||||||
|
? nbOverlayBanks - nbRom0Banks
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
if (nbUncoveredBanks > sections[SECTTYPE_ROMX].nbBanks) {
|
||||||
sections[SECTTYPE_ROMX].banks =
|
sections[SECTTYPE_ROMX].banks =
|
||||||
realloc(sections[SECTTYPE_ROMX].banks,
|
realloc(sections[SECTTYPE_ROMX].banks,
|
||||||
sizeof(*sections[SECTTYPE_ROMX].banks) *
|
sizeof(*sections[SECTTYPE_ROMX].banks) * nbUncoveredBanks);
|
||||||
nbOverlayBanks);
|
|
||||||
if (!sections[SECTTYPE_ROMX].banks)
|
if (!sections[SECTTYPE_ROMX].banks)
|
||||||
err(1, "Failed to realloc banks for overlay");
|
err(1, "Failed to realloc banks for overlay");
|
||||||
for (uint32_t i = sections[SECTTYPE_ROMX].nbBanks;
|
for (uint32_t i = sections[SECTTYPE_ROMX].nbBanks; i < nbUncoveredBanks; i++) {
|
||||||
i < nbOverlayBanks; i++) {
|
|
||||||
sections[SECTTYPE_ROMX].banks[i].sections = NULL;
|
sections[SECTTYPE_ROMX].banks[i].sections = NULL;
|
||||||
sections[SECTTYPE_ROMX].banks[i].zeroLenSections = NULL;
|
sections[SECTTYPE_ROMX].banks[i].zeroLenSections = NULL;
|
||||||
}
|
}
|
||||||
sections[SECTTYPE_ROMX].nbBanks = nbOverlayBanks;
|
sections[SECTTYPE_ROMX].nbBanks = nbUncoveredBanks;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,16 +232,19 @@ static void writeROM(void)
|
|||||||
outputFile = openFile(outputFileName, "wb");
|
outputFile = openFile(outputFileName, "wb");
|
||||||
overlayFile = openFile(overlayFileName, "rb");
|
overlayFile = openFile(overlayFileName, "rb");
|
||||||
|
|
||||||
checkOverlay();
|
uint32_t nbOverlayBanks = checkOverlaySize();
|
||||||
|
|
||||||
|
if (nbOverlayBanks > 0)
|
||||||
|
coverOverlayBanks(nbOverlayBanks);
|
||||||
|
|
||||||
if (outputFile) {
|
if (outputFile) {
|
||||||
if (sections[SECTTYPE_ROM0].nbBanks > 0)
|
if (sections[SECTTYPE_ROM0].nbBanks > 0)
|
||||||
writeBank(sections[SECTTYPE_ROM0].banks[0].sections,
|
writeBank(sections[SECTTYPE_ROM0].banks[0].sections,
|
||||||
0x0000, 0x4000);
|
startaddr[SECTTYPE_ROM0], maxsize[SECTTYPE_ROM0]);
|
||||||
|
|
||||||
for (uint32_t i = 0 ; i < sections[SECTTYPE_ROMX].nbBanks; i++)
|
for (uint32_t i = 0 ; i < sections[SECTTYPE_ROMX].nbBanks; i++)
|
||||||
writeBank(sections[SECTTYPE_ROMX].banks[i].sections,
|
writeBank(sections[SECTTYPE_ROMX].banks[i].sections,
|
||||||
0x4000, 0x4000);
|
startaddr[SECTTYPE_ROMX], maxsize[SECTTYPE_ROMX]);
|
||||||
}
|
}
|
||||||
|
|
||||||
closeFile(outputFile);
|
closeFile(outputFile);
|
||||||
@@ -296,12 +336,13 @@ static void writeSymBank(struct SortedSections const *bankSections)
|
|||||||
/**
|
/**
|
||||||
* Write a bank's contents to the map file
|
* Write a bank's contents to the map file
|
||||||
* @param bankSections The bank's sections
|
* @param bankSections The bank's sections
|
||||||
|
* @return The bank's slack space
|
||||||
*/
|
*/
|
||||||
static void writeMapBank(struct SortedSections const *sectList,
|
static uint16_t writeMapBank(struct SortedSections const *sectList,
|
||||||
enum SectionType type, uint32_t bank)
|
enum SectionType type, uint32_t bank)
|
||||||
{
|
{
|
||||||
if (!mapFile)
|
if (!mapFile)
|
||||||
return;
|
return 0;
|
||||||
|
|
||||||
struct SortedSection const *section = sectList->sections;
|
struct SortedSection const *section = sectList->sections;
|
||||||
struct SortedSection const *zeroLenSection = sectList->zeroLenSections;
|
struct SortedSection const *zeroLenSection = sectList->zeroLenSections;
|
||||||
@@ -340,6 +381,34 @@ static void writeMapBank(struct SortedSections const *sectList,
|
|||||||
else
|
else
|
||||||
fprintf(mapFile, " SLACK: $%04" PRIx16 " byte%s\n\n", slack,
|
fprintf(mapFile, " SLACK: $%04" PRIx16 " byte%s\n\n", slack,
|
||||||
slack == 1 ? "" : "s");
|
slack == 1 ? "" : "s");
|
||||||
|
|
||||||
|
return slack;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the total slack space by section type to the map file
|
||||||
|
* @param slackMap The total slack space by section type
|
||||||
|
*/
|
||||||
|
static void writeMapSlack(uint32_t slackMap[MIN_NB_ELMS(SECTTYPE_INVALID)])
|
||||||
|
{
|
||||||
|
if (!mapFile)
|
||||||
|
return;
|
||||||
|
|
||||||
|
fputs("FREE:\n", mapFile);
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < SECTTYPE_INVALID; i++) {
|
||||||
|
enum SectionType type = typeMap[i];
|
||||||
|
|
||||||
|
// Do not output slack space for VRAM or OAM
|
||||||
|
if (type == SECTTYPE_VRAM || type == SECTTYPE_OAM)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (sections[type].nbBanks > 0) {
|
||||||
|
fprintf(mapFile, " %s: $%04" PRIx32 " byte%s in %" PRIu32 " bank%s\n",
|
||||||
|
typeNames[type], slackMap[type], slackMap[type] == 1 ? "" : "s",
|
||||||
|
sections[type].nbBanks, sections[type].nbBanks == 1 ? "" : "s");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -350,16 +419,7 @@ static void writeSymAndMap(void)
|
|||||||
if (!symFileName && !mapFileName)
|
if (!symFileName && !mapFileName)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
enum SectionType typeMap[SECTTYPE_INVALID] = {
|
uint32_t slackMap[SECTTYPE_INVALID] = {0};
|
||||||
SECTTYPE_ROM0,
|
|
||||||
SECTTYPE_ROMX,
|
|
||||||
SECTTYPE_VRAM,
|
|
||||||
SECTTYPE_SRAM,
|
|
||||||
SECTTYPE_WRAM0,
|
|
||||||
SECTTYPE_WRAMX,
|
|
||||||
SECTTYPE_OAM,
|
|
||||||
SECTTYPE_HRAM
|
|
||||||
};
|
|
||||||
|
|
||||||
symFile = openFile(symFileName, "w");
|
symFile = openFile(symFileName, "w");
|
||||||
mapFile = openFile(mapFileName, "w");
|
mapFile = openFile(mapFileName, "w");
|
||||||
@@ -370,16 +430,16 @@ static void writeSymAndMap(void)
|
|||||||
for (uint8_t i = 0; i < SECTTYPE_INVALID; i++) {
|
for (uint8_t i = 0; i < SECTTYPE_INVALID; i++) {
|
||||||
enum SectionType type = typeMap[i];
|
enum SectionType type = typeMap[i];
|
||||||
|
|
||||||
if (sections[type].nbBanks > 0) {
|
for (uint32_t bank = 0; bank < sections[type].nbBanks; bank++) {
|
||||||
for (uint32_t bank = 0; bank < sections[type].nbBanks;
|
struct SortedSections const *sect = §ions[type].banks[bank];
|
||||||
bank++) {
|
|
||||||
writeSymBank(§ions[type].banks[bank]);
|
writeSymBank(sect);
|
||||||
writeMapBank(§ions[type].banks[bank],
|
slackMap[type] += writeMapBank(sect, type, bank);
|
||||||
type, bank);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
writeMapSlack(slackMap);
|
||||||
|
|
||||||
closeFile(symFile);
|
closeFile(symFile);
|
||||||
closeFile(mapFile);
|
closeFile(mapFile);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,50 +18,10 @@
|
|||||||
#include "link/symbol.h"
|
#include "link/symbol.h"
|
||||||
|
|
||||||
#include "linkdefs.h"
|
#include "linkdefs.h"
|
||||||
|
#include "opmath.h"
|
||||||
|
|
||||||
#include "extern/err.h"
|
#include "extern/err.h"
|
||||||
|
|
||||||
static int32_t asl(int32_t value, int32_t shiftamt); // Forward decl for below
|
|
||||||
static int32_t asr(int32_t value, int32_t shiftamt)
|
|
||||||
{
|
|
||||||
uint32_t uvalue = value;
|
|
||||||
|
|
||||||
// Get the easy cases out of the way
|
|
||||||
if (shiftamt == 0)
|
|
||||||
return value;
|
|
||||||
if (value == 0 || shiftamt <= -32)
|
|
||||||
return 0;
|
|
||||||
if (shiftamt > 31)
|
|
||||||
return (value < 0) ? -1 : 0;
|
|
||||||
if (shiftamt < 0)
|
|
||||||
return asl(value, -shiftamt);
|
|
||||||
if (value > 0)
|
|
||||||
return uvalue >> shiftamt;
|
|
||||||
|
|
||||||
{
|
|
||||||
// Calculate an OR mask for sign extension
|
|
||||||
// 1->0x80000000, 2->0xC0000000, ..., 31->0xFFFFFFFE
|
|
||||||
uint32_t shiftamt_high_bits = -((uint32_t)1 << (32 - shiftamt));
|
|
||||||
|
|
||||||
return (uvalue >> shiftamt) | shiftamt_high_bits;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t asl(int32_t value, int32_t shiftamt)
|
|
||||||
{
|
|
||||||
// Repeat the easy cases here to avoid INT_MIN funny business
|
|
||||||
if (shiftamt == 0)
|
|
||||||
return value;
|
|
||||||
if (value == 0 || shiftamt >= 32)
|
|
||||||
return 0;
|
|
||||||
if (shiftamt < -31)
|
|
||||||
return (value < 0) ? -1 : 0;
|
|
||||||
if (shiftamt < 0)
|
|
||||||
return asr(value, -shiftamt);
|
|
||||||
|
|
||||||
return (uint32_t)value << shiftamt;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This is an "empty"-type stack. Apart from the actual values, we also remember
|
* This is an "empty"-type stack. Apart from the actual values, we also remember
|
||||||
* whether the value is a placeholder inserted for error recovery. This allows
|
* whether the value is a placeholder inserted for error recovery. This allows
|
||||||
@@ -219,7 +179,7 @@ static int32_t computeRPNExpr(struct Patch const *patch,
|
|||||||
popRPN();
|
popRPN();
|
||||||
value = INT32_MAX;
|
value = INT32_MAX;
|
||||||
} else {
|
} else {
|
||||||
value = popRPN() / value;
|
value = op_divide(popRPN(), value);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case RPN_MOD:
|
case RPN_MOD:
|
||||||
@@ -231,12 +191,24 @@ static int32_t computeRPNExpr(struct Patch const *patch,
|
|||||||
popRPN();
|
popRPN();
|
||||||
value = 0;
|
value = 0;
|
||||||
} else {
|
} else {
|
||||||
value = popRPN() % value;
|
value = op_modulo(popRPN(), value);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case RPN_UNSUB:
|
case RPN_UNSUB:
|
||||||
value = -popRPN();
|
value = -popRPN();
|
||||||
break;
|
break;
|
||||||
|
case RPN_EXP:
|
||||||
|
value = popRPN();
|
||||||
|
if (value < 0) {
|
||||||
|
if (!isError)
|
||||||
|
error(patch->src, patch->lineNo, "Exponent by negative");
|
||||||
|
isError = true;
|
||||||
|
popRPN();
|
||||||
|
value = 0;
|
||||||
|
} else {
|
||||||
|
value = op_exponent(popRPN(), value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case RPN_OR:
|
case RPN_OR:
|
||||||
value = popRPN() | popRPN();
|
value = popRPN() | popRPN();
|
||||||
@@ -288,11 +260,11 @@ static int32_t computeRPNExpr(struct Patch const *patch,
|
|||||||
|
|
||||||
case RPN_SHL:
|
case RPN_SHL:
|
||||||
value = popRPN();
|
value = popRPN();
|
||||||
value = asl(popRPN(), value);
|
value = op_shift_left(popRPN(), value);
|
||||||
break;
|
break;
|
||||||
case RPN_SHR:
|
case RPN_SHR:
|
||||||
value = popRPN();
|
value = popRPN();
|
||||||
value = asr(popRPN(), value);
|
value = op_shift_right(popRPN(), value);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RPN_BANK_SYM:
|
case RPN_BANK_SYM:
|
||||||
@@ -490,9 +462,10 @@ static void applyFilePatches(struct Section *section, struct Section *dataSectio
|
|||||||
|
|
||||||
/* `jr` is quite unlike the others... */
|
/* `jr` is quite unlike the others... */
|
||||||
if (patch->type == PATCHTYPE_JR) {
|
if (patch->type == PATCHTYPE_JR) {
|
||||||
/* Target is relative to the byte *after* the operand */
|
// Offset is relative to the byte *after* the operand
|
||||||
|
// PC as operand to `jr` is lower than reference PC by 2
|
||||||
uint16_t address = patch->pcSection->org
|
uint16_t address = patch->pcSection->org
|
||||||
+ patch->pcOffset + 1;
|
+ patch->pcOffset + 2;
|
||||||
int16_t jumpOffset = value - address;
|
int16_t jumpOffset = value - address;
|
||||||
|
|
||||||
if (!isError && (jumpOffset < -128 || jumpOffset > 127))
|
if (!isError && (jumpOffset < -128 || jumpOffset > 127))
|
||||||
|
|||||||
@@ -39,62 +39,123 @@ void sect_ForEach(void (*callback)(struct Section *, void *), void *arg)
|
|||||||
hash_ForEach(sections, forEach, &callbackArg);
|
hash_ForEach(sections, forEach, &callbackArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void mergeSections(struct Section *target, struct Section *other, enum SectionModifier mod)
|
static void checkSectUnionCompat(struct Section *target, struct Section *other)
|
||||||
{
|
{
|
||||||
if (target->type != other->type)
|
|
||||||
errx(1, "Section \"%s\" is defined with conflicting types %s and %s",
|
|
||||||
other->name,
|
|
||||||
typeNames[target->type], typeNames[other->type]);
|
|
||||||
if (other->isAddressFixed) {
|
if (other->isAddressFixed) {
|
||||||
if (target->isAddressFixed) {
|
if (target->isAddressFixed) {
|
||||||
if (target->org != other->org)
|
if (target->org != other->org)
|
||||||
errx(1, "Section \"%s\" is defined with conflicting addresses $%" PRIx16 " and $%" PRIx16,
|
errx(1, "Section \"%s\" is defined with conflicting addresses $%04"
|
||||||
|
PRIx16 " and $%04" PRIx16,
|
||||||
other->name, target->org, other->org);
|
other->name, target->org, other->org);
|
||||||
} else if (target->isAlignFixed) {
|
} else if (target->isAlignFixed) {
|
||||||
if ((other->org - target->alignOfs) & target->alignMask)
|
if ((other->org - target->alignOfs) & target->alignMask)
|
||||||
errx(1, "Section \"%s\" is defined with conflicting %" PRIu16 "-byte alignment (offset %" PRIu16 ") and address $%" PRIx16,
|
errx(1, "Section \"%s\" is defined with conflicting %" PRIu16
|
||||||
|
"-byte alignment (offset %" PRIu16 ") and address $%04" PRIx16,
|
||||||
other->name, target->alignMask + 1,
|
other->name, target->alignMask + 1,
|
||||||
target->alignOfs, other->org);
|
target->alignOfs, other->org);
|
||||||
}
|
}
|
||||||
target->isAddressFixed = true;
|
target->isAddressFixed = true;
|
||||||
target->org = other->org;
|
target->org = other->org;
|
||||||
|
|
||||||
} else if (other->isAlignFixed) {
|
} else if (other->isAlignFixed) {
|
||||||
if (target->isAddressFixed) {
|
if (target->isAddressFixed) {
|
||||||
if ((target->org - other->alignOfs) & other->alignMask)
|
if ((target->org - other->alignOfs) & other->alignMask)
|
||||||
errx(1, "Section \"%s\" is defined with conflicting address $%" PRIx16 " and %" PRIu16 "-byte alignment (offset %" PRIu16 ")",
|
errx(1, "Section \"%s\" is defined with conflicting address $%04"
|
||||||
|
PRIx16 " and %" PRIu16 "-byte alignment (offset %" PRIu16 ")",
|
||||||
other->name, target->org,
|
other->name, target->org,
|
||||||
other->alignMask + 1, other->alignOfs);
|
other->alignMask + 1, other->alignOfs);
|
||||||
} else if (target->isAlignFixed
|
} else if (target->isAlignFixed
|
||||||
&& (other->alignMask & target->alignOfs)
|
&& (other->alignMask & target->alignOfs)
|
||||||
!= (target->alignMask & other->alignOfs)) {
|
!= (target->alignMask & other->alignOfs)) {
|
||||||
errx(1, "Section \"%s\" is defined with conflicting %" PRIu16 "-byte alignment (offset %" PRIu16 ") and %" PRIu16 "-byte alignment (offset %" PRIu16 ")",
|
errx(1, "Section \"%s\" is defined with conflicting %" PRIu16
|
||||||
other->name, target->alignMask + 1,
|
"-byte alignment (offset %" PRIu16 ") and %" PRIu16
|
||||||
target->alignOfs, other->alignMask + 1,
|
"-byte alignment (offset %" PRIu16 ")",
|
||||||
other->alignOfs);
|
other->name, target->alignMask + 1, target->alignOfs,
|
||||||
} else if (!target->isAlignFixed
|
other->alignMask + 1, other->alignOfs);
|
||||||
|| (other->alignMask > target->alignMask)) {
|
} else if (!target->isAlignFixed || (other->alignMask > target->alignMask)) {
|
||||||
target->isAlignFixed = true;
|
target->isAlignFixed = true;
|
||||||
target->alignMask = other->alignMask;
|
target->alignMask = other->alignMask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void checkFragmentCompat(struct Section *target, struct Section *other)
|
||||||
|
{
|
||||||
|
if (other->isAddressFixed) {
|
||||||
|
uint16_t org = other->org - target->size;
|
||||||
|
|
||||||
|
if (target->isAddressFixed) {
|
||||||
|
if (target->org != org)
|
||||||
|
errx(1, "Section \"%s\" is defined with conflicting addresses $%04"
|
||||||
|
PRIx16 " and $%04" PRIx16,
|
||||||
|
other->name, target->org, other->org);
|
||||||
|
|
||||||
|
} else if (target->isAlignFixed) {
|
||||||
|
if ((org - target->alignOfs) & target->alignMask)
|
||||||
|
errx(1, "Section \"%s\" is defined with conflicting %" PRIu16
|
||||||
|
"-byte alignment (offset %" PRIu16 ") and address $%04" PRIx16,
|
||||||
|
other->name, target->alignMask + 1,
|
||||||
|
target->alignOfs, other->org);
|
||||||
|
}
|
||||||
|
target->isAddressFixed = true;
|
||||||
|
target->org = org;
|
||||||
|
|
||||||
|
} else if (other->isAlignFixed) {
|
||||||
|
int32_t ofs = (other->alignOfs - target->size) % (other->alignMask + 1);
|
||||||
|
|
||||||
|
if (ofs < 0)
|
||||||
|
ofs += other->alignMask + 1;
|
||||||
|
|
||||||
|
if (target->isAddressFixed) {
|
||||||
|
if ((target->org - ofs) & other->alignMask)
|
||||||
|
errx(1, "Section \"%s\" is defined with conflicting address $%04"
|
||||||
|
PRIx16 " and %" PRIu16 "-byte alignment (offset %" PRIu16 ")",
|
||||||
|
other->name, target->org,
|
||||||
|
other->alignMask + 1, other->alignOfs);
|
||||||
|
|
||||||
|
} else if (target->isAlignFixed
|
||||||
|
&& (other->alignMask & target->alignOfs) != (target->alignMask & ofs)) {
|
||||||
|
errx(1, "Section \"%s\" is defined with conflicting %" PRIu16
|
||||||
|
"-byte alignment (offset %" PRIu16 ") and %" PRIu16
|
||||||
|
"-byte alignment (offset %" PRIu16 ")",
|
||||||
|
other->name, target->alignMask + 1, target->alignOfs,
|
||||||
|
other->alignMask + 1, other->alignOfs);
|
||||||
|
|
||||||
|
} else if (!target->isAlignFixed || (other->alignMask > target->alignMask)) {
|
||||||
|
target->isAlignFixed = true;
|
||||||
|
target->alignMask = other->alignMask;
|
||||||
|
target->alignOfs = ofs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mergeSections(struct Section *target, struct Section *other, enum SectionModifier mod)
|
||||||
|
{
|
||||||
|
// Common checks
|
||||||
|
|
||||||
|
if (target->type != other->type)
|
||||||
|
errx(1, "Section \"%s\" is defined with conflicting types %s and %s",
|
||||||
|
other->name, typeNames[target->type], typeNames[other->type]);
|
||||||
|
|
||||||
if (other->isBankFixed) {
|
if (other->isBankFixed) {
|
||||||
if (!target->isBankFixed) {
|
if (!target->isBankFixed) {
|
||||||
target->isBankFixed = true;
|
target->isBankFixed = true;
|
||||||
target->bank = other->bank;
|
target->bank = other->bank;
|
||||||
} else if (target->bank != other->bank) {
|
} else if (target->bank != other->bank) {
|
||||||
errx(1, "Section \"%s\" is defined with conflicting banks %" PRIu32 " and %" PRIu32,
|
errx(1, "Section \"%s\" is defined with conflicting banks %" PRIu32 " and %"
|
||||||
other->name, target->bank, other->bank);
|
PRIu32, other->name, target->bank, other->bank);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (mod) {
|
switch (mod) {
|
||||||
case SECTION_UNION:
|
case SECTION_UNION:
|
||||||
|
checkSectUnionCompat(target, other);
|
||||||
if (other->size > target->size)
|
if (other->size > target->size)
|
||||||
target->size = other->size;
|
target->size = other->size;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SECTION_FRAGMENT:
|
case SECTION_FRAGMENT:
|
||||||
|
checkFragmentCompat(target, other);
|
||||||
target->size += other->size;
|
target->size += other->size;
|
||||||
other->offset = target->size - other->size;
|
other->offset = target->size - other->size;
|
||||||
if (sect_HasData(target->type)) {
|
if (sect_HasData(target->type)) {
|
||||||
@@ -167,20 +228,20 @@ static void doSanityChecks(struct Section *section, void *ptr)
|
|||||||
fail("Section \"%s\" has an invalid type.", section->name);
|
fail("Section \"%s\" has an invalid type.", section->name);
|
||||||
if (is32kMode && section->type == SECTTYPE_ROMX) {
|
if (is32kMode && section->type == SECTTYPE_ROMX) {
|
||||||
if (section->isBankFixed && section->bank != 1)
|
if (section->isBankFixed && section->bank != 1)
|
||||||
fail("%s: ROMX sections must be in bank 1 with option -t.",
|
fail("%s: ROMX sections must be in bank 1 (if any) with option -t",
|
||||||
section->name);
|
section->name);
|
||||||
else
|
else
|
||||||
section->type = SECTTYPE_ROM0;
|
section->type = SECTTYPE_ROM0;
|
||||||
}
|
}
|
||||||
if (isWRA0Mode && section->type == SECTTYPE_WRAMX) {
|
if (isWRA0Mode && section->type == SECTTYPE_WRAMX) {
|
||||||
if (section->isBankFixed && section->bank != 1)
|
if (section->isBankFixed && section->bank != 1)
|
||||||
fail("%s: WRAMX sections must be in bank 1 with options -w or -d.",
|
fail("%s: WRAMX sections must be in bank 1 with options -w or -d",
|
||||||
section->name);
|
section->name);
|
||||||
else
|
else
|
||||||
section->type = SECTTYPE_WRAMX;
|
section->type = SECTTYPE_WRAMX;
|
||||||
}
|
}
|
||||||
if (isDmgMode && section->type == SECTTYPE_VRAM && section->bank == 1)
|
if (isDmgMode && section->type == SECTTYPE_VRAM && section->bank == 1)
|
||||||
fail("%s: VRAM bank 1 can't be used with option -d.",
|
fail("%s: VRAM bank 1 can't be used with option -d",
|
||||||
section->name);
|
section->name);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -191,17 +252,13 @@ static void doSanityChecks(struct Section *section, void *ptr)
|
|||||||
section->isAlignFixed = false;
|
section->isAlignFixed = false;
|
||||||
|
|
||||||
/* Too large an alignment may not be satisfiable */
|
/* Too large an alignment may not be satisfiable */
|
||||||
if (section->isAlignFixed
|
if (section->isAlignFixed && (section->alignMask & startaddr[section->type]))
|
||||||
&& (section->alignMask & startaddr[section->type]))
|
fail("%s: %s sections cannot be aligned to $%04" PRIx16 " bytes",
|
||||||
fail("%s: %s sections cannot be aligned to $%" PRIx16 " bytes",
|
section->name, typeNames[section->type], section->alignMask + 1);
|
||||||
section->name, typeNames[section->type],
|
|
||||||
section->alignMask + 1);
|
|
||||||
|
|
||||||
uint32_t minbank = bankranges[section->type][0],
|
uint32_t minbank = bankranges[section->type][0], maxbank = bankranges[section->type][1];
|
||||||
maxbank = bankranges[section->type][1];
|
|
||||||
|
|
||||||
if (section->isBankFixed && section->bank < minbank
|
if (section->isBankFixed && section->bank < minbank && section->bank > maxbank)
|
||||||
&& section->bank > maxbank)
|
|
||||||
fail(minbank == maxbank
|
fail(minbank == maxbank
|
||||||
? "Cannot place section \"%s\" in bank %" PRIu32 ", it must be %" PRIu32
|
? "Cannot place section \"%s\" in bank %" PRIu32 ", it must be %" PRIu32
|
||||||
: "Cannot place section \"%s\" in bank %" PRIu32 ", it must be between %" PRIu32 " and %" PRIu32,
|
: "Cannot place section \"%s\" in bank %" PRIu32 ", it must be between %" PRIu32 " and %" PRIu32,
|
||||||
@@ -219,35 +276,26 @@ static void doSanityChecks(struct Section *section, void *ptr)
|
|||||||
section->isBankFixed = true;
|
section->isBankFixed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (section->isAlignFixed) {
|
|
||||||
enum SectionType type = section->type;
|
|
||||||
|
|
||||||
/* It doesn't make sense to have both org and alignment set */
|
|
||||||
if (section->isAddressFixed) {
|
if (section->isAddressFixed) {
|
||||||
if (section->org & section->alignMask)
|
/* It doesn't make sense to have both org and alignment set */
|
||||||
|
if (section->isAlignFixed) {
|
||||||
|
if ((section->org & section->alignMask) != section->alignOfs)
|
||||||
fail("Section \"%s\"'s fixed address doesn't match its alignment",
|
fail("Section \"%s\"'s fixed address doesn't match its alignment",
|
||||||
section->name);
|
section->name);
|
||||||
section->isAlignFixed = false;
|
section->isAlignFixed = false;
|
||||||
} else if ((endaddr(type) & section->alignMask)
|
|
||||||
== startaddr[type]) {
|
|
||||||
section->org = startaddr[type];
|
|
||||||
section->isAlignFixed = false;
|
|
||||||
section->isAddressFixed = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (section->isAddressFixed) {
|
|
||||||
/* Ensure the target address is valid */
|
/* Ensure the target address is valid */
|
||||||
if (section->org < startaddr[section->type]
|
if (section->org < startaddr[section->type]
|
||||||
|| section->org > endaddr(section->type))
|
|| section->org > endaddr(section->type))
|
||||||
fail("Section \"%s\"'s fixed address %#" PRIx16 " is outside of range [%#" PRIx16 "; %#" PRIx16 "]",
|
fail("Section \"%s\"'s fixed address %#" PRIx16 " is outside of range [%#"
|
||||||
section->name, section->org,
|
PRIx16 "; %#" PRIx16 "]", section->name, section->org,
|
||||||
startaddr[section->type], endaddr(section->type));
|
startaddr[section->type], endaddr(section->type));
|
||||||
|
|
||||||
if (section->org + section->size > endaddr(section->type) + 1)
|
if (section->org + section->size > endaddr(section->type) + 1)
|
||||||
fail("Section \"%s\"'s end address %#" PRIx16 " is greater than last address %#" PRIx16,
|
fail("Section \"%s\"'s end address %#" PRIx16
|
||||||
section->name, section->org + section->size,
|
" is greater than last address %#" PRIx16, section->name,
|
||||||
endaddr(section->type) + 1);
|
section->org + section->size, endaddr(section->type) + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef fail
|
#undef fail
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ uint16_t startaddr[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
uint16_t maxsize[] = {
|
uint16_t maxsize[] = {
|
||||||
[SECTTYPE_ROM0] = 0x8000,
|
[SECTTYPE_ROM0] = 0x8000, // patched to 0x4000 if !is32kMode
|
||||||
[SECTTYPE_ROMX] = 0x4000,
|
[SECTTYPE_ROMX] = 0x4000,
|
||||||
[SECTTYPE_VRAM] = 0x2000,
|
[SECTTYPE_VRAM] = 0x2000,
|
||||||
[SECTTYPE_SRAM] = 0x2000,
|
[SECTTYPE_SRAM] = 0x2000,
|
||||||
[SECTTYPE_WRAM0] = 0x2000,
|
[SECTTYPE_WRAM0] = 0x2000, // patched to 0x1000 if !isWRA0Mode
|
||||||
[SECTTYPE_WRAMX] = 0x1000,
|
[SECTTYPE_WRAMX] = 0x1000,
|
||||||
[SECTTYPE_OAM] = 0x00A0,
|
[SECTTYPE_OAM] = 0x00A0,
|
||||||
[SECTTYPE_HRAM] = 0x007F
|
[SECTTYPE_HRAM] = 0x007F
|
||||||
|
|||||||
88
src/opmath.c
Normal file
88
src/opmath.c
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of RGBDS.
|
||||||
|
*
|
||||||
|
* Copyright (c) 1997-2021, RGBDS contributors.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Mathematical operators that don't reuse C's behavior
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "opmath.h"
|
||||||
|
|
||||||
|
int32_t op_divide(int32_t dividend, int32_t divisor)
|
||||||
|
{
|
||||||
|
// Adjust division to floor toward negative infinity,
|
||||||
|
// not truncate toward zero
|
||||||
|
return dividend / divisor - ((dividend % divisor < 0) != (divisor < 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t op_modulo(int32_t dividend, int32_t divisor)
|
||||||
|
{
|
||||||
|
int32_t remainder = dividend % divisor;
|
||||||
|
|
||||||
|
// Adjust modulo to have the sign of the divisor,
|
||||||
|
// not the sign of the dividend
|
||||||
|
return remainder + divisor * ((remainder < 0) != (divisor < 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t op_exponent(int32_t base, uint32_t power)
|
||||||
|
{
|
||||||
|
int32_t result = 1;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
if (power % 2)
|
||||||
|
result *= base;
|
||||||
|
power /= 2;
|
||||||
|
if (!power)
|
||||||
|
break;
|
||||||
|
base *= base;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t op_shift_left(int32_t value, int32_t amount)
|
||||||
|
{
|
||||||
|
// Get the easy cases out of the way
|
||||||
|
if (amount == 0)
|
||||||
|
return value;
|
||||||
|
if (value == 0 || amount >= 32)
|
||||||
|
return 0;
|
||||||
|
if (amount < -31)
|
||||||
|
return (value < 0) ? -1 : 0;
|
||||||
|
if (amount < 0)
|
||||||
|
return op_shift_right(value, -amount);
|
||||||
|
|
||||||
|
// Use unsigned to force a bitwise shift
|
||||||
|
// Casting back is OK because the types implement two's complement behavior
|
||||||
|
return (uint32_t)value << amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t op_shift_right(int32_t value, int32_t amount)
|
||||||
|
{
|
||||||
|
// Repeat the easy cases here to avoid INT_MIN funny business
|
||||||
|
if (amount == 0)
|
||||||
|
return value;
|
||||||
|
if (value == 0 || amount <= -32)
|
||||||
|
return 0;
|
||||||
|
if (amount > 31)
|
||||||
|
return (value < 0) ? -1 : 0;
|
||||||
|
if (amount < 0)
|
||||||
|
return op_shift_left(value, -amount);
|
||||||
|
|
||||||
|
if (value > 0)
|
||||||
|
return (uint32_t)value >> amount;
|
||||||
|
|
||||||
|
// Calculate an OR mask for sign extension
|
||||||
|
// 1->0x80000000, 2->0xC0000000, ..., 31->0xFFFFFFFE
|
||||||
|
uint32_t amount_high_bits = -(UINT32_C(1) << (32 - amount));
|
||||||
|
|
||||||
|
// The C standard leaves shifting right negative values
|
||||||
|
// undefined, so use a left shift manually sign-extended
|
||||||
|
return ((uint32_t)value >> amount) | amount_high_bits;
|
||||||
|
}
|
||||||
@@ -225,6 +225,7 @@ with some bytes being special prefixes for integers and symbols.
|
|||||||
.It Li $03 Ta Li / operator
|
.It Li $03 Ta Li / operator
|
||||||
.It Li $04 Ta Li % operator
|
.It Li $04 Ta Li % operator
|
||||||
.It Li $05 Ta Li unary -
|
.It Li $05 Ta Li unary -
|
||||||
|
.It Li $06 Ta Li ** operator
|
||||||
.It Li $10 Ta Li | operator
|
.It Li $10 Ta Li | operator
|
||||||
.It Li $11 Ta Li & operator
|
.It Li $11 Ta Li & operator
|
||||||
.It Li $12 Ta Li ^ operator
|
.It Li $12 Ta Li ^ operator
|
||||||
|
|||||||
@@ -9,17 +9,24 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "helpers.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
const char *get_package_version_string(void)
|
const char *get_package_version_string(void)
|
||||||
{
|
{
|
||||||
static char s[50];
|
// The following conditional should be simplified by the compiler.
|
||||||
|
|
||||||
/* The following conditional should be simplified by the compiler. */
|
|
||||||
if (strlen(BUILD_VERSION_STRING) == 0) {
|
if (strlen(BUILD_VERSION_STRING) == 0) {
|
||||||
snprintf(s, sizeof(s), "v%d.%d.%d", PACKAGE_VERSION_MAJOR,
|
// Fallback if version string can't be obtained from Git
|
||||||
PACKAGE_VERSION_MINOR, PACKAGE_VERSION_PATCH);
|
#ifndef PACKAGE_VERSION_RC
|
||||||
return s;
|
return "v" EXPAND_AND_STR(PACKAGE_VERSION_MAJOR)
|
||||||
|
"." EXPAND_AND_STR(PACKAGE_VERSION_MINOR)
|
||||||
|
"." EXPAND_AND_STR(PACKAGE_VERSION_PATCH);
|
||||||
|
#else
|
||||||
|
return "v" EXPAND_AND_STR(PACKAGE_VERSION_MAJOR)
|
||||||
|
"." EXPAND_AND_STR(PACKAGE_VERSION_MINOR)
|
||||||
|
"." EXPAND_AND_STR(PACKAGE_VERSION_PATCH)
|
||||||
|
"-rc" EXPAND_AND_STR(PACKAGE_VERSION_RC);
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
return BUILD_VERSION_STRING;
|
return BUILD_VERSION_STRING;
|
||||||
}
|
}
|
||||||
|
|||||||
4
test/asm/.gitignore
vendored
4
test/asm/.gitignore
vendored
@@ -1 +1,3 @@
|
|||||||
quote\"file.*
|
/quote\"file.*
|
||||||
|
/version.asm
|
||||||
|
/version.out
|
||||||
|
|||||||
8
test/asm/align-16.asm
Normal file
8
test/asm/align-16.asm
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
SECTION "Byte", ROM0
|
||||||
|
|
||||||
|
db 2
|
||||||
|
|
||||||
|
SECTION "ROM0", ROM0, ALIGN[16]
|
||||||
|
|
||||||
|
db 1
|
||||||
1
test/asm/align-16.out.bin
Normal file
1
test/asm/align-16.out.bin
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
2
test/asm/align-large-ofs.asm
Normal file
2
test/asm/align-large-ofs.asm
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
SECTION "Tesst", ROM0, ALIGN[1,2]
|
||||||
3
test/asm/align-large-ofs.err
Normal file
3
test/asm/align-large-ofs.err
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
ERROR: align-large-ofs.asm(2):
|
||||||
|
Alignment offset (2) must be smaller than alignment size (2)
|
||||||
|
error: Assembly aborted (1 errors)!
|
||||||
0
test/asm/align-large-ofs.out
Normal file
0
test/asm/align-large-ofs.out
Normal file
2
test/asm/align-large.asm
Normal file
2
test/asm/align-large.asm
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
SECTION "You lost the game", ROM0[17]
|
||||||
0
test/asm/align-large.err
Normal file
0
test/asm/align-large.err
Normal file
0
test/asm/align-large.out
Normal file
0
test/asm/align-large.out
Normal file
18
test/asm/anon-label-bad.asm
Normal file
18
test/asm/anon-label-bad.asm
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
: ; Outside of section
|
||||||
|
|
||||||
|
SECTION "Anonymous label errors test", ROM0
|
||||||
|
|
||||||
|
db :-- ; Reference goes too far back
|
||||||
|
|
||||||
|
; Uncomment this if you're a badass with a *lot* of RAM
|
||||||
|
; REPT 2147483647
|
||||||
|
; :
|
||||||
|
; ENDR
|
||||||
|
; REPT 2147483647
|
||||||
|
; :
|
||||||
|
; ENDR
|
||||||
|
; db :+ ; OK
|
||||||
|
; db :++ ; Reference goes too far
|
||||||
|
|
||||||
|
:: ; Syntax error, can't export this
|
||||||
7
test/asm/anon-label-bad.err
Normal file
7
test/asm/anon-label-bad.err
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
ERROR: anon-label-bad.asm(2):
|
||||||
|
Label "!0" created outside of a SECTION
|
||||||
|
ERROR: anon-label-bad.asm(6):
|
||||||
|
Reference to anonymous label 2 before, when only 1 has been created so far
|
||||||
|
ERROR: anon-label-bad.asm(18):
|
||||||
|
syntax error, unexpected :
|
||||||
|
error: Assembly aborted (3 errors)!
|
||||||
0
test/asm/anon-label-bad.out
Normal file
0
test/asm/anon-label-bad.out
Normal file
7
test/asm/anon-label-bad.simple.err
Normal file
7
test/asm/anon-label-bad.simple.err
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
ERROR: anon-label-bad.asm(2):
|
||||||
|
Label "!0" created outside of a SECTION
|
||||||
|
ERROR: anon-label-bad.asm(6):
|
||||||
|
Reference to anonymous label 2 before, when only 1 has been created so far
|
||||||
|
ERROR: anon-label-bad.asm(18):
|
||||||
|
syntax error
|
||||||
|
error: Assembly aborted (3 errors)!
|
||||||
12
test/asm/anon-label.asm
Normal file
12
test/asm/anon-label.asm
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
SECTION "Anonymous label test", ROM0[0]
|
||||||
|
|
||||||
|
ld hl, :++
|
||||||
|
: ld a, [hli]
|
||||||
|
ldh [c], a
|
||||||
|
dec c
|
||||||
|
jr nz, :-
|
||||||
|
ret
|
||||||
|
|
||||||
|
:
|
||||||
|
dw $7FFF, $1061, $03E0, $58A5
|
||||||
0
test/asm/anon-label.err
Normal file
0
test/asm/anon-label.err
Normal file
0
test/asm/anon-label.out
Normal file
0
test/asm/anon-label.out
Normal file
BIN
test/asm/anon-label.out.bin
Normal file
BIN
test/asm/anon-label.out.bin
Normal file
Binary file not shown.
@@ -1,24 +1,23 @@
|
|||||||
print_all: MACRO
|
print_all: MACRO
|
||||||
REPT _NARG
|
REPT _NARG
|
||||||
PRINTT " \1"
|
PRINT " \1"
|
||||||
SHIFT
|
SHIFT
|
||||||
ENDR
|
ENDR
|
||||||
PRINTT "\n"
|
PRINTLN
|
||||||
ENDM
|
ENDM
|
||||||
|
|
||||||
print_some: MACRO
|
print_some: MACRO
|
||||||
PRINTT "\1"
|
PRINT "\1"
|
||||||
SHIFT 5
|
SHIFT 5
|
||||||
PRINTT "\2\6\9"
|
PRINT "\2\6\9"
|
||||||
SHIFT 17
|
SHIFT 17
|
||||||
SHIFT
|
SHIFT
|
||||||
PRINTT "\3\9"
|
PRINT "\3\9"
|
||||||
ENDM
|
ENDM
|
||||||
|
|
||||||
bad: MACRO
|
bad: MACRO
|
||||||
shift _NARG - 1
|
shift _NARG - 1
|
||||||
PRINTT \1
|
PRINTLN \1
|
||||||
PRINTT "\n"
|
|
||||||
ENDM
|
ENDM
|
||||||
|
|
||||||
bad_rept: MACRO
|
bad_rept: MACRO
|
||||||
@@ -27,8 +26,7 @@ bad_rept: MACRO
|
|||||||
shift
|
shift
|
||||||
ENDR
|
ENDR
|
||||||
ENDR
|
ENDR
|
||||||
PRINTT \1
|
PRINTLN \1
|
||||||
PRINTT "\n"
|
|
||||||
ENDM
|
ENDM
|
||||||
|
|
||||||
print_all This test, probably, passes\,, but who knows, ?
|
print_all This test, probably, passes\,, but who knows, ?
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ def_sect: macro
|
|||||||
SECTION "\1", \2, BANK[\3]
|
SECTION "\1", \2, BANK[\3]
|
||||||
ENDC
|
ENDC
|
||||||
|
|
||||||
PRINTV BANK("\1")
|
PRINTLN BANK("\1")
|
||||||
PRINTT "\n"
|
|
||||||
endm
|
endm
|
||||||
|
|
||||||
def_sect ROM0_ok, ROM0
|
def_sect ROM0_ok, ROM0
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
ERROR: bank.asm(14) -> bank.asm::def_sect(8):
|
ERROR: bank.asm(13) -> bank.asm::def_sect(8):
|
||||||
Expected constant expression: Section "ROMX_bad"'s bank is not known
|
Expected constant expression: Section "ROMX_bad"'s bank is not known
|
||||||
ERROR: bank.asm(16) -> bank.asm::def_sect(8):
|
ERROR: bank.asm(15) -> bank.asm::def_sect(8):
|
||||||
Expected constant expression: Section "VRAM_bad"'s bank is not known
|
Expected constant expression: Section "VRAM_bad"'s bank is not known
|
||||||
ERROR: bank.asm(18) -> bank.asm::def_sect(8):
|
ERROR: bank.asm(17) -> bank.asm::def_sect(8):
|
||||||
Expected constant expression: Section "SRAM_bad"'s bank is not known
|
Expected constant expression: Section "SRAM_bad"'s bank is not known
|
||||||
ERROR: bank.asm(21) -> bank.asm::def_sect(8):
|
ERROR: bank.asm(20) -> bank.asm::def_sect(8):
|
||||||
Expected constant expression: Section "WRAMX_bad"'s bank is not known
|
Expected constant expression: Section "WRAMX_bad"'s bank is not known
|
||||||
error: Assembly aborted (4 errors)!
|
error: Assembly aborted (4 errors)!
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
/* block comments containing /* throw warnings */
|
/* block comments containing /* throw warnings */
|
||||||
PRINTT "reachable\n"
|
PRINTLN "reachable"
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
PRINTT /* block comments must terminate before EOF
|
PRINT /* block comments must terminate before EOF
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
ERROR: block-comment-termination-error.asm(1):
|
ERROR: block-comment-termination-error.asm(1):
|
||||||
Unterminated block comment
|
Unterminated block comment
|
||||||
ERROR: block-comment-termination-error.asm(1):
|
ERROR: block-comment-termination-error.asm(1):
|
||||||
syntax error
|
syntax error, unexpected newline
|
||||||
error: Assembly aborted (2 errors)!
|
error: Assembly aborted (2 errors)!
|
||||||
|
|||||||
5
test/asm/block-comment-termination-error.simple.err
Normal file
5
test/asm/block-comment-termination-error.simple.err
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
ERROR: block-comment-termination-error.asm(1):
|
||||||
|
Unterminated block comment
|
||||||
|
ERROR: block-comment-termination-error.asm(1):
|
||||||
|
syntax error
|
||||||
|
error: Assembly aborted (2 errors)!
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
PRINTT /* block comments are ignored // ** */ "hi\n"
|
PRINTLN /* block comments are ignored // ** */ "hi"
|
||||||
PRINTT "block (/* ... */) comments at ends of line are fine\n" /* hi */
|
PRINTLN "block (/* ... */) comments at ends of line are fine" /* hi */
|
||||||
PRINTT /* block comments
|
PRINTLN /* block comments
|
||||||
can span multiple lines
|
can span multiple lines
|
||||||
*/ "mutliline\n"
|
*/ "mutliline"
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
X = 42
|
X = 42
|
||||||
PRINTT "{X}\n"
|
PRINTLN "{X}"
|
||||||
PRINTT "{x:X}\n"
|
PRINTLN "{x:X}"
|
||||||
PRINTT "{X:X}\n"
|
PRINTLN "{X:X}"
|
||||||
PRINTT "{d:X}\n"
|
PRINTLN "{d:X}"
|
||||||
PRINTT "{b:X}\n"
|
PRINTLN "{b:X}"
|
||||||
|
|
||||||
Y equ 1337
|
Y equ 1337
|
||||||
PRINTT "{b:Y}\n"
|
PRINTLN "{b:Y}"
|
||||||
|
|
||||||
rsreset
|
rsreset
|
||||||
R rb 0
|
R rb 0
|
||||||
PRINTT "{d:R}\n"
|
PRINTLN "{d:R}"
|
||||||
|
|
||||||
S equs "You can't format me!"
|
S equs "You can't format me!"
|
||||||
PRINTT "{X:S}\n"
|
PRINTLN "{X:S}"
|
||||||
|
|
||||||
SECTION "Test", ROM0
|
SECTION "Test", ROM0
|
||||||
Label:
|
Label:
|
||||||
PRINTT "{x:Label}\n"
|
PRINTLN "{x:Label}"
|
||||||
PRINTT "{x:@}\n"
|
PRINTLN "{x:@}"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
ERROR: bracketed-symbols.asm(16):
|
ERROR: bracketed-symbols.asm(16):
|
||||||
Print types are only allowed for numbers
|
Formatting string as type 'X'
|
||||||
ERROR: bracketed-symbols.asm(20):
|
ERROR: bracketed-symbols.asm(20):
|
||||||
"Label" does not have a constant value
|
"Label" does not have a constant value
|
||||||
ERROR: bracketed-symbols.asm(21):
|
ERROR: bracketed-symbols.asm(21):
|
||||||
|
|||||||
23
test/asm/break.asm
Normal file
23
test/asm/break.asm
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
FOR V, 1, 100
|
||||||
|
PRINTLN "- {d:V}"
|
||||||
|
IF V == 5
|
||||||
|
PRINTLN "stop"
|
||||||
|
BREAK
|
||||||
|
ENDC
|
||||||
|
PRINTLN "cont"
|
||||||
|
ENDR
|
||||||
|
WARN "done {d:V}"
|
||||||
|
rept 1
|
||||||
|
break
|
||||||
|
; skips invalid code
|
||||||
|
!@#$%
|
||||||
|
elif: macro
|
||||||
|
invalid
|
||||||
|
endr
|
||||||
|
warn "OK"
|
||||||
|
rept 1
|
||||||
|
if 1
|
||||||
|
break
|
||||||
|
no endc
|
||||||
|
endr
|
||||||
|
println "done"
|
||||||
6
test/asm/break.err
Normal file
6
test/asm/break.err
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
warning: break.asm(9): [-Wuser]
|
||||||
|
done 5
|
||||||
|
warning: break.asm(17): [-Wuser]
|
||||||
|
OK
|
||||||
|
FATAL: break.asm(18) -> break.asm::REPT~1(22):
|
||||||
|
Ended block with 1 unterminated IF construct
|
||||||
10
test/asm/break.out
Normal file
10
test/asm/break.out
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
- 1
|
||||||
|
cont
|
||||||
|
- 2
|
||||||
|
cont
|
||||||
|
- 3
|
||||||
|
cont
|
||||||
|
- 4
|
||||||
|
cont
|
||||||
|
- 5
|
||||||
|
stop
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user