Compare commits

...

20 Commits

Author SHA1 Message Date
Rangi42
8b85875b67 Release v0.9.3 2025-06-30 15:08:04 -04:00
Rangi
7054d81650 Implement grayscale DMG palette specs (#1709) 2025-06-30 14:53:05 -04:00
Rangi
5942117ac3 Avoid generating phony dependencies for files that don't exist (#1708) 2025-06-29 16:42:24 -04:00
Rangi42
e7a3b9d90e Format rgbgfx -vvvvvv string visually 2025-06-21 14:16:48 -04:00
Rangi
b13d623ad4 Encode reversed PNG images as grayscale or indexed when possible (#1703) 2025-06-19 09:48:27 -04:00
Rangi
37bf9fae01 Only define parse.lac for Bison 3.5 or greater (#1702) 2025-06-14 17:01:16 -04:00
Rangi42
612cf3b7dd Fix some formatting 2025-06-12 17:27:08 -04:00
Rangi
089e366ddc Implement CHARVAL function (#1701) 2025-06-12 17:21:12 -04:00
Rangi
fa9e29e4ce Implement ++ operator for string concatenation (#1698) 2025-06-12 22:52:00 +02:00
Antonio Vivace
fa3d83a3d1 ci container build: when pushing a version-tagged build, overwrite 'latest' as well 2025-06-08 18:21:58 +02:00
Rangi
804db4e073 Handle missing newline at EOF for linkerscript INCLUDEd files (#1691) 2025-05-22 10:55:58 +02:00
Rangi
5d998ef483 Restrict custom binary and graphics digits (#1693)
* Restrict custom binary and graphics digits

* Update documentation

* Fix build error
2025-05-22 10:52:51 +02:00
Rangi
126b1e5726 Reuse startsIdentifier and continuesIdentifier functions (#1695) 2025-05-19 15:31:26 -04:00
Rangi
4f2400c15b Hint to {interpolate} names when EQUS expanding does not occur (#1692) 2025-05-18 17:53:34 +02:00
Antonio Vivace
063d284cbf Dockerfile: explain commands 2025-05-16 18:48:08 +02:00
Antonio Vivace
205bf5a11d Dockerfile: install the compiled tools after the compilation (#1690) 2025-05-16 18:48:08 +02:00
Rangi
41c94aa448 Omit the version number from distrbuted release archive filenames (#1685) 2025-05-06 13:28:54 +02:00
Rangi
d413870e6d .sym file sorting accounts for local labels' parents' addresses and names (#1684) 2025-05-05 13:57:25 -04:00
Rangi
e95ac6fb06 Recover from errors even inside REPT/FOR loops (#1683) 2025-05-04 17:51:53 -04:00
Rangi
e1ae92709c Fix STRSLICE with no stop index argument (#1682) 2025-05-04 16:56:25 -04:00
87 changed files with 840 additions and 350 deletions

View File

@@ -38,6 +38,7 @@ jobs:
sed -i "2i LABEL org.opencontainers.image.description=\"RGBDS container image for the release version $TAG_NAME\"" Dockerfile
docker build . --tag ghcr.io/gbdev/rgbds:$TAG_NAME
docker push ghcr.io/gbdev/rgbds:$TAG_NAME
docker push ghcr.io/gbdev/rgbds:latest
- name: Delete untagged container images
if: github.repository_owner == 'gbdev'

View File

@@ -61,12 +61,12 @@ jobs:
cmake --install build --verbose --prefix install_dir --strip
- name: Package 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 }}-win${{ matrix.bits }}.zip"
Compress-Archive -LiteralPath @("install_dir/bin/rgbasm.exe", "install_dir/bin/rgblink.exe", "install_dir/bin/rgbfix.exe", "install_dir/bin/rgbgfx.exe", "install_dir/bin/zlib1.dll", "install_dir/bin/libpng16.dll") "rgbds-win${{ matrix.bits }}.zip"
- name: Upload Windows binaries
uses: actions/upload-artifact@v4
with:
name: win${{ matrix.bits }}
path: rgbds-${{ env.version }}-win${{ matrix.bits }}.zip
path: rgbds-win${{ matrix.bits }}.zip
macos:
runs-on: macos-14
@@ -92,12 +92,12 @@ jobs:
strip rgb{asm,link,fix,gfx}
- name: Package binaries
run: |
zip --junk-paths rgbds-${{ env.version }}-macos.zip rgb{asm,link,fix,gfx} man/* .github/scripts/install.sh
zip --junk-paths rgbds-macos.zip rgb{asm,link,fix,gfx} man/* .github/scripts/install.sh
- name: Upload macOS binaries
uses: actions/upload-artifact@v4
with:
name: macos
path: rgbds-${{ env.version }}-macos.zip
path: rgbds-macos.zip
linux:
runs-on: ubuntu-22.04 # Oldest supported, for best glibc compatibility.
@@ -119,12 +119,12 @@ jobs:
strip rgb{asm,link,fix,gfx}
- name: Package binaries
run: |
tar caf rgbds-${{ env.version }}-linux-x86_64.tar.xz --transform='s#.*/##' rgb{asm,link,fix,gfx} man/* .github/scripts/install.sh
tar caf rgbds-linux-x86_64.tar.xz --transform='s#.*/##' rgb{asm,link,fix,gfx} man/* .github/scripts/install.sh
- name: Upload Linux binaries
uses: actions/upload-artifact@v4
with:
name: linux
path: rgbds-${{ env.version }}-linux-x86_64.tar.xz
path: rgbds-linux-x86_64.tar.xz
release:
runs-on: ubuntu-latest
@@ -155,11 +155,11 @@ jobs:
draft: true # Don't publish the release quite yet...
prerelease: ${{ contains(github.ref, '-rc') }}
files: |
win32/rgbds-${{ env.version }}-win32.zip
win64/rgbds-${{ env.version }}-win64.zip
macos/rgbds-${{ env.version }}-macos.zip
linux/rgbds-${{ env.version }}-linux-x86_64.tar.xz
rgbds-${{ env.version }}.tar.gz
win32/rgbds-win32.zip
win64/rgbds-win64.zip
macos/rgbds-macos.zip
linux/rgbds-linux-x86_64.tar.xz
rgbds-source.tar.gz
fail_on_unmatched_files: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,6 +1,6 @@
FROM debian:12-slim
LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds
ARG version=0.9.2
ARG version=0.9.3
WORKDIR /rgbds
COPY . .
@@ -8,7 +8,14 @@ COPY . .
RUN apt-get update && \
apt-get install sudo make cmake gcc build-essential -y
# Install dependencies and compile RGBDS
RUN ./.github/scripts/install_deps.sh ubuntu-22.04
RUN make -j CXXFLAGS="-O3 -flto -DNDEBUG -static" PKG_CONFIG="pkg-config --static" Q=
RUN tar caf rgbds-${version}-linux-x86_64.tar.xz --transform='s#.*/##' rgbasm rgblink rgbfix rgbgfx man/* .github/scripts/install.sh
# Create an archive with the compiled executables and all the necessary to install it,
# so it can be copied outside of the container and installed/used in another system
RUN tar caf rgbds-linux-x86_64.tar.xz --transform='s#.*/##' rgbasm rgblink rgbfix rgbgfx man/* .github/scripts/install.sh
# Install RGBDS on the container so all the executables will be available in the PATH
RUN cp man/* .
RUN ./.github/scripts/install.sh

View File

@@ -265,4 +265,4 @@ wine-shim:
dist:
$Qgit ls-files | sed s~^~$${PWD##*/}/~ \
| tar -czf rgbds-`git -c safe.directory='*' describe --tags | cut -c 2-`.tar.gz -C .. -T -
| tar -czf rgbds-source.tar.gz -C .. -T -

View File

@@ -3,6 +3,7 @@
#ifndef RGBDS_ASM_CHARMAP_HPP
#define RGBDS_ASM_CHARMAP_HPP
#include <optional>
#include <stdint.h>
#include <string>
#include <string_view>
@@ -22,6 +23,7 @@ void charmap_CheckStack();
void charmap_Add(std::string const &mapping, std::vector<int32_t> &&value);
bool charmap_HasChar(std::string const &mapping);
size_t charmap_CharSize(std::string const &mapping);
std::optional<int32_t> charmap_CharValue(std::string const &mapping, size_t idx);
std::vector<int32_t> charmap_Convert(std::string const &input);
size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output);
std::string charmap_Reverse(std::vector<int32_t> const &value, bool &unique);

View File

@@ -71,7 +71,6 @@ void fstk_RunFor(
int32_t reptLineNo,
ContentSpan const &span
);
void fstk_StopRept();
bool fstk_Break();
void fstk_NewRecursionDepth(size_t newDepth);

View File

@@ -118,17 +118,8 @@ struct LexerState {
extern char binDigits[2];
extern char gfxDigits[4];
static inline void lexer_SetBinDigits(char const digits[2]) {
binDigits[0] = digits[0];
binDigits[1] = digits[1];
}
static inline void lexer_SetGfxDigits(char const digits[4]) {
gfxDigits[0] = digits[0];
gfxDigits[1] = digits[1];
gfxDigits[2] = digits[2];
gfxDigits[3] = digits[3];
}
void lexer_SetBinDigits(char const digits[2]);
void lexer_SetGfxDigits(char const digits[4]);
bool lexer_AtTopLevel();
void lexer_RestartRept(uint32_t lineNo);

View File

@@ -29,8 +29,10 @@ struct Options {
NO_SPEC,
EXPLICIT,
EMBEDDED,
DMG,
} palSpecType = NO_SPEC; // -c
std::vector<std::array<std::optional<Rgba>, 4>> palSpec{};
uint8_t palSpecDmg = 0;
uint8_t bitDepth = 2; // -d
std::string inputTileset{}; // -i
struct {
@@ -65,6 +67,12 @@ struct Options {
mutable bool hasTransparentPixels = false;
uint8_t maxOpaqueColors() const { return nbColorsPerPal - hasTransparentPixels; }
uint8_t dmgColors[4] = {};
uint8_t dmgValue(uint8_t i) const {
assume(i < 4);
return (palSpecDmg >> (2 * i)) & 0b11;
}
};
extern Options options;
@@ -119,27 +127,4 @@ static constexpr auto flipTable = ([]() constexpr {
return table;
})();
// Parsing helpers.
static constexpr uint8_t nibble(char c) {
if (c >= 'a') {
assume(c <= 'f');
return c - 'a' + 10;
} else if (c >= 'A') {
assume(c <= 'F');
return c - 'A' + 10;
} else {
assume(c >= '0' && c <= '9');
return c - '0';
}
}
static constexpr uint8_t toHex(char c1, char c2) {
return nibble(c1) * 16 + nibble(c2);
}
static constexpr uint8_t singleToHex(char c) {
return toHex(c, c);
}
#endif // RGBDS_GFX_MAIN_HPP

View File

@@ -3,7 +3,10 @@
#ifndef RGBDS_GFX_PAL_SPEC_HPP
#define RGBDS_GFX_PAL_SPEC_HPP
void parseInlinePalSpec(char const * const arg);
void parseInlinePalSpec(char const * const rawArg);
void parseExternalPalSpec(char const *arg);
void parseDmgPalSpec(char const * const rawArg);
void parseBackgroundPalSpec(char const *arg);
#endif // RGBDS_GFX_PAL_SPEC_HPP

View File

@@ -3,6 +3,9 @@
#ifndef RGBDS_UTIL_HPP
#define RGBDS_UTIL_HPP
bool startsIdentifier(int c);
bool continuesIdentifier(int c);
char const *printChar(int c);
#endif // RGBDS_UTIL_HPP

View File

@@ -5,7 +5,7 @@
#define PACKAGE_VERSION_MAJOR 0
#define PACKAGE_VERSION_MINOR 9
#define PACKAGE_VERSION_PATCH 2
#define PACKAGE_VERSION_PATCH 3
char const *get_package_version_string();

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT
.\"
.Dd May 4, 2025
.Dd June 30, 2025
.Dt GBZ80 7
.Os
.Sh NAME

View File

@@ -2,7 +2,7 @@
.\"
.\" SPDX-License-Identifier: MIT
.\"
.Dd May 4, 2025
.Dd June 30, 2025
.Dt RGBASM-OLD 5
.Os
.Sh NAME

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT
.\"
.Dd May 4, 2025
.Dd June 30, 2025
.Dt RGBASM 1
.Os
.Sh NAME
@@ -51,8 +51,19 @@ is invalid because it could also be
The arguments are as follows:
.Bl -tag -width Ds
.It Fl b Ar chars , Fl \-binary-digits Ar chars
Change the two characters used for binary constants.
The defaults are 01.
Allow two characters to be used for binary constants in addition to the default
.Sq 0
and
.Sq 1 .
Valid characters are numbers other than
.Sq 0
and
.Sq 1 ,
letters,
.Sq \&. ,
.Sq # ,
or
.Sq @ .
.It Fl D Ar name Ns Oo = Ns Ar value Oc , Fl \-define Ar name Ns Oo = Ns Ar value Oc
Add a string symbol to the compiled source code.
This is equivalent to
@@ -65,7 +76,21 @@ is not specified.
.It Fl E , Fl \-export-all
Export all labels, including unreferenced and local labels.
.It Fl g Ar chars , Fl \-gfx-chars Ar chars
Change the four characters used for gfx constants.
Allow four characters to be used for graphics constants in addition to the default
.Sq 0 ,
.Sq 1 ,
.Sq 2 ,
and
.Sq 3 .
Valid characters are numbers other than
.Sq 0
to
.Sq 3 ,
letters,
.Sq \&. ,
.Sq # ,
or
.Sq @ .
The defaults are 0123.
.It Fl h , Fl \-help
Print help text for the program and exit.
@@ -275,7 +300,7 @@ This warning is enabled by
.Fl Wall .
.It Fl Wbuiltin-args
Warn about incorrect arguments to built-in functions, such as
.Fn STRSUB
.Fn STRSLICE
with indexes outside of the string's bounds.
This warning is enabled by
.Fl Wall .

View File

@@ -2,7 +2,7 @@
.\"
.\" SPDX-License-Identifier: MIT
.\"
.Dd May 4, 2025
.Dd June 30, 2025
.Dt RGBASM 5
.Os
.Sh NAME
@@ -548,7 +548,7 @@ There are a number of escape sequences you can use within a string:
.El
.Pp
Multi-line strings are contained in triple quotes
.Pq Ql \&"\&"\&"for instance\&"\&"\&" .
.Pq Ql \&"\&"\&"for instance""" .
Escape sequences work the same way in multi-line strings; however, literal newline characters will be included as-is, without needing to escape them with
.Ql \er
or
@@ -560,10 +560,19 @@ Inside them, backslashes and braces are treated like regular characters, so they
For example, the raw string
.Ql #"\et\e1{s}\e"
is equivalent to the regular string
.Ql "\e\et\e\e1\e{s}\e\e" .
.Ql \&"\e\et\e\e1\e{s}\e\e" .
(Note that this prevents raw strings from including the double quote character.)
Raw strings also may be contained in triple quotes for them to be multi-line, so they can include literal newline or quote characters (although still not three quotes in a row).
.Pp
You can use the
.Sq ++
operator to concatenate two strings.
.Ql \&"str" ++ \&"ing"
is equivalent to
.Ql \&"string" ,
or to
.Ql STRCAT("str", \&"ing") .
.Pp
The following functions operate on string expressions, and return strings themselves.
.Bl -column "STRSLICE(str, start, stop)"
.It Sy Name Ta Sy Operation
@@ -574,7 +583,7 @@ in uppercase.
.It Fn STRLWR str Ta Returns Ar str No with all ASCII letters
.Pq Ql A-Z
in lowercase.
.It Fn STRSLICE str start stop Ta Returns a substring of Ar str No starting at Ar start No and ending at Ar stop No (exclusive). If Ar stop No is not specified, the substring continues to the end of Ar str Ns .
.It Fn STRSLICE str start stop Ta Returns a substring of Ar str No starting at Ar start No and ending at Ar stop No (exclusive). If Ar stop No is not specified, the substring continues to the end of Ar str .
.It Fn STRRPL str old new Ta Returns Ar str No with each non-overlapping occurrence of the substring Ar old No replaced with Ar new .
.It Fn STRFMT fmt args... Ta Returns the string Ar fmt No with each
.Ql %spec
@@ -595,18 +604,25 @@ The following functions operate on string expressions, but return integers.
.It Fn STRCMP str1 str2 Ta Compares Ar str1 No and Ar str2 No according to ASCII ordering of their characters. Returns -1 if Ar str1 No is lower than Ar str2 Ns , 1 if Ar str1 No is greater than Ar str2 Ns , or 0 if they match.
.It Fn STRFIND str sub Ta Returns the first index of Ar sub No in Ar str Ns , or -1 if it's not present.
.It Fn STRRFIND str sub Ta Returns the last index of Ar sub No in Ar str Ns , or -1 if it's not present.
.It Fn INCHARMAP str Ta Returns 1 if Ar str No has an entry in the current charmap, or 0 otherwise .
.It Fn CHARLEN str Ta Returns the number of charmap entries in Ar str No with the current charmap .
.It Fn INCHARMAP str Ta Returns 1 if Ar str No has an entry in the current charmap, or 0 otherwise.
.It Fn CHARLEN str Ta Returns the number of charmap entries in Ar str No with the current charmap.
.It Fn CHARCMP str1 str2 Ta Compares Ar str1 No and Ar str2 No according to their charmap entry values with the current charmap. Returns -1 if Ar str1 No is lower than Ar str2 Ns , 1 if Ar str1 No is greater than Ar str2 Ns , or 0 if they match.
.It Fn CHARSIZE char Ta Returns how many values are in the charmap entry for Ar char No with the current charmap.
.It Fn CHARVAL char idx Ta Returns the value at Ar idx No of the charmap entry for Ar char .
.El
.Pp
Note that the first character of a string is at index 0, and the last is at index -1.
Note that indexes count starting from 0 at the beginning, or from -1 at the end.
The characters of a string are counted by
.Ql STRLEN ;
the charmap entries of a string are counted by
.Ql CHARLEN ;
and the values of a charmap entry are counted by
.Ql CHARSIZE .
.Pp
The following legacy functions are similar to other functions that operate on string expressions, but for historical reasons, they count characters starting from
The following legacy functions are similar to other functions that operate on string expressions, but for historical reasons, they count starting from
.Em position 1 ,
not from index 0!
(Position -1 still counts from the last character.)
(Position -1 still counts from the end.)
.Bl -column "STRSUB(str, pos, len)"
.It Sy Name Ta Sy Operation
.It Fn STRSUB str pos len Ta Returns a substring of Ar str No starting at Ar pos No and Ar len No characters long. If Ar len No is not specified, the substring continues to the end of Ar str No .

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT
.\"
.Dd May 4, 2025
.Dd June 30, 2025
.Dt RGBDS 5
.Os
.Sh NAME

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT
.\"
.Dd May 4, 2025
.Dd June 30, 2025
.Dt RGBDS 7
.Os
.Sh NAME

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT
.\"
.Dd May 4, 2025
.Dd June 30, 2025
.Dt RGBFIX 1
.Os
.Sh NAME

View File

@@ -2,7 +2,7 @@
.\"
.\" SPDX-License-Identifier: MIT
.\"
.Dd May 4, 2025
.Dd June 30, 2025
.Dt RGBGFX 1
.Os
.Sh NAME
@@ -159,6 +159,24 @@ is the case-insensitive word
then the first four colors of the input PNG's embedded palette are used.
It is an error if the PNG is not indexed, or if colors other than these 4 are used.
.Pq This is different from the default behavior of indexed PNGs, as then unused entries in the embedded palette are ignored, whereas they are not with Fl c Cm embedded .
.It Sy DMG palette spec
If
.Ar pal_spec
starts with case-insensitive
.Cm dmg= ,
then the following two-digit hexadecimal number specifies four grayscale DMG color indexes.
The number functions like the DMG's $FF47
.Sy BGP
register
(see
.Lk https://gbdev.io/pandocs/Palettes.html Pan Docs
for more information):
the low two bits 0-1 specify which gray shade goes in color index 0,
the next two bits 2-3 specify which gray shade goes in color index 1,
and so on.
Gray shade 0 is the lightest (white), 3 is the darkest (black).
The same gray shade cannot go in two color indexes.
To specify a DMG palette, the input PNG must have all its colors in shades of gray, without any transparent colors.
.It Sy external palette spec
Otherwise,
.Ar pal_spec
@@ -528,6 +546,8 @@ Otherwise, if the PNG only contains shades of gray, they will be categorized int
.Dq bins
as there are colors per palette, and the palette is set to these bins.
The darkest gray will end up in bin #0, and so on; note that this is the opposite of the RGB method below.
This is equivalent to having specified a DMG palette of
.Fl c Cm dmg=E4 .
If two distinct grays end up in the same bin, the RGB method is used instead.
.Pp
Be careful that

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT
.\"
.Dd May 4, 2025
.Dd June 30, 2025
.Dt RGBLINK 1
.Os
.Sh NAME

View File

@@ -1,6 +1,6 @@
.\" SPDX-License-Identifier: MIT
.\"
.Dd May 4, 2025
.Dd June 30, 2025
.Dt RGBLINK 5
.Os
.Sh NAME

View File

@@ -9,10 +9,10 @@ set(common_src
)
find_package(BISON 3.0.0 REQUIRED)
set(BISON_FLAGS "-Wall -Dparse.lac=full -Dlr.type=ielr")
set(BISON_FLAGS "-Wall -Dlr.type=ielr")
# Set some optimization flags on versions that support them
if(BISON_VERSION VERSION_GREATER_EQUAL "3.5")
set(BISON_FLAGS "${BISON_FLAGS} -Dapi.token.raw=true")
set(BISON_FLAGS "${BISON_FLAGS} -Dparse.lac=full -Dapi.token.raw=true")
endif()
if(BISON_VERSION VERSION_GREATER_EQUAL "3.6")
set(BISON_FLAGS "${BISON_FLAGS} -Dparse.error=detailed")

View File

@@ -189,7 +189,7 @@ bool charmap_HasChar(std::string const &mapping) {
return charmap.nodes[nodeIdx].isTerminal();
}
size_t charmap_CharSize(std::string const &mapping) {
static CharmapNode const *charmapEntry(std::string const &mapping) {
Charmap const &charmap = *currentCharmap;
size_t nodeIdx = 0;
@@ -197,12 +197,24 @@ size_t charmap_CharSize(std::string const &mapping) {
nodeIdx = charmap.nodes[nodeIdx].next[static_cast<uint8_t>(c)];
if (!nodeIdx) {
return 0;
return nullptr;
}
}
CharmapNode const &node = charmap.nodes[nodeIdx];
return node.isTerminal() ? node.value.size() : 0;
return &charmap.nodes[nodeIdx];
}
size_t charmap_CharSize(std::string const &mapping) {
CharmapNode const *node = charmapEntry(mapping);
return node && node->isTerminal() ? node->value.size() : 0;
}
std::optional<int32_t> charmap_CharValue(std::string const &mapping, size_t idx) {
if (CharmapNode const *node = charmapEntry(mapping);
node && node->isTerminal() && idx < node->value.size()) {
return node->value[idx];
}
return std::nullopt;
}
std::vector<int32_t> charmap_Convert(std::string const &input) {

View File

@@ -126,20 +126,20 @@ void fstk_SetPreIncludeFile(std::string const &path) {
// LCOV_EXCL_STOP
}
static void printDep(std::string const &path) {
if (dependFile) {
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), path.c_str());
if (generatePhonyDeps) {
fprintf(dependFile, "%s:\n", path.c_str());
}
}
}
static bool isValidFilePath(std::string const &path) {
struct stat statBuf;
return stat(path.c_str(), &statBuf) == 0 && !S_ISDIR(statBuf.st_mode); // Reject directories
}
static void printDep(std::string const &path) {
if (dependFile) {
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), path.c_str());
if (generatePhonyDeps && isValidFilePath(path)) {
fprintf(dependFile, "%s:\n", path.c_str());
}
}
}
std::optional<std::string> fstk_FindFile(std::string const &path) {
for (std::string &incPath : includePaths) {
if (std::string fullPath = incPath + path; isValidFilePath(fullPath)) {
@@ -392,17 +392,13 @@ void fstk_RunFor(
context.forName = symName;
}
void fstk_StopRept() {
contextStack.top().nbReptIters = 0; // Prevent more iterations
}
bool fstk_Break() {
if (contextStack.top().fileInfo->type != NODE_REPT) {
error("BREAK can only be used inside a REPT/FOR block\n");
return false;
}
fstk_StopRept();
contextStack.top().nbReptIters = 0; // Prevent more iterations
return true;
}

View File

@@ -136,9 +136,8 @@ struct CaseInsensitive {
}
};
// This map lists all RGBASM keywords which `yylex_NORMAL` lexes as identifiers
// (see `startsIdentifier` and `continuesIdentifier` below). All non-identifier
// tokens are lexed separately.
// This map lists all RGBASM keywords which `yylex_NORMAL` lexes as identifiers.
// All non-identifier tokens are lexed separately.
static std::unordered_map<std::string, int, CaseInsensitive, CaseInsensitive> keywordDict = {
{"ADC", T_(SM83_ADC) },
{"ADD", T_(SM83_ADD) },
@@ -260,6 +259,7 @@ static std::unordered_map<std::string, int, CaseInsensitive, CaseInsensitive> ke
{"CHARLEN", T_(OP_CHARLEN) },
{"CHARSIZE", T_(OP_CHARSIZE) },
{"CHARSUB", T_(OP_CHARSUB) },
{"CHARVAL", T_(OP_CHARVAL) },
{"INCHARMAP", T_(OP_INCHARMAP) },
{"REVCHAR", T_(OP_REVCHAR) },
@@ -611,9 +611,7 @@ static bool isMacroChar(char c) {
// forward declarations for readBracketedMacroArgNum
static int peek();
static void shiftChar();
static uint32_t readNumber(int radix, uint32_t baseValue);
static bool startsIdentifier(int c);
static bool continuesIdentifier(int c);
static uint32_t readDecimalNumber(int initial);
static uint32_t readBracketedMacroArgNum() {
bool disableMacroArgs = lexerState->disableMacroArgs;
@@ -637,7 +635,7 @@ static uint32_t readBracketedMacroArgNum() {
}
if (c >= '0' && c <= '9') {
uint32_t n = readNumber(10, 0);
uint32_t n = readDecimalNumber(0);
if (n > INT32_MAX) {
error("Number in bracketed macro argument is too large\n");
return 0;
@@ -1021,26 +1019,6 @@ static std::string readAnonLabelRef(char c) {
return sym_MakeAnonLabelName(n, c == '-');
}
static uint32_t readNumber(int radix, uint32_t baseValue) {
uint32_t value = baseValue;
for (;; shiftChar()) {
int c = peek();
if (c == '_') {
continue;
} else if (c < '0' || c > '0' + radix - 1) {
break;
}
if (value > (UINT32_MAX - (c - '0')) / radix) {
warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n");
}
value = value * radix + (c - '0');
}
return value;
}
static uint32_t readFractionalPart(uint32_t integer) {
uint32_t value = 0, divisor = 1;
uint8_t precision = 0;
@@ -1106,21 +1084,64 @@ static uint32_t readFractionalPart(uint32_t integer) {
}
char binDigits[2];
char gfxDigits[4];
static bool isValidDigit(char c) {
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '.'
|| c == '#' || c == '@';
}
static bool checkDigitErrors(char const *digits, size_t n, char const *type) {
for (size_t i = 0; i < n; i++) {
char c = digits[i];
if (!isValidDigit(c)) {
error("Invalid digit for %s constant %s\n", type, printChar(c));
return false;
}
if (c >= '0' && c < static_cast<char>(n + '0') && c != static_cast<char>(i + '0')) {
error("Changed digit for %s constant %s\n", type, printChar(c));
return false;
}
for (size_t j = i + 1; j < n; j++) {
if (c == digits[j]) {
error("Repeated digit for %s constant %s\n", type, printChar(c));
return false;
}
}
}
return true;
}
void lexer_SetBinDigits(char const digits[2]) {
if (size_t n = std::size(binDigits); checkDigitErrors(digits, n, "binary")) {
memcpy(binDigits, digits, n);
}
}
void lexer_SetGfxDigits(char const digits[4]) {
if (size_t n = std::size(gfxDigits); checkDigitErrors(digits, n, "graphics")) {
memcpy(gfxDigits, digits, n);
}
}
static uint32_t readBinaryNumber() {
uint32_t value = 0;
bool empty = true;
for (;; shiftChar()) {
int c = peek();
int bit;
// Check for '_' after digits in case one of the digits is '_'
if (c == binDigits[0]) {
bit = 0;
} else if (c == binDigits[1]) {
bit = 1;
} else if (c == '_') {
if (c == '_' && !empty) {
continue;
} else if (c == '0' || c == binDigits[0]) {
bit = 0;
} else if (c == '1' || c == binDigits[1]) {
bit = 1;
} else {
break;
}
@@ -1128,6 +1149,72 @@ static uint32_t readBinaryNumber() {
warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n");
}
value = value * 2 + bit;
empty = false;
}
if (empty) {
error("Invalid integer constant, no digits after '%%'\n");
}
return value;
}
static uint32_t readOctalNumber() {
uint32_t value = 0;
bool empty = true;
for (;; shiftChar()) {
int c = peek();
if (c == '_' && !empty) {
continue;
} else if (c >= '0' && c <= '7') {
c = c - '0';
} else {
break;
}
if (value > (UINT32_MAX - c) / 8) {
warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n");
}
value = value * 8 + c;
empty = false;
}
if (empty) {
error("Invalid integer constant, no digits after '&'\n");
}
return value;
}
static uint32_t readDecimalNumber(int initial) {
uint32_t value = initial ? initial - '0' : 0;
bool empty = !initial;
for (;; shiftChar()) {
int c = peek();
if (c == '_' && !empty) {
continue;
} else if (c >= '0' && c <= '9') {
c = c - '0';
} else {
break;
}
if (value > (UINT32_MAX - c) / 10) {
warning(WARNING_LARGE_CONSTANT, "Integer constant is too large\n");
}
value = value * 10 + c;
empty = false;
}
if (empty) {
error("Invalid integer constant, no digits\n");
}
return value;
@@ -1140,14 +1227,14 @@ static uint32_t readHexNumber() {
for (;; shiftChar()) {
int c = peek();
if (c >= 'a' && c <= 'f') {
if (c == '_' && !empty) {
continue;
} else if (c >= 'a' && c <= 'f') {
c = c - 'a' + 10;
} else if (c >= 'A' && c <= 'F') {
c = c - 'A' + 10;
} else if (c >= '0' && c <= '9') {
c = c - '0';
} else if (c == '_' && !empty) {
continue;
} else {
break;
}
@@ -1167,8 +1254,6 @@ static uint32_t readHexNumber() {
return value;
}
char gfxDigits[4];
static uint32_t readGfxConstant() {
uint32_t bitPlaneLower = 0, bitPlaneUpper = 0;
uint8_t width = 0;
@@ -1177,17 +1262,16 @@ static uint32_t readGfxConstant() {
int c = peek();
uint32_t pixel;
// Check for '_' after digits in case one of the digits is '_'
if (c == gfxDigits[0]) {
pixel = 0;
} else if (c == gfxDigits[1]) {
pixel = 1;
} else if (c == gfxDigits[2]) {
pixel = 2;
} else if (c == gfxDigits[3]) {
pixel = 3;
} else if (c == '_' && width > 0) {
if (c == '_' && width > 0) {
continue;
} else if (c == '0' || c == gfxDigits[0]) {
pixel = 0;
} else if (c == '1' || c == gfxDigits[1]) {
pixel = 1;
} else if (c == '2' || c == gfxDigits[2]) {
pixel = 2;
} else if (c == '3' || c == gfxDigits[3]) {
pixel = 3;
} else {
break;
}
@@ -1215,15 +1299,6 @@ static uint32_t readGfxConstant() {
// Functions to read identifiers and keywords
static bool startsIdentifier(int c) {
// Anonymous labels internally start with '!'
return (c <= 'Z' && c >= 'A') || (c <= 'z' && c >= 'a') || c == '.' || c == '_';
}
static bool continuesIdentifier(int c) {
return startsIdentifier(c) || (c <= '9' && c >= '0') || c == '#' || c == '$' || c == '@';
}
static Token readIdentifier(char firstChar, bool raw) {
std::string identifier(1, firstChar);
int tokenType = firstChar == '.' ? T_(LOCAL) : T_(SYMBOL);
@@ -1700,12 +1775,17 @@ static Token yylex_NORMAL() {
// Handle ambiguous 1- or 2-char tokens
case '+': // Either += or ADD
if (peek() == '=') {
case '+': // Either +=, ADD, or CAT
switch (peek()) {
case '=':
shiftChar();
return Token(T_(POP_ADDEQ));
}
case '+':
shiftChar();
return Token(T_(OP_CAT));
default:
return Token(T_(OP_ADD));
}
case '-': // Either -= or SUB
if (peek() == '=') {
@@ -1838,7 +1918,7 @@ static Token yylex_NORMAL() {
case 'o':
case 'O':
shiftChar();
return Token(T_(NUMBER), readNumber(8, 0));
return Token(T_(NUMBER), readOctalNumber());
case 'b':
case 'B':
shiftChar();
@@ -1857,7 +1937,7 @@ static Token yylex_NORMAL() {
case '7':
case '8':
case '9': {
uint32_t n = readNumber(10, c - '0');
uint32_t n = readDecimalNumber(c);
if (peek() == '.') {
shiftChar();
@@ -1875,7 +1955,7 @@ static Token yylex_NORMAL() {
shiftChar();
return Token(T_(OP_LOGICAND));
} else if (c >= '0' && c <= '7') {
return Token(T_(NUMBER), readNumber(8, 0));
return Token(T_(NUMBER), readOctalNumber());
}
return Token(T_(OP_AND));
@@ -1884,7 +1964,7 @@ static Token yylex_NORMAL() {
if (c == '=') {
shiftChar();
return Token(T_(POP_MODEQ));
} else if (c == binDigits[0] || c == binDigits[1]) {
} else if (c == '0' || c == '1' || c == binDigits[0] || c == binDigits[1]) {
return Token(T_(NUMBER), readBinaryNumber());
}
return Token(T_(OP_MOD));

View File

@@ -36,7 +36,7 @@ static std::string make_escape(std::string &str) {
size_t pos = 0;
for (;;) {
// All dollars needs to be doubled
size_t nextPos = str.find("$", pos);
size_t nextPos = str.find('$', pos);
if (nextPos == std::string::npos) {
break;
}

View File

@@ -14,8 +14,8 @@
#include "asm/warning.hpp"
struct OptStackEntry {
char binary[2];
char gbgfx[4];
char binDigits[2];
char gfxDigits[4];
uint8_t fixPrecision;
uint8_t fillByte;
bool warningsAreErrors;
@@ -151,13 +151,8 @@ void opt_Push() {
OptStackEntry entry;
// Both of these are pulled from lexer.hpp
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];
memcpy(entry.binDigits, binDigits, std::size(binDigits));
memcpy(entry.gfxDigits, gfxDigits, std::size(gfxDigits));
entry.fixPrecision = fixPrecision; // Pulled from fixpoint.hpp
@@ -181,8 +176,8 @@ void opt_Pop() {
OptStackEntry entry = stack.top();
stack.pop();
opt_B(entry.binary);
opt_G(entry.gbgfx);
opt_B(entry.binDigits);
opt_G(entry.gfxDigits);
opt_P(entry.fillByte);
opt_Q(entry.fixPrecision);
opt_R(entry.maxRecursionDepth);

View File

@@ -125,6 +125,9 @@
%token OP_MUL "*" OP_DIV "/" OP_MOD "%"
%token OP_EXP "**"
// String operators
%token OP_CAT "++"
// Comparison operators
%token OP_LOGICEQU "==" OP_LOGICNE "!="
%token OP_LOGICLT "<" OP_LOGICGT ">"
@@ -147,6 +150,7 @@
%left OP_AND OP_OR OP_XOR
%left OP_SHL OP_SHR OP_USHR
%left OP_MUL OP_DIV OP_MOD
%left OP_CAT
%precedence NEG // applies to unary OP_LOGICNOT, OP_ADD, OP_SUB, OP_NOT
%right OP_EXP
@@ -283,6 +287,7 @@
%token OP_CHARLEN "CHARLEN"
%token OP_CHARSIZE "CHARSIZE"
%token OP_CHARSUB "CHARSUB"
%token OP_CHARVAL "CHARVAL"
%token OP_COS "COS"
%token OP_DEF "DEF"
%token OP_FDIV "FDIV"
@@ -425,7 +430,6 @@ lines:
lexer_SetMode(LEXER_NORMAL);
lexer_ToggleStringExpansion(true);
} endofline {
fstk_StopRept();
yyerrok;
}
;
@@ -1577,6 +1581,24 @@ relocexpr_no_str:
}
$$.makeNumber(charSize);
}
| OP_CHARVAL LPAREN string COMMA iconst RPAREN {
if (size_t len = charmap_CharSize($3); len != 0) {
uint32_t idx = adjustNegativeIndex($5, len, "CHARVAL");
if (std::optional<int32_t> val = charmap_CharValue($3, idx); val.has_value()) {
$$.makeNumber(*val);
} else {
warning(
WARNING_BUILTIN_ARG,
"CHARVAL: Index %" PRIu32 " is past the end of the character mapping\n",
idx
);
$$.makeNumber(0);
}
} else {
::error("CHARVAL: No character mapping for \"%s\"\n", $3.c_str());
$$.makeNumber(0);
}
}
| LPAREN relocexpr RPAREN {
$$ = std::move($2);
}
@@ -1614,6 +1636,10 @@ string_literal:
STRING {
$$ = std::move($1);
}
| string OP_CAT string {
$$ = std::move($1);
$$.append($3);
}
| OP_STRSLICE LPAREN string COMMA iconst COMMA iconst RPAREN {
size_t len = strlenUTF8($3, false);
uint32_t start = adjustNegativeIndex($5, len, "STRSLICE");
@@ -1623,7 +1649,7 @@ string_literal:
| OP_STRSLICE LPAREN string COMMA iconst RPAREN {
size_t len = strlenUTF8($3, false);
uint32_t start = adjustNegativeIndex($5, len, "STRSLICE");
$$ = strsliceUTF8($3, start, len - 1);
$$ = strsliceUTF8($3, start, len);
}
| OP_STRSUB LPAREN string COMMA iconst COMMA uconst RPAREN {
size_t len = strlenUTF8($3, false);

View File

@@ -2,6 +2,7 @@
#include "asm/symbol.hpp"
#include <algorithm>
#include <inttypes.h>
#include <stdio.h>
#include <unordered_map>
@@ -9,6 +10,7 @@
#include "error.hpp"
#include "helpers.hpp" // assume
#include "util.hpp"
#include "version.hpp"
#include "asm/fstack.hpp"
@@ -130,6 +132,11 @@ static void updateSymbolFilename(Symbol &sym) {
}
}
static bool isValidIdentifier(std::string const &s) {
return !s.empty() && startsIdentifier(s[0])
&& std::all_of(s.begin() + 1, s.end(), [](char c) { return continuesIdentifier(c); });
}
static void alreadyDefinedError(Symbol const &sym, char const *asType) {
if (sym.isBuiltin && !sym_FindScopedValidSymbol(sym.name)) {
// `DEF()` would return false, so we should not claim the symbol is already defined
@@ -141,6 +148,15 @@ static void alreadyDefinedError(Symbol const &sym, char const *asType) {
}
fputs(" at ", stderr);
dumpFilename(sym);
if (sym.type == SYM_EQUS) {
if (std::string const &contents = *sym.getEqus(); isValidIdentifier(contents)) {
fprintf(
stderr,
" (should it be {interpolated} to define its contents \"%s\"?)\n",
contents.c_str()
);
}
}
}
}

View File

@@ -12,11 +12,11 @@ if [ "$BISON_MAJOR" -lt 3 ]; then
exit 1
fi
BISON_FLAGS="-Wall -Dparse.lac=full -Dlr.type=ielr"
BISON_FLAGS="-Wall -Dlr.type=ielr"
# Set some optimization flags on versions that support them
if [ "$BISON_MAJOR" -ge 4 ] || [ "$BISON_MAJOR" -eq 3 ] && [ "$BISON_MINOR" -ge 5 ]; then
BISON_FLAGS="$BISON_FLAGS -Dapi.token.raw=true"
BISON_FLAGS="$BISON_FLAGS -Dparse.lac=full -Dapi.token.raw=true"
fi
if [ "$BISON_MAJOR" -ge 4 ] || [ "$BISON_MAJOR" -eq 3 ] && [ "$BISON_MINOR" -ge 6 ]; then
BISON_FLAGS="$BISON_FLAGS -Dparse.error=detailed"

View File

@@ -16,7 +16,6 @@
#include "extern/getopt.hpp"
#include "file.hpp"
#include "helpers.hpp" // assume
#include "platform.hpp"
#include "version.hpp"
@@ -354,7 +353,6 @@ static char *parseArgv(int argc, char *argv[]) {
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
char *arg = musl_optarg; // Make a copy for scanning
uint16_t number;
size_t size;
switch (ch) {
case 'A':
localOptions.autoAttrmap = true;
@@ -367,41 +365,7 @@ static char *parseArgv(int argc, char *argv[]) {
options.attrmap = musl_optarg;
break;
case 'B':
if (strcasecmp(musl_optarg, "transparent") == 0) {
options.bgColor = Rgba(0x00, 0x00, 0x00, 0x00);
break;
}
if (musl_optarg[0] != '#') {
error("Background color specification must be `#rgb`, `#rrggbb`, or `transparent`");
break;
}
size = strspn(&musl_optarg[1], "0123456789ABCDEFabcdef");
switch (size) {
case 3:
options.bgColor = Rgba(
singleToHex(musl_optarg[1]),
singleToHex(musl_optarg[2]),
singleToHex(musl_optarg[3]),
0xFF
);
break;
case 6:
options.bgColor = Rgba(
toHex(musl_optarg[1], musl_optarg[2]),
toHex(musl_optarg[3], musl_optarg[4]),
toHex(musl_optarg[5], musl_optarg[6]),
0xFF
);
break;
default:
error("Unknown background color specification \"%s\"", musl_optarg);
}
if (musl_optarg[size + 1] != '\0') {
error(
"Unexpected text \"%s\" after background color specification",
&musl_optarg[size + 1]
);
}
parseBackgroundPalSpec(musl_optarg);
break;
case 'b':
number = parseNumber(arg, "Bank 0 base tile ID", 0);
@@ -442,18 +406,18 @@ static char *parseArgv(int argc, char *argv[]) {
options.useColorCurve = true;
break;
case 'c':
localOptions.externalPalSpec = nullptr; // Allow overriding a previous pal spec
if (musl_optarg[0] == '#') {
options.palSpecType = Options::EXPLICIT;
parseInlinePalSpec(musl_optarg);
} else if (strcasecmp(musl_optarg, "embedded") == 0) {
// Use PLTE, error out if missing
options.palSpecType = Options::EMBEDDED;
} else if (strncasecmp(musl_optarg, "dmg=", literal_strlen("dmg=")) == 0) {
options.palSpecType = Options::DMG;
parseDmgPalSpec(&musl_optarg[literal_strlen("dmg=")]);
} else {
options.palSpecType = Options::EXPLICIT;
// Can't parse the file yet, as "flat" color collections need to know the palette
// size to be split; thus, we defer that.
// If a following `-c` overrides a previous one, the `fmt` part of an overridden
// external palette spec will not be validated, but I guess that's okay.
localOptions.externalPalSpec = musl_optarg;
}
break;
@@ -800,10 +764,31 @@ int main(int argc, char *argv[]) {
if (options.verbosity >= Options::VERB_VVVVVV) {
putc('\n', stderr);
// clang-format off: vertically align values
static std::array<uint16_t, 21> gfx{
0x1FE, 0x3FF, 0x399, 0x399, 0x3FF, 0x3FF, 0x381, 0x3C3, 0x1FE, 0x078, 0x1FE,
0x3FF, 0x3FF, 0x3FF, 0x37B, 0x37B, 0x0FC, 0x0CC, 0x1CE, 0x1CE, 0x1CE,
0b0111111110,
0b1111111111,
0b1110011001,
0b1110011001,
0b1111111111,
0b1111111111,
0b1110000001,
0b1111000011,
0b0111111110,
0b0001111000,
0b0111111110,
0b1111111111,
0b1111111111,
0b1111111111,
0b1101111011,
0b1101111011,
0b0011111100,
0b0011001100,
0b0111001110,
0b0111001110,
0b0111001110,
};
// clang-format on
static std::array<char const *, 3> textbox{
" ,----------------------------------------.",
" | Augh, dimensional interference again?! |",
@@ -856,6 +841,8 @@ int main(int argc, char *argv[]) {
return "Explicit";
case Options::EMBEDDED:
return "Embedded";
case Options::DMG:
return "DMG";
}
return "???";
}());

View File

@@ -299,7 +299,7 @@ static void decant(
break;
}
auto attrs = from.begin();
std::advance(attrs, (iter - processed.begin()));
std::advance(attrs, iter - processed.begin());
// Build up the "component"...
colors.clear();

View File

@@ -28,6 +28,27 @@ static void skipWhitespace(Str const &str, size_t &pos) {
pos = std::min(str.find_first_not_of(" \t"sv, pos), str.length());
}
static constexpr uint8_t nibble(char c) {
if (c >= 'a') {
assume(c <= 'f');
return c - 'a' + 10;
} else if (c >= 'A') {
assume(c <= 'F');
return c - 'A' + 10;
} else {
assume(c >= '0' && c <= '9');
return c - '0';
}
}
static constexpr uint8_t toHex(char c1, char c2) {
return nibble(c1) * 16 + nibble(c2);
}
static constexpr uint8_t singleToHex(char c) {
return toHex(c, c);
}
void parseInlinePalSpec(char const * const rawArg) {
// List of #rrggbb/#rgb colors (or #none); comma-separated.
// Palettes are separated by colons.
@@ -595,3 +616,61 @@ void parseExternalPalSpec(char const *arg) {
std::get<1> (*iter)(file);
}
void parseDmgPalSpec(char const * const rawArg) {
// Two hex digit DMG palette spec
std::string_view arg(rawArg);
if (arg.length() != 2
|| arg.find_first_not_of("0123456789ABCDEFabcdef"sv) != std::string_view::npos) {
error("Unknown DMG palette specification \"%s\"", rawArg);
return;
}
options.palSpecDmg = toHex(arg[0], arg[1]);
// Map gray shades to their DMG color indexes for fast lookup by `Rgba::grayIndex`
for (uint8_t i = 0; i < 4; ++i) {
options.dmgColors[options.dmgValue(i)] = i;
}
// Validate that DMG palette spec does not have conflicting colors
for (uint8_t i = 0; i < 3; ++i) {
for (uint8_t j = i + 1; j < 4; ++j) {
if (options.dmgValue(i) == options.dmgValue(j)) {
error("DMG palette specification maps two gray shades to the same color index");
return;
}
}
}
}
void parseBackgroundPalSpec(char const *arg) {
if (strcasecmp(arg, "transparent") == 0) {
options.bgColor = Rgba(0x00, 0x00, 0x00, 0x00);
return;
}
if (arg[0] != '#') {
error("Background color specification must be `#rgb`, `#rrggbb`, or `transparent`");
return;
}
size_t size = strspn(&arg[1], "0123456789ABCDEFabcdef");
switch (size) {
case 3:
options.bgColor = Rgba(singleToHex(arg[1]), singleToHex(arg[2]), singleToHex(arg[3]), 0xFF);
break;
case 6:
options.bgColor =
Rgba(toHex(arg[1], arg[2]), toHex(arg[3], arg[4]), toHex(arg[5], arg[6]), 0xFF);
break;
default:
error("Unknown background color specification \"%s\"", arg);
}
if (arg[size + 1] != '\0') {
error("Unexpected text \"%s\" after background color specification", &arg[size + 1]);
}
}

View File

@@ -586,8 +586,10 @@ static std::tuple<DefaultInitVec<size_t>, std::vector<Palette>>
}
// "Sort" colors in the generated palettes, see the man page for the flowchart
auto [embPalSize, embPalRGB, embPalAlphaSize, embPalAlpha] = png.getEmbeddedPal();
if (embPalRGB != nullptr) {
if (options.palSpecType == Options::DMG) {
sortGrayscale(palettes, png.getColors().raw());
} else if (auto [embPalSize, embPalRGB, embPalAlphaSize, embPalAlpha] = png.getEmbeddedPal();
embPalRGB != nullptr) {
sortIndexed(palettes, embPalSize, embPalRGB, embPalAlphaSize, embPalAlpha);
} else if (png.isSuitableForGrayscale()) {
sortGrayscale(palettes, png.getColors().raw());
@@ -1139,6 +1141,18 @@ void process() {
}
// LCOV_EXCL_STOP
if (options.palSpecType == Options::DMG) {
if (options.hasTransparentPixels) {
fatal(
"Image contains transparent pixels, not compatible with a DMG palette specification"
);
}
if (!png.isSuitableForGrayscale()) {
fatal("Image contains too many or non-gray colors, not compatible with a DMG palette "
"specification");
}
}
// Now, iterate through the tiles, generating proto-palettes as we go
// We do this unconditionally because this performs the image validation (which we want to
// perform even if no output is requested), and because it's necessary to generate any
@@ -1248,7 +1262,8 @@ continue_visiting_tiles:;
if (options.palSpecType == Options::EMBEDDED) {
generatePalSpec(png);
}
auto [mappings, palettes] = options.palSpecType == Options::NO_SPEC
auto [mappings, palettes] =
options.palSpecType == Options::NO_SPEC || options.palSpecType == Options::DMG
? generatePalettes(protoPalettes, png)
: makePalsAsSpecified(protoPalettes);
outputPalettes(palettes);

View File

@@ -195,10 +195,13 @@ void reverse() {
Options::VERB_INTERM, "Reversed image dimensions: %zux%zu tiles\n", width, height
);
std::vector<std::array<std::optional<Rgba>, 4>> palettes{
{Rgba(0xFFFFFFFF), Rgba(0xAAAAAAFF), Rgba(0x555555FF), Rgba(0x000000FF)}
Rgba const grayColors[4] = {
Rgba(0xFFFFFFFF), Rgba(0xAAAAAAFF), Rgba(0x555555FF), Rgba(0x000000FF)
};
// If a palette file is used as input, it overrides the default colors.
std::vector<std::array<std::optional<Rgba>, 4>> palettes{
{grayColors[0], grayColors[1], grayColors[2], grayColors[3]}
};
// If a palette file or palette spec is used as input, it overrides the default colors.
if (!options.palettes.empty()) {
File file;
if (!file.open(options.palettes, std::ios::in | std::ios::binary)) {
@@ -255,6 +258,10 @@ void reverse() {
putc('\n', stderr);
}
}
} else if (options.palSpecType == Options::DMG) {
for (size_t i = 0; i < palettes[0].size(); ++i) {
palettes[0][i] = grayColors[options.dmgValue(i)];
}
} else if (options.palSpecType == Options::EMBEDDED) {
warning("An embedded palette was requested, but no palette file was specified; ignoring "
"request.");
@@ -422,38 +429,73 @@ void reverse() {
}
png_set_write_fn(png, &pngFile, writePng, flushPng);
int pngColorType = options.palettes.empty() ? PNG_COLOR_TYPE_GRAY
: palettes.size() == 1 ? PNG_COLOR_TYPE_PALETTE
: PNG_COLOR_TYPE_RGB_ALPHA;
int pngDepth = options.palettes.empty() ? options.bitDepth : 8;
png_set_IHDR(
png,
pngInfo,
width * 8,
height * 8,
8,
PNG_COLOR_TYPE_RGB_ALPHA,
pngDepth,
pngColorType,
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT
);
png_write_info(png, pngInfo);
if (pngColorType != PNG_COLOR_TYPE_GRAY) {
png_color_8 sbitChunk;
sbitChunk.red = 5;
sbitChunk.green = 5;
sbitChunk.blue = 5;
if (pngColorType == PNG_COLOR_TYPE_RGB_ALPHA) {
sbitChunk.alpha = 1;
}
png_set_sBIT(png, pngInfo, &sbitChunk);
}
constexpr uint8_t SIZEOF_TILE = 4 * 8; // 4 bytes/pixel (RGBA @ 8 bits/channel) * 8 pixels/tile
size_t const SIZEOF_ROW = width * SIZEOF_TILE;
std::vector<uint8_t> tileRow(8 * SIZEOF_ROW, 0xFF); // Data for 8 rows of pixels
if (pngColorType == PNG_COLOR_TYPE_PALETTE) {
assume(palettes.size() == 1);
png_color pngPalette[4] = {};
png_byte pngTrans[4] = {};
int nbPngColors = 0, nbPngTrans = 0;
for (auto const &color : palettes[0]) {
if (!color.has_value()) {
continue;
}
pngPalette[nbPngColors].red = color->red;
pngPalette[nbPngColors].green = color->green;
pngPalette[nbPngColors].blue = color->blue;
pngTrans[nbPngColors] = color->alpha;
++nbPngColors;
if (color->alpha < 255) {
nbPngTrans = nbPngColors;
}
}
png_set_PLTE(png, pngInfo, pngPalette, nbPngColors);
if (nbPngTrans > 0) {
png_set_tRNS(png, pngInfo, pngTrans, nbPngTrans, nullptr);
}
}
png_write_info(png, pngInfo);
// N bits/pixel * 8 pixels/tile row / 8 bits/byte = N bytes/tile row
uint8_t const bytesPerTileRow = pngColorType == PNG_COLOR_TYPE_RGB_ALPHA ? 32 : pngDepth;
size_t const bytesPerRow = width * bytesPerTileRow;
std::vector<uint8_t> tileRow(8 * bytesPerRow, 0xFF); // Data for 8 rows of pixels
uint8_t * const rowPtrs[8] = {
&tileRow.data()[0 * SIZEOF_ROW],
&tileRow.data()[1 * SIZEOF_ROW],
&tileRow.data()[2 * SIZEOF_ROW],
&tileRow.data()[3 * SIZEOF_ROW],
&tileRow.data()[4 * SIZEOF_ROW],
&tileRow.data()[5 * SIZEOF_ROW],
&tileRow.data()[6 * SIZEOF_ROW],
&tileRow.data()[7 * SIZEOF_ROW],
&tileRow.data()[0 * bytesPerRow],
&tileRow.data()[1 * bytesPerRow],
&tileRow.data()[2 * bytesPerRow],
&tileRow.data()[3 * bytesPerRow],
&tileRow.data()[4 * bytesPerRow],
&tileRow.data()[5 * bytesPerRow],
&tileRow.data()[6 * bytesPerRow],
&tileRow.data()[7 * bytesPerRow],
};
for (size_t ty = 0; ty < height; ++ty) {
@@ -486,19 +528,36 @@ void reverse() {
bitplane0 = flipTable[bitplane0];
bitplane1 = flipTable[bitplane1];
}
uint8_t *ptr = &rowPtrs[y][tx * SIZEOF_TILE];
uint8_t *ptr = &rowPtrs[y][tx * bytesPerTileRow];
uint16_t gray = 0;
for (uint8_t x = 0; x < 8; ++x) {
uint8_t bit0 = bitplane0 & 0x80, bit1 = bitplane1 & 0x80;
Rgba const &pixel = *palette[bit0 >> 7 | bit1 >> 6];
uint8_t colorID = bit0 >> 7 | bit1 >> 6;
Rgba const &pixel = *palette[colorID];
if (pngColorType == PNG_COLOR_TYPE_GRAY) {
gray = gray << pngDepth | (pixel.red & ((1 << pngDepth) - 1));
} else if (pngColorType == PNG_COLOR_TYPE_PALETTE) {
*ptr++ = palID * 4 + colorID;
} else {
*ptr++ = pixel.red;
*ptr++ = pixel.green;
*ptr++ = pixel.blue;
*ptr++ = pixel.alpha;
}
// Shift the pixel out
bitplane0 <<= 1;
bitplane1 <<= 1;
}
if (pngDepth == 1) {
*ptr = gray;
} else if (pngDepth == 2) {
*ptr++ = gray >> 8;
*ptr = gray & 0xff;
}
}
}
// We never modify the pointers, and neither should libpng, despite the overly lax function

View File

@@ -58,7 +58,15 @@ uint16_t Rgba::cgbColor() const {
uint8_t Rgba::grayIndex() const {
assume(isGray());
// Convert from 0..<256 to hasTransparentPixels..<nbColorsPerPal
// 2bpp shades are inverted from RGB PNG; %00 = white, %11 = black
uint8_t gray = 255 - red;
if (options.palSpecType == Options::DMG) {
assume(!options.hasTransparentPixels);
// Reduce gray shade from 0..<256 to 0..<4, then map to color index,
// then reduce to 0..<nbColorsPerPal
return options.dmgColors[gray * 4 / 256] * options.nbColorsPerPal / 4;
}
// Reduce gray shade from 0..<256 to hasTransparentPixels..<nbColorsPerPal
// Note that `maxOpaqueColors()` already takes `hasTransparentPixels` into account
return (255 - red) * options.maxOpaqueColors() / 256 + options.hasTransparentPixels;
return gray * options.maxOpaqueColors() / 256 + options.hasTransparentPixels;
}

View File

@@ -15,6 +15,7 @@
#include "helpers.hpp"
#include "linkdefs.hpp"
#include "platform.hpp"
#include "util.hpp"
#include "link/main.hpp"
#include "link/section.hpp"
@@ -30,6 +31,7 @@ FILE *mapFile;
struct SortedSymbol {
Symbol const *sym;
uint16_t addr;
uint16_t parentAddr;
};
struct SortedSections {
@@ -259,29 +261,13 @@ static void writeROM() {
}
}
// Checks whether a symbol is legal for a sym file or map file.
// Eliminates anonymous labels, which start with a '!'.
static bool isLegalSymbol(Symbol const &sym) {
if (sym.name.empty()) {
return false;
}
char c = sym.name[0];
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_';
}
// Checks whether this character is legal in a symbol's name in a sym file
static bool isLegalForSymName(char c) {
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_'
|| c == '@' || c == '#' || c == '$' || c == '.';
}
// Prints a symbol's name to a file, assuming that the first character is legal.
// Illegal characters are UTF-8-decoded (errors are replaced by U+FFFD) and emitted as '\u'/'\U'.
static void printSymName(std::string const &name, FILE *file) {
for (char const *ptr = name.c_str(); *ptr != '\0';) {
char c = *ptr;
if (isLegalForSymName(c)) {
if (continuesIdentifier(c)) {
// Output legal ASCII characters as-is
putc(c, file);
++ptr;
@@ -311,33 +297,28 @@ static void printSymName(std::string const &name, FILE *file) {
}
// Comparator function for `std::stable_sort` to sort symbols
// Symbols are ordered by address, then by parentage
static bool compareSymbols(SortedSymbol const &sym1, SortedSymbol const &sym2) {
// First, sort by address
if (sym1.addr != sym2.addr) {
return sym1.addr < sym2.addr;
}
// Second, sort by locality (global before local)
std::string const &sym1_name = sym1.sym->name;
std::string const &sym2_name = sym2.sym->name;
bool sym1_local = sym1_name.find(".") != std::string::npos;
bool sym2_local = sym2_name.find(".") != std::string::npos;
bool sym1_local = sym1_name.find('.') != std::string::npos;
bool sym2_local = sym2_name.find('.') != std::string::npos;
if (sym1_local != sym2_local) {
size_t sym1_len = sym1_name.length();
size_t sym2_len = sym2_name.length();
// Sort parent labels before their child local labels
if (sym2_name.starts_with(sym1_name) && sym2_name[sym1_len] == '.') {
return true;
}
if (sym1_name.starts_with(sym2_name) && sym1_name[sym2_len] == '.') {
return false;
}
// Sort local labels before unrelated global labels
return sym1_local;
return sym1_local < sym2_local;
}
return false;
// Third, sort by parent address
if (sym1.parentAddr != sym2.parentAddr) {
return sym1.parentAddr < sym2.parentAddr;
}
// Fourth, sort by name
return sym1_name < sym2_name;
}
// Write a bank's contents to the sym file
@@ -373,11 +354,20 @@ static void writeSymBank(SortedSections const &bankSections, SectionType type, u
forEachSortedSection(sect, {
for (Symbol const *sym : sect->symbols) {
// Don't output symbols that begin with an illegal character
if (isLegalSymbol(*sym)) {
symList.push_back({
.sym = sym,
.addr = static_cast<uint16_t>(sym->label().offset + sect->org),
});
if (!sym->name.empty() && startsIdentifier(sym->name[0])) {
uint16_t addr = static_cast<uint16_t>(sym->label().offset + sect->org);
uint16_t parentAddr = addr;
if (auto pos = sym->name.find('.'); pos != std::string::npos) {
std::string parentName = sym->name.substr(0, pos);
if (Symbol const *parentSym = sym_GetSymbol(parentName);
parentSym && parentSym->data.holds<Label>()) {
auto const &parentLabel = parentSym->label();
assume(parentLabel.section != nullptr);
parentAddr =
static_cast<uint16_t>(parentLabel.offset + parentLabel.section->org);
}
}
symList.push_back({.sym = sym, .addr = addr, .parentAddr = parentAddr});
}
}
});
@@ -477,7 +467,7 @@ static void writeMapBank(SortedSections const &sectList, SectionType type, uint3
for (uint16_t org = sect->org; sect; sect = sect->nextu.get()) {
for (Symbol *sym : sect->symbols) {
// Don't output symbols that begin with an illegal character
if (isLegalSymbol(*sym)) {
if (!sym->name.empty() && startsIdentifier(sym->name[0])) {
// Space matches "\tSECTION: $xxxx ..."
fprintf(mapFile, "\t $%04" PRIx32 " = ", sym->label().offset + org);
printSymName(sym->name, mapFile);

View File

@@ -251,10 +251,16 @@ yy::parser::symbol_type yylex() {
if (c == EOF) {
// Basically yywrap().
if (lexerStack.size() != 1) {
if (!atEof) {
// Inject a newline at EOF to simplify parsing.
atEof = true;
return yy::parser::make_newline();
} else {
lexerStack.pop_back();
return yylex();
}
} else if (!atEof) {
// Inject a newline at EOF, to avoid errors for files that don't end with one.
// Inject a newline at EOF to simplify parsing.
atEof = true;
return yy::parser::make_newline();
} else {

View File

@@ -6,6 +6,15 @@
#include <stdint.h>
#include <stdio.h>
bool startsIdentifier(int c) {
// This returns false for anonymous labels, which internally start with a '!'
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '.' || c == '_';
}
bool continuesIdentifier(int c) {
return startsIdentifier(c) || (c >= '0' && c <= '9') || c == '#' || c == '$' || c == '@';
}
char const *printChar(int c) {
// "'A'" + '\0': 4 bytes
// "'\\n'" + '\0': 5 bytes

23
test/asm/charval.asm Normal file
View File

@@ -0,0 +1,23 @@
charmap "a", 1
charmap "b", 2, 3
charmap "cdef", 4
charmap "ghi", 5, 6, 7, 8, 9
charmap "jkl", 123, 456, 789
charmap "mno", 123456789
assert charval("a", 0) == 1
assert charval("a", -1) == 1
assert charval("b", 0) == 2
assert charval("b", 1) == 3
assert charval("b", -1) == 3
assert charval("b", -2) == 2
assert charval("cdef", 0) == 4
assert charval("ghi", 2) == charval("ghi", -3)
assert charval("jkl", -1) == 789
assert charval("mno", 0) == 123456789
assert charval("abc", 0) == 0
assert charval("cd", 1) == 0
assert charval("xyz", 2) == 0
assert charval("ghi", -10) == 5
assert charval("ghi", 10) == 0

11
test/asm/charval.err Normal file
View File

@@ -0,0 +1,11 @@
error: charval.asm(19):
CHARVAL: No character mapping for "abc"
error: charval.asm(20):
CHARVAL: No character mapping for "cd"
error: charval.asm(21):
CHARVAL: No character mapping for "xyz"
warning: charval.asm(22): [-Wbuiltin-args]
CHARVAL: Index starts at 0
warning: charval.asm(23): [-Wbuiltin-args]
CHARVAL: Index 10 is past the end of the character mapping
error: Assembly aborted (3 errors)!

View File

@@ -1,3 +1,4 @@
error: command-line-symbols.asm(3):
'FOO' already defined at <command-line>
(should it be {interpolated} to define its contents "hello"?)
error: Assembly aborted (1 error)!

View File

@@ -28,3 +28,15 @@ redef string equs "there"
redef constant equ 6*9
println constant
macro mac
endm
redef mac equ 42
def name equs "constant2"
def name equ 1337
redef name equs "mac2"
macro name
endm

View File

@@ -1,3 +1,11 @@
error: def.asm(23):
'constant' already defined at def.asm(10)
error: Assembly aborted (1 error)!
error: def.asm(35):
'mac' already defined as non-EQU at def.asm(32)
error: def.asm(38):
'name' already defined at def.asm(37)
(should it be {interpolated} to define its contents "constant2"?)
error: def.asm(42):
'name' already defined at def.asm(40)
(should it be {interpolated} to define its contents "mac2"?)
error: Assembly aborted (4 errors)!

View File

@@ -3,6 +3,5 @@ warning: equs-newline.asm(3): [-Wuser]
while expanding symbol "ACT"
warning: equs-newline.asm(3): [-Wuser]
Second
while expanding symbol "ACT"
warning: equs-newline.asm(4): [-Wuser]
Third

View File

@@ -2,7 +2,7 @@
println 42, 1 2 3 4
for n, 5
for n, 3
println "start {d:n}"
println syntax error
println "finish {d:n}"

View File

@@ -2,4 +2,8 @@ error: error-recovery.asm(3):
syntax error, unexpected number
error: error-recovery.asm(5) -> error-recovery.asm::REPT~1(7):
syntax error, unexpected symbol
error: Assembly aborted (2 errors)!
error: error-recovery.asm(5) -> error-recovery.asm::REPT~2(7):
syntax error, unexpected symbol
error: error-recovery.asm(5) -> error-recovery.asm::REPT~3(7):
syntax error, unexpected symbol
error: Assembly aborted (4 errors)!

View File

@@ -1,4 +1,8 @@
begin
$2Astart 0
finish 0
end 0
start 1
finish 1
start 2
finish 2
end 3

View File

@@ -6,6 +6,7 @@ warning: for.asm(20): [-Wbackwards-for]
FOR goes backwards from 1 to 2 by -1
error: for.asm(46):
's' already defined as constant at for.asm(39)
(should it be {interpolated} to define its contents "x"?)
error: for.asm(48) -> for.asm::REPT~4(54):
'v' already defined as constant at for.asm(48) -> for.asm::REPT~4(52)
FATAL: for.asm(48) -> for.asm::REPT~4(54):

View File

@@ -1,5 +1,13 @@
opt b123
opt b_1
opt b10
opt b00
opt g12345
opt g012_
opt g$123
opt g0234
opt gxxyy
opt gxyzy
opt pxy
opt p1234
opt Qxy

View File

@@ -1,23 +1,39 @@
error: invalid-opt.asm(1):
Must specify exactly 2 characters for option 'b'
error: invalid-opt.asm(2):
Must specify exactly 4 characters for option 'g'
Invalid digit for binary constant '_'
error: invalid-opt.asm(3):
Invalid argument for option 'p'
Changed digit for binary constant '1'
error: invalid-opt.asm(4):
Invalid argument for option 'p'
Repeated digit for binary constant '0'
error: invalid-opt.asm(5):
Invalid argument for option 'Q'
Must specify exactly 4 characters for option 'g'
error: invalid-opt.asm(6):
Invalid argument for option 'Q'
Invalid digit for graphics constant '_'
error: invalid-opt.asm(7):
Argument for option 'Q' must be between 1 and 31
Invalid digit for graphics constant '$'
error: invalid-opt.asm(8):
Argument to 'r' is out of range ("99999999999999999999999999")
Changed digit for graphics constant '2'
error: invalid-opt.asm(9):
Must specify an argument for option 'W'
Repeated digit for graphics constant 'x'
error: invalid-opt.asm(10):
syntax error, unexpected end of line, expecting string
Repeated digit for graphics constant 'y'
error: invalid-opt.asm(11):
Invalid argument for option 'p'
error: invalid-opt.asm(12):
Invalid argument for option 'p'
error: invalid-opt.asm(13):
Invalid argument for option 'Q'
error: invalid-opt.asm(14):
Invalid argument for option 'Q'
error: invalid-opt.asm(15):
Argument for option 'Q' must be between 1 and 31
error: invalid-opt.asm(16):
Argument to 'r' is out of range ("99999999999999999999999999")
error: invalid-opt.asm(17):
Must specify an argument for option 'W'
error: invalid-opt.asm(18):
syntax error, unexpected end of line, expecting string
error: invalid-opt.asm(19):
No entries in the option stack
error: Assembly aborted (11 errors)!
error: Assembly aborted (19 errors)!

View File

@@ -1,7 +1,7 @@
SECTION "test", ROM0
; '\0' is not special here; it's lexed as a line continuation...
DEF foo\0bar EQU 42
db foo\0bar
DEF foo\0qux EQU 42
db foo\0qux
; ...just like any other non-whitespace character
DEF spam\Xeggs EQU 69
db spam\Xeggs

View File

@@ -1,4 +1,4 @@
PRINTLN `pqpq_rsrs
OPT g.x0X
PRINTLN `.x.x_0X0X
OPT g.xOX
PRINTLN `.x.x_OXOX

View File

@@ -1,3 +1,4 @@
error: redef-equ.asm(25):
'N' already defined as non-EQU at redef-equ.asm(24)
(should it be {interpolated} to define its contents "X"?)
error: Assembly aborted (1 error)!

View File

@@ -0,0 +1,26 @@
SECTION "test", ROM0
MACRO test
assert !strcmp(\1, \2)
ENDM
test "a"++"b", "ab"
test "a"++""++"b", "ab"
test "a"++"b", strcat("a", "b")
test "a"++"b"++"c", strcat("a","b","c")
test "" ++ "", ""
test strupr("a") ++ strlwr("B"), "Ab"
def str equs "hi"
test #str ++ strupr(#str), "hiHI"
test "a" ++ """b""" ++ strupr("c") ++ strslice(#str, 0, 0), "abC"
charmap "a", 1
charmap "b", 2
charmap "ab", 12
assert "a" + "b" == 3
assert "a" ++ "b" == 12
; errors
assert 2 ++ 2 == 4
ld a, [hl++]

View File

@@ -0,0 +1,5 @@
error: string-concat.asm(25):
syntax error, unexpected ++
error: string-concat.asm(26):
syntax error, unexpected ++, expecting ] or + or -
error: Assembly aborted (2 errors)!

View File

@@ -5,10 +5,10 @@ STRSLICE("ABC",-3,-2): A
STRSLICE("ABC",-2,-1): B
STRSLICE("ABC",-1,-0):
STRSLICE("ABC",-1,3): C
STRSLICE("ABC",1): B
STRSLICE("ABC",-2): B
STRSLICE("ABC",1): BC
STRSLICE("ABC",-2): BC
STRSLICE("ABC",4):
STRSLICE("ABC",-4): AB
STRSLICE("ABC",-4): ABC
STRSLICE("ABC",0,2): AB
STRSLICE("ABC",1,3): BC
STRSLICE("ABC",1,31): BC

View File

@@ -22,7 +22,7 @@ _1234::
dl 6_._283_185 ; fixed point
dw `0123_3210, `00_33_22_11_ ; gfx
; underscores as digits
opt g_ABC, b_X
db %_X_X__XX
dw `_A_B_C__
; underscores with custom digits
opt g.ABC, b.X
db %.X.X_..XX_
dw `.A.B_.C.._

View File

@@ -3,41 +3,30 @@ error: unique-id.asm(11):
while expanding symbol "warn_unique"
warning: unique-id.asm(11): [-Wuser]
!
while expanding symbol "warn_unique"
warning: unique-id.asm(12) -> unique-id.asm::m(4): [-Wuser]
_u1!
while expanding symbol "warn_unique"
warning: unique-id.asm(12) -> unique-id.asm::m(5) -> unique-id.asm::m::REPT~1(6): [-Wuser]
_u2!
while expanding symbol "warn_unique"
warning: unique-id.asm(12) -> unique-id.asm::m(5) -> unique-id.asm::m::REPT~2(6): [-Wuser]
_u3!
while expanding symbol "warn_unique"
warning: unique-id.asm(12) -> unique-id.asm::m(8): [-Wuser]
_u1!
while expanding symbol "warn_unique"
error: unique-id.asm(13):
'\@' cannot be used outside of a macro or REPT/FOR block
while expanding symbol "warn_unique"
warning: unique-id.asm(13): [-Wuser]
!
while expanding symbol "warn_unique"
warning: unique-id.asm(14) -> unique-id.asm::m(4): [-Wuser]
_u4!
while expanding symbol "warn_unique"
warning: unique-id.asm(14) -> unique-id.asm::m(5) -> unique-id.asm::m::REPT~1(6): [-Wuser]
_u5!
while expanding symbol "warn_unique"
warning: unique-id.asm(14) -> unique-id.asm::m(5) -> unique-id.asm::m::REPT~2(6): [-Wuser]
_u6!
while expanding symbol "warn_unique"
warning: unique-id.asm(14) -> unique-id.asm::m(8): [-Wuser]
_u4!
while expanding symbol "warn_unique"
error: unique-id.asm(15):
'\@' cannot be used outside of a macro or REPT/FOR block
while expanding symbol "warn_unique"
warning: unique-id.asm(15): [-Wuser]
!
while expanding symbol "warn_unique"
error: Assembly aborted (3 errors)!

View File

@@ -107,10 +107,10 @@ if ! "$external"; then
fi
if "$nonfree"; then
action pret pokecrystal 2025-04-28 f480cb9e247a63e95aa353daef3ecd44fd526659
action pret pokered 2025-04-19 d47f74ee1fed608f902dbc0fd51634dd7bafc6e4
action zladx LADX-Disassembly 2025-04-12 8db9dbbd1dcbbdb4df7c455f56d0ad1fa52b124b
action pret pokecrystal 2025-06-29 de396249e10eb294f41afd0e82245a4a68a3c8e8
action pret pokered 2025-06-29 1e997474be15950eb3176864b346b96504760e67
action zladx LADX-Disassembly 2025-05-30 f685f6aaff2f2e0e36d1856d4ed8fd58f833a1f2
fi
action AntonioND ucity 2025-03-20 04cdda3dda35e62501cf9543b73343935039ee4d
action pinobatch libbet 2025-03-28 c564dc01baa91bd6123ec64d877630dd392e74b5
action LIJI32 SameBoy 2025-04-07 1cf84a5436c49413ae756c9e9c18ce71e96d7990
action AntonioND ucity 2025-05-30 83e5c697cbd9e10a0bc72b02bcb6146c35e2c328
action pinobatch libbet 2025-05-20 bb6cfc026644aa1034eee6d9c49bb4705601c9f6
action LIJI32 SameBoy 2025-06-28 33d237706e18d92fb79e3fd7313d5181d8a806cd

Binary file not shown.

View File

@@ -0,0 +1,2 @@
-d 1
-c dmg=1b

2
test/gfx/dmg_2bit.flags Normal file
View File

@@ -0,0 +1,2 @@
-c nonexistent.gpl
-c DMG=E4

BIN
test/gfx/dmg_2bit.out.2bpp Normal file

Binary file not shown.

BIN
test/gfx/dmg_2bit.out.pal Normal file

Binary file not shown.

BIN
test/gfx/dmg_2bit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

2
test/gfx/dmg_plte.flags Normal file
View File

@@ -0,0 +1,2 @@
-c embedded
-c DMG=E4

BIN
test/gfx/dmg_plte.out.2bpp Normal file

Binary file not shown.

BIN
test/gfx/dmg_plte.out.pal Normal file

Binary file not shown.

BIN
test/gfx/dmg_plte.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 B

Binary file not shown.

View File

@@ -0,0 +1 @@
-c dmg=72

View File

@@ -0,0 +1,3 @@
section "a", rom0
export def parent = 42
db 1, 2, 3

View File

@@ -0,0 +1,4 @@
section "b", rom0
db 4, 5, 6
parent.child::
db 7, 8, 9

View File

View File

@@ -0,0 +1 @@
 

View File

@@ -0,0 +1,3 @@
; File generated by rgblink
00:0003 parent.child
2a parent

View File

@@ -2,8 +2,8 @@
00:0000 Part1
00:0004 Part1End
00:0004 Part2
00:0010 Part3
00:0010 Part2End
00:0010 Part3
00:0014 Part3End
00:c000 wPart1
00:c004 wPart3

View File

@@ -0,0 +1,2 @@
section "a", rom0
db $11

View File

@@ -0,0 +1,3 @@
rom0
org 0
"a" ; no newline

View File

@@ -0,0 +1,2 @@
section "b", rom0
db $22

View File

@@ -0,0 +1,3 @@
rom0
org 1
"b" ; yes newline

View File

View File

@@ -0,0 +1 @@
"

View File

@@ -0,0 +1,2 @@
include "script-include/a.inc"
include "script-include/b.inc"

View File

@@ -1,16 +1,16 @@
; File generated by rgblink
00:0000 Beta
00:0003 End
00:0003 Alpha
00:0003 End
00:0006 End
00:c000 wStart
00:c000 wStart
00:c000 wStart.word1
00:c000 wStart.long1
00:c000 wStart.word1
00:c002 wStart.word2
00:c004 wEnd
00:c004 wEnd
00:c004 wBeta
00:c007 wBeta.End
00:c004 wEnd
00:c004 wEnd
00:c007 wAlpha
00:c007 wBeta.End
00:c00a wAlpha.End

View File

@@ -149,6 +149,17 @@ rgblinkQuiet -o "$gbtemp" "$otemp" "$gbtemp2" 2>"$outtemp"
tryDiff "$test"/out.err "$outtemp"
evaluateTest
test="constant-parent"
startTest
"$RGBASM" -o "$otemp" "$test"/a.asm
"$RGBASM" -o "$gbtemp2" "$test"/b.asm
continueTest
rgblinkQuiet -o "$gbtemp" -n "$outtemp2" "$otemp" "$gbtemp2" 2>"$outtemp"
tryDiff "$test"/out.err "$outtemp"
tryDiff "$test"/ref.out.sym "$outtemp2"
tryCmpRom "$test"/ref.out.bin
evaluateTest
for test in fragment-align/*; do
startTest
"$RGBASM" -o "$otemp" "$test"/a.asm
@@ -251,6 +262,16 @@ tryDiff "$test"/out.err "$outtemp"
tryCmpRomSize "$gbtemp" 65536
evaluateTest
test="script-include"
startTest
"$RGBASM" -o "$otemp" "$test"/a.asm
"$RGBASM" -o "$gbtemp2" "$test"/b.asm
continueTest
rgblinkQuiet -o "$gbtemp" -l "$test"/script.link "$otemp" "$gbtemp2" 2>"$outtemp"
tryDiff "$test"/out.err "$outtemp"
tryCmpRom "$test"/ref.out.bin
evaluateTest
test="sdcc/good"
startTest
"$RGBASM" -o "$otemp" "$test"/a.asm