Compare commits

..

70 Commits

Author SHA1 Message Date
Rangi
830df360ae Specify that all .sh files need Unix line endings
The `git config --global --unset core.autocrlf` command
was failing in the "Create release artifacts" workflow,
so this is an alternate method of fixing issue #841.
2021-05-08 23:22:31 -04:00
Rangi
c75551b1b3 Release 0.5.1 2021-05-08 22:38:20 -04:00
Rangi
5a38f6e148 rgbasm supports "Q16.16" fixed-point literals
"Qm.n" signifies an m-bit two's complement integer
with n fractional bits.
<https://en.wikipedia.org/wiki/Q_(number_format)>
2021-05-06 23:17:08 -04:00
Rangi
869021f47d Fix -Wformat build warnings on macOS
C arithmetic promotes certain expressions to `int`,
so formatting has to use "%d" or "%x", not inttypes.h.

Fixes #883
2021-05-04 21:28:57 -04:00
Eldred Habert
c06985a7ad Fix incorrect lexing of "$ff00+c" (#882)
Fixes #881 by moving the task from the lexer to the parser.
This both alleviates the need for backtracking in the lexer,
removing what is (was) arguably a hack, and causes tokenization
boundaries to be properly respected, fixing the issue mentioned above.

Co-authored-by: Rangi <remy.oukaour+rangi42@gmail.com>
2021-05-05 02:04:19 +02:00
ISSOtm
c502804192 Update winflexbison to 2.5.24
Updates Bison to 3.7.4, removing support for VS2015
https://github.com/lexxmark/winflexbison/releases/tag/v2.5.24
2021-05-05 01:57:16 +02:00
ISSOtm
75f1bcde31 Make SECTION size overflow non-fatal
Fixes #538
2021-05-04 15:34:20 +02:00
ISSOtm
60b85298a9 Fix all memory leaks in RGBLINK
At least all that were reported in the tests.
Partial fix for #709, that only leaves RGBASM to be fixed... oh boy!
2021-05-03 12:50:14 +02:00
ISSOtm
8bbafb7200 Rename out_ functions in section.c to sect_
More consistent with convention used everywhere, and makes it easier to
know which file the functions come from.
2021-05-03 12:22:14 +02:00
ISSOtm
75ce230dce Make UNION-related errors non-fatal 2021-05-03 10:57:14 +02:00
ISSOtm
1d01268249 Remove LOAD FRAGMENT
It's very troublesome, with flaky semantics and a very restricted use space.
2021-05-03 10:51:55 +02:00
ISSOtm
02cb5a0526 Avoid performing invalid actions on LOAD errors
These are rejected because they could lead to incorrect behavior,
so then don't do it...
2021-05-03 10:46:52 +02:00
Rangi
8397b3d8ec .sym file sorts symbols from zero-length sections first
This will, for instance, ensure that a zero-length
`SECTION "NULL", ROM0[0] / NULL::` comes first.
2021-05-02 17:57:20 -04:00
Rangi
296e5489c9 rgblink adjusts patches' PC offsets when merging FRAGMENTs
Fixes #869
2021-05-02 23:54:42 +02:00
Rangi
9ab9d0f39c Output all SECTION UNION/FRAGMENT symbols in .sym files
Fixes #809
2021-05-02 23:51:17 +02:00
ISSOtm
6e1a5dcc9d Add TPP1 support
Fixes #846
2021-05-02 19:09:53 +02:00
Jakub Kądziołka
d360d03403 Enable address sanitizer in develop builds (#834)
Co-authored-by: ISSOtm <eldredhabert0@gmail.com>
2021-05-02 18:08:03 +02:00
Rangi
e9bfe849ad Allow OPT to toggle -h 2021-05-02 11:06:53 +02:00
Rangi
665eb916a2 OPT L acts like -L and *dis*ables optimizing LD to LDH
Fixes #867
2021-05-02 11:06:53 +02:00
Rangi
04788e15af Fix a potential out-of-bounds array access in RGBGFX
This was caught by ASAN for pokered's gfx/battle/minimize.png.
2021-05-01 22:33:54 -04:00
ISSOtm
dcb8c69661 Fix UAF in lexer capture
Fixes #689
2021-05-02 03:24:18 +02:00
ISSOtm
cc6b70f1d5 Add option to list accepted MBC names and clarify man page
Referring to "Pan Docs names" skims over a lot of details.
Add `-m help` to list accepted names for clarity
2021-05-02 00:39:42 +02:00
ISSOtm
38a9a613da Make data output outside of a SECTION non-fatal 2021-05-01 23:48:48 +02:00
ISSOtm
ad9a766a56 Allow dollar-prefixed hex for RGBFIX -m
Fixes #872
2021-05-01 23:48:23 +02:00
Eldred Habert
21b59c4651 Reinstate PUSHS clearing the SECTION scope (#870)
* Reinstate PUSHS clearing the SECTION scope

Otherwise you can use `PUSHS` to simulate the old `ds -21`, and possibly cause bugs

* Have PUSHS push LOAD block state as well

It does not make sense not to, and coud cause bugs.
2021-05-01 23:30:09 +02:00
ISSOtm
3ffdd50909 Test that RGBFIX does nothing when given no flags 2021-05-01 14:16:45 +02:00
Rangi
ca36422ac9 Parse 'ld hl, sp - <e8>' correctly
Fixes #864
2021-05-01 11:08:01 +02:00
Rangi
8e4ba8d2e4 Allow REDEF for EQU constants
Fixes #853
2021-04-29 12:24:07 +02:00
Rangi
ee67f1039c Fix REDEF EQUS behavior
Redefining an EQUS constant will now update its filename,
like redefining a SET/= constant.

Attempting to redefine as EQUS a non-EQUS constant will
print an appropriate error message.

This also standardizes on `sym` versus `symbol` as a
variable name (which pairs with `symName`).
2021-04-28 12:11:26 -04:00
Rangi
d37aa93a7d Port some cleanup from the WIP 'strings' branch
This is mostly variable renaming
2021-04-28 11:58:56 -04:00
Rangi
bba532193b Port some cleanup from PR #847
- Update some whitespace formatting
- Factor out some functions
- Free data after outputting to an object file
2021-04-28 11:58:56 -04:00
GreenAndEievui
b4814b06b9 Updated RGBFIX to report when non-zero bytes are overwritten
Also updated many .err files with the new warning.
2021-04-28 11:57:43 -04:00
Rangi
4ee2eb845b Clone test repositories with earlier --shallow-since dates
The previous dates were not reliably downloading the
necessary commit IDs for checkout to work.
2021-04-27 17:27:23 -04:00
Rangi
3fdf01c0f5 Resolve some TODO comments
- `out_PushSection` should not set `currentSection` to NULL because
  PUSHS, PUSHC, and PUSHO consistently keep the current section,
  charmap, and options, even though the stack has been pushed.

- `Callback__FILE__` does not need to assert that `fileName` is not
  empty because `__FILE__`'s value is quoted, and can safely be empty.

- `YY_FATAL_ERROR` and `YYLMAX` are not needed since the lexer is
  not generated with flex.
2021-04-26 15:52:30 -04:00
Rangi
1949a61c6f Tested the ctz and clz shim functions
Their values match the GCC builtins for all
4,294,967,295 nonzero inputs.
2021-04-26 12:17:34 -04:00
Rangi
43cf20b155 Support Mac OS classic CR line endings in linkerscripts
This also refactors `readChar(file)` to `nextChar()` to be
more like the rgbasm lexer.
2021-04-26 12:05:36 -04:00
Rangi
e27a6d53a0 Support character escapes in linkerscript strings
This allows linkerscripts to refer to section names even if
they contain special characters: '\r' '\n' '\t' '"' '\\'.
2021-04-26 12:05:36 -04:00
Rangi
d17e9c663e Update the tested commits of pokecrystal, pokered, and ucity (#859)
These are the latest commits as of today. The pokecrystal and
pokered commits use rgbds 0.5.0 features; ucity does not but
is compatible with it.
2021-04-26 10:47:32 -04:00
ISSOtm
dd8f396227 Fix compiler warnings
As reported in #789
2021-04-25 20:40:11 +02:00
ISSOtm
b60853ea21 Fix RGBFIX option parsing on platforms with unsigned char
Such as Termux, once again.
2021-04-25 11:05:34 +02:00
ISSOtm
b936ca27ab Enable _ISOC11_SOURCE
See #789
2021-04-23 17:07:52 +02:00
Rangi
e050803ed1 Use size_t for measuring nested depths
Multiple functions involve tracking the current depth
of a nested structure (symbol expansions, interpolations,
REPT/FOR blocks, parentheses).
2021-04-23 14:28:10 +02:00
Rangi
27f38770d4 Parentheses in macro args prevent commas from starting new arguments
This is similar to C's behavior, and convenient for passing
function calls as single values, like `MUL(3.0, 4.0)` or
`STRSUB("str", 2, 1)`.

Fixes #704
2021-04-23 14:28:10 +02:00
ISSOtm
db1f77f90b Correct "| operator" line not including the pipe 2021-04-23 14:24:53 +02:00
Rangi
4d21588eb2 Make invalid UTF-8 characters in strings non-fatal
STRLEN and STRSUB report the erroneous bytes

Fixes #848
2021-04-22 09:59:02 +02:00
Rangi
e596dbfc80 Make failed macro arg expansions non-fatal
Expanding empty strings is valid but pointless;
macro args already skipped doing so, now other
`beginExpansion` calls do too.

This also fixes failed interpolations (which were
already non-fatal) to continue reading characters,
not evaluate to their initial '{' character.
2021-04-22 09:59:02 +02:00
Rangi
1aeaca2af6 Add test case sort-algorithms.asm
This combines 0.5.0 and post-0.5.0 features:
print and println, strfmt, for loops,
def assignments, redef equs, {interpolation},
new macro syntax, and \<bracketed macro args>
2021-04-20 22:36:56 -04:00
Rangi
267e4bc25c rgbds.7(7) shows an example of piping rgbasm to rgblink to rgbfix
This uses one line instead of three
2021-04-20 22:06:02 -04:00
Rangi
c3e27217dd More specific "Symbol name too long" error messages
Identifiers, {interpolations} and \<macroArgs> are distinct
2021-04-20 17:14:21 +02:00
Rangi
fe3521c7a4 Switch from parentheses to angle brackets
`\(` is more likely to be a valid escape sequence in the
future (as is `\[`) and `\{` is already taken.
2021-04-20 17:14:21 +02:00
Rangi
b0f8d75d1d Shorten quine.asm with \(parenthesized) macro args 2021-04-20 17:14:21 +02:00
Rangi
7a314e7aff Support numeric symbol names in \(parentheses)
For example, \(_NARG) will get the last argument
2021-04-20 17:14:21 +02:00
Rangi
637bbbdf43 Support multi-digit macro arguments in parentheses
This allows access to arguments past \9 without using 'shift'
2021-04-20 17:14:21 +02:00
Rangi
8230e8165c Eliminate isAtEOF by changing yylex control flow
`yylex` calls `yywrap` at the beginning of the next call, after it
has set `lexerState->lastToken` to `T_EOB`.
2021-04-20 17:10:08 +02:00
Rangi
a727a0f81f Capture termination status is equivalent to not having reached EOF
This avoids the need for a separate `terminated` flag
2021-04-20 17:10:08 +02:00
Rangi
7a587eb7d6 Use midrule action values for captures' terminated status
Bison 3.1 introduces "typed midrule values", which would write
`<captureTerminated>{ ... }` and `$$` instead of `{ ... }` and
`$<captureTerminated>[1-9]`, but rgbds supports 3.0 or even lower.
2021-04-20 17:10:08 +02:00
Rangi
7ac8bd6e24 Return a marker token at the end of any buffer
Removes the lexer hack mentioned in #778
2021-04-20 17:10:08 +02:00
Rangi
be2572edca Track nested interpolation depth even outside string literals
Fixes #837
2021-04-20 09:37:29 -04:00
Rangi
cf2bbe6435 Position -1 is the last character of a string
Position 0 is invalid, which matches with STRIN/STRRIN
returning 0 on failure.
2021-04-20 14:27:59 +02:00
Rangi
dc5b7802c8 Make the len parameter optional in STRSUB(str, pos, len)
An unspecified length will continue to the end of the string.
2021-04-20 14:27:59 +02:00
Rangi
b1e6c73197 STRSUB and CHARSUB allow zero or negative positions
These are offsets from the end of the string, as if the
STRLEN or CHARLEN respectively were added to the position.

Fixes #812
2021-04-20 14:27:59 +02:00
Rangi
459773b3f0 Update some whitespace after Hungarian prefixes were removed
Keep the parameter alignment and 100-char line limit
2021-04-19 16:47:39 -04:00
ISSOtm
6d0a3c75e9 Get rid of Hungarian notation for good
Bye bye it was not nice knowing ya
2021-04-19 22:12:10 +02:00
ISSOtm
e35585960c Avoid generating CRLF'd release tarballs
Fixes #841
2021-04-18 23:19:22 +02:00
Rangi
3bea7930a9 Only update documentation for gbdev/rgbds
Prevent the relevant GitHub Actions from running on forks
2021-04-18 16:16:17 -04:00
Rangi
52797b6f68 Implement SIZEOF("Section") and STARTOF("Section") (#766)
Updates the object file revision to 8

Fixes #765
2021-04-17 18:36:26 -04:00
Rangi
5108c5643c Let charmap_ConvertNext advance its output pointer 2021-04-17 18:18:34 -04:00
Rangi
2005ed1df9 Implement CHARLEN and CHARSUB
Fixes #786
2021-04-17 18:18:34 -04:00
Rangi
d43408f4f3 Allow OPT to modify -W
Warning flags are processed individually;
PUSHO and POPO (re)store all the warning states.
2021-04-18 00:11:18 +02:00
Rangi
2c30ab8731 Allow OPT to modify -L
-L is a Boolean flag option, so you specify 'OPT L' or 'OPT !L'.
2021-04-18 00:11:18 +02:00
216 changed files with 2972 additions and 1480 deletions

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
# Shell scripts need Unix line endings (see https://github.com/gbdev/rgbds/issues/841)
*.sh text eol=lf

View File

@@ -29,9 +29,9 @@ jobs:
Write-Host "libpng SHA256 mismatch! ($hash)" Write-Host "libpng SHA256 mismatch! ($hash)"
exit 1 exit 1
} }
$wc.DownloadFile('https://github.com/lexxmark/winflexbison/releases/download/v2.5.23/win_flex_bison-2.5.23.zip', 'winflexbison.zip') $wc.DownloadFile('https://github.com/lexxmark/winflexbison/releases/download/v2.5.24/win_flex_bison-2.5.24.zip', 'winflexbison.zip')
$hash = (Get-FileHash "winflexbison.zip" -Algorithm SHA256).Hash $hash = (Get-FileHash "winflexbison.zip" -Algorithm SHA256).Hash
if ($hash -ne '6AA5C8EA662DA1550020A5804C28BE63FFAA53486DA9F6842E24C379EC422DFC') { if ($hash -ne '39c6086ce211d5415500acc5ed2d8939861ca1696aee48909c7f6daf5122b505') {
Write-Host "bison SHA256 mismatch! ($hash)" Write-Host "bison SHA256 mismatch! ($hash)"
} }
Expand-Archive -DestinationPath . "zlib.zip" Expand-Archive -DestinationPath . "zlib.zip"

View File

@@ -6,6 +6,7 @@ on:
jobs: jobs:
build: build:
if: github.repository_owner == 'gbdev'
runs-on: ubuntu-18.04 runs-on: ubuntu-18.04
steps: steps:
- name: Checkout rgbds@release - name: Checkout rgbds@release

View File

@@ -88,9 +88,9 @@ jobs:
Write-Host "libpng SHA256 mismatch! ($hash)" Write-Host "libpng SHA256 mismatch! ($hash)"
exit 1 exit 1
} }
$wc.DownloadFile('https://github.com/lexxmark/winflexbison/releases/download/v2.5.23/win_flex_bison-2.5.23.zip', 'winflexbison.zip') $wc.DownloadFile('https://github.com/lexxmark/winflexbison/releases/download/v2.5.24/win_flex_bison-2.5.24.zip', 'winflexbison.zip')
$hash = (Get-FileHash "winflexbison.zip" -Algorithm SHA256).Hash $hash = (Get-FileHash "winflexbison.zip" -Algorithm SHA256).Hash
if ($hash -ne '6AA5C8EA662DA1550020A5804C28BE63FFAA53486DA9F6842E24C379EC422DFC') { if ($hash -ne '39c6086ce211d5415500acc5ed2d8939861ca1696aee48909c7f6daf5122b505') {
Write-Host "bison SHA256 mismatch! ($hash)" Write-Host "bison SHA256 mismatch! ($hash)"
} }
Expand-Archive -DestinationPath . "zlib.zip" Expand-Archive -DestinationPath . "zlib.zip"

View File

@@ -17,6 +17,7 @@ on:
jobs: jobs:
build: build:
if: github.repository_owner == 'gbdev'
runs-on: ubuntu-18.04 runs-on: ubuntu-18.04
steps: steps:
- name: Checkout rgbds@master - name: Checkout rgbds@master

View File

@@ -35,12 +35,13 @@ if(MSVC)
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)
add_definitions(-D_POSIX_C_SOURCE=200809L -D_ISOC11_SOURCE)
if(SANITIZERS) if(SANITIZERS)
set(SAN_FLAGS -fsanitize=shift -fsanitize=integer-divide-by-zero set(SAN_FLAGS -fsanitize=shift -fsanitize=integer-divide-by-zero
-fsanitize=unreachable -fsanitize=vla-bound -fsanitize=unreachable -fsanitize=vla-bound
-fsanitize=signed-integer-overflow -fsanitize=bounds -fsanitize=signed-integer-overflow -fsanitize=bounds
-fsanitize=object-size -fsanitize=bool -fsanitize=enum -fsanitize=object-size -fsanitize=bool -fsanitize=enum
-fsanitize=alignment -fsanitize=null) -fsanitize=alignment -fsanitize=null -fsanitize=address)
add_compile_options(${SAN_FLAGS}) add_compile_options(${SAN_FLAGS})
link_libraries(${SAN_FLAGS}) link_libraries(${SAN_FLAGS})
endif() endif()

View File

@@ -35,8 +35,9 @@ WARNFLAGS := -Wall
# Overridable CFLAGS # Overridable CFLAGS
CFLAGS ?= -O3 -flto -DNDEBUG CFLAGS ?= -O3 -flto -DNDEBUG
# Non-overridable CFLAGS # Non-overridable CFLAGS
REALCFLAGS := ${CFLAGS} ${WARNFLAGS} -std=gnu11 -D_POSIX_C_SOURCE=200809L \ # _ISOC11_SOURCE is required on certain platforms to get C11 on top of the C99-based POSIX 2008
-Iinclude REALCFLAGS := ${CFLAGS} ${WARNFLAGS} -std=gnu11 -I include \
-D_POSIX_C_SOURCE=200809L -D_ISOC11_SOURCE
# Overridable LDFLAGS # Overridable LDFLAGS
LDFLAGS ?= LDFLAGS ?=
# Non-overridable LDFLAGS # Non-overridable LDFLAGS
@@ -60,9 +61,9 @@ rgbasm_obj := \
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/parser.o \
src/asm/opt.o \ src/asm/opt.o \
src/asm/output.o \ src/asm/output.o \
src/asm/parser.o \
src/asm/rpn.o \ src/asm/rpn.o \
src/asm/section.o \ src/asm/section.o \
src/asm/symbol.o \ src/asm/symbol.o \
@@ -203,7 +204,7 @@ checkpatch:
# compilation and make the continous integration infrastructure return failure. # compilation and make the continous integration infrastructure return failure.
develop: develop:
$Qenv $(MAKE) -j WARNFLAGS="-Werror -Wall -Wextra -Wpedantic -Wno-type-limits \ $Qenv $(MAKE) 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=4 \ -Wuninitialized -Wunknown-pragmas -Wstrict-overflow=4 \
@@ -215,7 +216,8 @@ develop:
-fsanitize=unreachable -fsanitize=vla-bound \ -fsanitize=unreachable -fsanitize=vla-bound \
-fsanitize=signed-integer-overflow -fsanitize=bounds \ -fsanitize=signed-integer-overflow -fsanitize=bounds \
-fsanitize=object-size -fsanitize=bool -fsanitize=enum \ -fsanitize=object-size -fsanitize=bool -fsanitize=enum \
-fsanitize=alignment -fsanitize=null" CFLAGS="-ggdb3 -O0" -fsanitize=alignment -fsanitize=null -fsanitize=address" \
CFLAGS="-ggdb3 -O0"
# Targets for the project maintainer to easily create Windows exes. # Targets for the project maintainer to easily create Windows exes.
# This is not for Windows users! # This is not for Windows users!

View File

@@ -18,5 +18,6 @@ void charmap_Push(void);
void charmap_Pop(void); void charmap_Pop(void);
void charmap_Add(char *mapping, uint8_t value); void charmap_Add(char *mapping, uint8_t value);
size_t charmap_Convert(char const *input, uint8_t *output); size_t charmap_Convert(char const *input, uint8_t *output);
size_t charmap_ConvertNext(char const **input, uint8_t **output);
#endif /* RGBDS_ASM_CHARMAP_H */ #endif /* RGBDS_ASM_CHARMAP_H */

View File

@@ -9,8 +9,8 @@
#ifndef RGBDS_FORMAT_SPEC_H #ifndef RGBDS_FORMAT_SPEC_H
#define RGBDS_FORMAT_SPEC_H #define RGBDS_FORMAT_SPEC_H
#include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h>
enum FormatState { enum FormatState {
FORMAT_SIGN, // expects '+' or ' ' (optional) FORMAT_SIGN, // expects '+' or ' ' (optional)

View File

@@ -49,7 +49,7 @@ struct FileStackNamedNode { /* NODE_FILE, NODE_MACRO */
char name[]; /* File name for files, file::macro name for macros */ char name[]; /* File name for files, file::macro name for macros */
}; };
extern size_t nMaxRecursionDepth; extern size_t maxRecursionDepth;
struct MacroArgs; struct MacroArgs;
@@ -72,12 +72,12 @@ bool fstk_FindFile(char const *path, char **fullPath, size_t *size);
bool yywrap(void); 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 reptLineNo, char *body, size_t size);
void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step, void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step,
int32_t reptLineNo, char *body, size_t size); int32_t reptLineNo, char *body, size_t size);
void fstk_StopRept(void); void fstk_StopRept(void);
bool fstk_Break(void); bool fstk_Break(void);
void fstk_Init(char const *mainPath, size_t maxRecursionDepth); void fstk_Init(char const *mainPath, size_t maxDepth);
#endif /* RGBDS_ASM_FSTACK_H */ #endif /* RGBDS_ASM_FSTACK_H */

View File

@@ -53,7 +53,7 @@ static inline void lexer_SetGfxDigits(char const digits[4])
* `path` is referenced, but not held onto..! * `path` is referenced, but not held onto..!
*/ */
struct LexerState *lexer_OpenFile(char const *path); struct LexerState *lexer_OpenFile(char const *path);
struct LexerState *lexer_OpenFileView(char *buf, size_t size, uint32_t lineNo); struct LexerState *lexer_OpenFileView(char const *path, char *buf, size_t size, uint32_t lineNo);
void lexer_RestartRept(uint32_t lineNo); void lexer_RestartRept(uint32_t lineNo);
void lexer_DeleteState(struct LexerState *state); void lexer_DeleteState(struct LexerState *state);
void lexer_Init(void); void lexer_Init(void);
@@ -88,8 +88,8 @@ 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(struct CaptureBody *capture); bool lexer_CaptureRept(struct CaptureBody *capture);
void lexer_CaptureMacroBody(struct CaptureBody *capture); bool lexer_CaptureMacroBody(struct CaptureBody *capture);
#define INITIAL_DS_ARG_SIZE 2 #define INITIAL_DS_ARG_SIZE 2
struct DsArgList { struct DsArgList {

View File

@@ -16,22 +16,14 @@
#include "helpers.h" #include "helpers.h"
extern bool haltnop; extern bool haltnop;
extern bool optimizeloads; extern bool optimizeLoads;
extern bool verbose; extern bool verbose;
extern bool warnings; /* True to enable warnings, false to disable them. */ extern bool warnings; /* True to enable warnings, false to disable them. */
extern FILE *dependfile; extern FILE *dependfile;
extern char *tzTargetFileName; extern char *targetFileName;
extern bool oGeneratedMissingIncludes; extern bool generatedMissingIncludes;
extern bool oFailedOnMissingInclude; extern bool failedOnMissingInclude;
extern bool oGeneratePhonyDeps; extern bool generatePhonyDeps;
/* TODO: are these really needed? */
#define YY_FATAL_ERROR fatalerror
#ifdef YYLMAX
#undef YYLMAX
#endif
#define YYLMAX 65536
#endif /* RGBDS_MAIN_H */ #endif /* RGBDS_MAIN_H */

View File

@@ -9,14 +9,16 @@
#ifndef RGBDS_OPT_H #ifndef RGBDS_OPT_H
#define RGBDS_OPT_H #define RGBDS_OPT_H
#include <stdbool.h>
void opt_B(char chars[2]); void opt_B(char chars[2]);
void opt_G(char chars[4]); void opt_G(char chars[4]);
void opt_P(uint8_t fill); void opt_P(uint8_t fill);
void opt_L(bool optimize);
void opt_W(char const *flag);
void opt_Parse(char const *option); void opt_Parse(char const *option);
void opt_Push(void); void opt_Push(void);
void opt_Pop(void); void opt_Pop(void);
#endif #endif

View File

@@ -16,8 +16,8 @@
struct Expression; struct Expression;
struct FileStackNode; struct FileStackNode;
extern char *tzObjectname; extern char *objectName;
extern struct Section *pSectionList, *pCurrentSection; extern struct Section *sectionList;
void out_RegisterNode(struct FileStackNode *node); void out_RegisterNode(struct FileStackNode *node);
void out_ReplaceNode(struct FileStackNode *node); void out_ReplaceNode(struct FileStackNode *node);

View File

@@ -17,20 +17,20 @@
#define MAXRPNLEN 1048576 #define MAXRPNLEN 1048576
struct Expression { struct Expression {
int32_t nVal; // If the expression's value is known, it's here int32_t val; // If the expression's value is known, it's here
char *reason; // Why the expression is not known, if it isn't char *reason; // Why the expression is not known, if it isn't
bool isKnown; // Whether the expression's value is known bool isKnown; // Whether the expression's value is known
bool isSymbol; // Whether the expression represents a symbol bool isSymbol; // Whether the expression represents a symbol
uint8_t *tRPN; // Array of bytes serializing the RPN expression uint8_t *rpn; // Array of bytes serializing the RPN expression
uint32_t nRPNCapacity; // Size of the `tRPN` buffer uint32_t rpnCapacity; // Size of the `rpn` buffer
uint32_t nRPNLength; // Used size of the `tRPN` buffer uint32_t rpnLength; // Used size of the `rpn` buffer
uint32_t nRPNPatchSize; // Size the expression will take in the obj file uint32_t rpnPatchSize; // Size the expression will take in the obj file
}; };
/* /*
* Determines if an expression is known at assembly time * Determines if an expression is known at assembly time
*/ */
static inline bool rpn_isKnown(const struct Expression *expr) static inline bool rpn_isKnown(struct Expression const *expr)
{ {
return expr->isKnown; return expr->isKnown;
} }
@@ -43,11 +43,11 @@ static inline bool rpn_isSymbol(const struct Expression *expr)
return expr->isSymbol; return expr->isSymbol;
} }
void rpn_Symbol(struct Expression *expr, char const *tzSym); void rpn_Symbol(struct Expression *expr, char const *symName);
void rpn_Number(struct Expression *expr, uint32_t i); void rpn_Number(struct Expression *expr, uint32_t i);
void rpn_LOGNOT(struct Expression *expr, const struct Expression *src); void rpn_LOGNOT(struct Expression *expr, const struct Expression *src);
struct Symbol const *rpn_SymbolOf(struct Expression const *expr); struct Symbol const *rpn_SymbolOf(struct Expression const *expr);
bool rpn_IsDiffConstant(struct Expression const *src, struct Symbol const *sym); bool rpn_IsDiffConstant(struct Expression const *src, struct Symbol const *symName);
void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr, void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
const struct Expression *src1, const struct Expression *src1,
const struct Expression *src2); const struct Expression *src2);
@@ -56,11 +56,15 @@ void rpn_LOW(struct Expression *expr, const struct Expression *src);
void rpn_ISCONST(struct Expression *expr, const struct Expression *src); void rpn_ISCONST(struct Expression *expr, const struct Expression *src);
void rpn_UNNEG(struct Expression *expr, const struct Expression *src); void rpn_UNNEG(struct Expression *expr, const struct Expression *src);
void rpn_UNNOT(struct Expression *expr, const struct Expression *src); void rpn_UNNOT(struct Expression *expr, const struct Expression *src);
void rpn_BankSymbol(struct Expression *expr, char const *tzSym); void rpn_BankSymbol(struct Expression *expr, char const *symName);
void rpn_BankSection(struct Expression *expr, char const *tzSectionName); void rpn_BankSection(struct Expression *expr, char const *sectionName);
void rpn_BankSelf(struct Expression *expr); void rpn_BankSelf(struct Expression *expr);
void rpn_SizeOfSection(struct Expression *expr, char const *sectionName);
void rpn_StartOfSection(struct Expression *expr, char const *sectionName);
void rpn_Free(struct Expression *expr); void rpn_Free(struct Expression *expr);
void rpn_CheckHRAM(struct Expression *expr, const struct Expression *src); void rpn_CheckHRAM(struct Expression *expr, const struct Expression *src);
void rpn_CheckRST(struct Expression *expr, const struct Expression *src); void rpn_CheckRST(struct Expression *expr, const struct Expression *src);
void rpn_CheckNBit(struct Expression const *expr, uint8_t n);
int32_t rpn_GetConstVal(struct Expression const *expr);
#endif /* RGBDS_ASM_RPN_H */ #endif /* RGBDS_ASM_RPN_H */

View File

@@ -40,14 +40,14 @@ struct SectionSpec {
uint16_t alignOfs; uint16_t alignOfs;
}; };
struct Section *out_FindSectionByName(const char *name); extern struct Section *currentSection;
void out_NewSection(char const *name, uint32_t secttype, uint32_t org,
struct SectionSpec const *attributes, struct Section *sect_FindSectionByName(const char *name);
enum SectionModifier mod); void sect_NewSection(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, enum SectionModifier mod);
struct SectionSpec const *attributes, void sect_SetLoadSection(char const *name, uint32_t secttype, uint32_t org,
enum SectionModifier mod); struct SectionSpec const *attributes, enum SectionModifier mod);
void out_EndLoadSection(void); void sect_EndLoadSection(void);
struct Section *sect_GetSymbolSection(void); struct Section *sect_GetSymbolSection(void);
uint32_t sect_GetSymbolOffset(void); uint32_t sect_GetSymbolOffset(void);
@@ -59,21 +59,21 @@ void sect_NextUnionMember(void);
void sect_EndUnion(void); void sect_EndUnion(void);
void sect_CheckUnionClosed(void); void sect_CheckUnionClosed(void);
void out_AbsByte(uint8_t b); void sect_AbsByte(uint8_t b);
void out_AbsByteGroup(uint8_t const *s, int32_t length); void sect_AbsByteGroup(uint8_t const *s, int32_t length);
void out_AbsWordGroup(uint8_t const *s, int32_t length); void sect_AbsWordGroup(uint8_t const *s, int32_t length);
void out_AbsLongGroup(uint8_t const *s, int32_t length); void sect_AbsLongGroup(uint8_t const *s, int32_t length);
void out_Skip(int32_t skip, bool ds); void sect_Skip(int32_t skip, bool ds);
void out_String(char const *s); void sect_String(char const *s);
void out_RelByte(struct Expression *expr, uint32_t pcShift); void sect_RelByte(struct Expression *expr, uint32_t pcShift);
void out_RelBytes(uint32_t n, struct Expression *exprs, size_t size); void sect_RelBytes(uint32_t n, struct Expression *exprs, size_t size);
void out_RelWord(struct Expression *expr, uint32_t pcShift); void sect_RelWord(struct Expression *expr, uint32_t pcShift);
void out_RelLong(struct Expression *expr, uint32_t pcShift); void sect_RelLong(struct Expression *expr, uint32_t pcShift);
void out_PCRelByte(struct Expression *expr, uint32_t pcShift); void sect_PCRelByte(struct Expression *expr, uint32_t pcShift);
void out_BinaryFile(char const *s, int32_t startPos); void sect_BinaryFile(char const *s, int32_t startPos);
void out_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length); void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length);
void out_PushSection(void); void sect_PushSection(void);
void out_PopSection(void); void sect_PopSection(void);
#endif #endif

View File

@@ -45,13 +45,13 @@ struct Symbol {
/* If sym_IsNumeric */ /* If sym_IsNumeric */
int32_t value; int32_t value;
int32_t (*numCallback)(void); int32_t (*numCallback)(void);
/* For SYM_MACRO */ /* For SYM_MACRO and SYM_EQUS; TODO: have separate fields */
struct { struct {
size_t macroSize; size_t macroSize;
char *macro; char *macro;
}; };
/* For SYM_EQUS, TODO: separate "base" fields from SYM_MACRO */ /* For SYM_EQUS */
char const *(*strCallback)(void); /* For SYM_EQUS */ char const *(*strCallback)(void);
}; };
uint32_t ID; /* ID of the symbol in the object file (-1 if none) */ uint32_t ID; /* ID of the symbol in the object file (-1 if none) */
@@ -82,8 +82,7 @@ static inline bool sym_IsConstant(struct Symbol const *sym)
static inline bool sym_IsNumeric(struct Symbol const *sym) static inline bool sym_IsNumeric(struct Symbol const *sym)
{ {
return sym->type == SYM_LABEL || sym->type == SYM_EQU return sym->type == SYM_LABEL || sym->type == SYM_EQU || sym->type == SYM_SET;
|| sym->type == SYM_SET;
} }
static inline bool sym_IsLabel(struct Symbol const *sym) static inline bool sym_IsLabel(struct Symbol const *sym)
@@ -121,22 +120,23 @@ struct Symbol *sym_AddAnonLabel(void);
void sym_WriteAnonLabelName(char buf[MIN_NB_ELMS(MAXSYMLEN + 1)], uint32_t ofs, bool neg); 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_RedefEqu(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);
uint32_t sym_GetPCValue(void); uint32_t sym_GetPCValue(void);
uint32_t sym_GetConstantSymValue(struct Symbol const *sym); uint32_t sym_GetConstantSymValue(struct Symbol const *sym);
uint32_t sym_GetConstantValue(char const *s); uint32_t sym_GetConstantValue(char const *symName);
/* /*
* Find a symbol by exact name, bypassing expansion checks * Find a symbol by exact name, bypassing expansion checks
*/ */
struct Symbol *sym_FindExactSymbol(char const *name); struct Symbol *sym_FindExactSymbol(char const *symName);
/* /*
* Find a symbol by exact name; may not be scoped, produces an error if it is * Find a symbol by exact name; may not be scoped, produces an error if it is
*/ */
struct Symbol *sym_FindUnscopedSymbol(char const *name); struct Symbol *sym_FindUnscopedSymbol(char const *symName);
/* /*
* Find a symbol, possibly scoped, by name * Find a symbol, possibly scoped, by name
*/ */
struct Symbol *sym_FindScopedSymbol(char const *name); struct Symbol *sym_FindScopedSymbol(char const *symName);
struct Symbol const *sym_GetPC(void); 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);

View File

@@ -13,6 +13,13 @@
extern unsigned int nbErrors; extern unsigned int nbErrors;
enum WarningState {
WARNING_DEFAULT,
WARNING_DISABLED,
WARNING_ENABLED,
WARNING_ERROR
};
enum WarningID { enum WarningID {
WARNING_ASSERT, /* Assertions */ WARNING_ASSERT, /* Assertions */
WARNING_BACKWARDS_FOR, /* `for` loop with backwards range */ WARNING_BACKWARDS_FOR, /* `for` loop with backwards range */
@@ -43,6 +50,9 @@ enum WarningID {
#define NB_META_WARNINGS (NB_WARNINGS_ALL - NB_WARNINGS) #define NB_META_WARNINGS (NB_WARNINGS_ALL - NB_WARNINGS)
}; };
extern enum WarningState warningStates[NB_WARNINGS];
extern bool warningsAreErrors;
void processWarningFlag(char const *flag); void processWarningFlag(char const *flag);
/* /*

View File

@@ -18,6 +18,7 @@
#ifdef __GNUC__ // GCC or compatible #ifdef __GNUC__ // GCC or compatible
#define format_(archetype, str_index, first_arg) \ #define format_(archetype, str_index, first_arg) \
__attribute__ ((format (archetype, str_index, first_arg))) __attribute__ ((format (archetype, str_index, first_arg)))
#define attr_(...) __attribute__ ((__VA_ARGS__))
// In release builds, define "unreachable" as such, but trap in debug builds // In release builds, define "unreachable" as such, but trap in debug builds
#ifdef NDEBUG #ifdef NDEBUG
#define unreachable_ __builtin_unreachable #define unreachable_ __builtin_unreachable
@@ -27,6 +28,7 @@
#else #else
// Unsupported, but no need to throw a fit // Unsupported, but no need to throw a fit
#define format_(archetype, str_index, first_arg) #define format_(archetype, str_index, first_arg)
#define attr_(...)
// This seems to generate similar code to __builtin_unreachable, despite different semantics // This seems to generate similar code to __builtin_unreachable, despite different semantics
// Note that executing this is undefined behavior (declared _Noreturn, but does return) // Note that executing this is undefined behavior (declared _Noreturn, but does return)
static inline _Noreturn unreachable_(void) {} static inline _Noreturn unreachable_(void) {}
@@ -59,7 +61,6 @@
} }
#else #else
// FIXME: these are rarely used, and need testing...
#include <limits.h> #include <limits.h>
static inline int ctz(unsigned int x) static inline int ctz(unsigned int x)
{ {

View File

@@ -21,10 +21,7 @@ struct Assertion {
struct Patch patch; struct Patch patch;
// enum AssertionType type; The `patch`'s field is instead re-used // enum AssertionType type; The `patch`'s field is instead re-used
char *message; char *message;
/* // This would be redundant with `.section->fileSymbols`... but `section` is sometimes NULL!
* This would be redundant with `.section->fileSymbols`... but
* `section` is sometimes NULL!
*/
struct Symbol **fileSymbols; struct Symbol **fileSymbols;
struct Assertion *next; struct Assertion *next;

View File

@@ -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 7U #define RGBDS_OBJECT_REV 8U
enum AssertionType { enum AssertionType {
ASSERT_WARN, ASSERT_WARN,
@@ -28,17 +28,17 @@ enum RPNCommand {
RPN_MUL = 0x02, RPN_MUL = 0x02,
RPN_DIV = 0x03, RPN_DIV = 0x03,
RPN_MOD = 0x04, RPN_MOD = 0x04,
RPN_UNSUB = 0x05, RPN_UNSUB = 0x05, // FIXME: should be renamed to "NEG" for consistency
RPN_EXP = 0x06, RPN_EXP = 0x06,
RPN_OR = 0x10, RPN_OR = 0x10,
RPN_AND = 0x11, RPN_AND = 0x11,
RPN_XOR = 0x12, RPN_XOR = 0x12,
RPN_UNNOT = 0x13, RPN_UNNOT = 0x13, // FIXME: should be renamed to "NOT" for consistency
RPN_LOGAND = 0x21, RPN_LOGAND = 0x21,
RPN_LOGOR = 0x22, RPN_LOGOR = 0x22,
RPN_LOGUNNOT = 0x23, RPN_LOGUNNOT = 0x23, // FIXME: should be renamed to "LOGNOT" for consistency
RPN_LOGEQ = 0x30, RPN_LOGEQ = 0x30,
RPN_LOGNE = 0x31, RPN_LOGNE = 0x31,
@@ -53,6 +53,8 @@ enum RPNCommand {
RPN_BANK_SYM = 0x50, RPN_BANK_SYM = 0x50,
RPN_BANK_SECT = 0x51, RPN_BANK_SECT = 0x51,
RPN_BANK_SELF = 0x52, RPN_BANK_SELF = 0x52,
RPN_SIZEOF_SECT = 0x53,
RPN_STARTOF_SECT = 0x54,
RPN_HRAM = 0x60, RPN_HRAM = 0x60,
RPN_RST = 0x61, RPN_RST = 0x61,

View File

@@ -11,9 +11,10 @@
#ifndef RGBDS_PLATFORM_H #ifndef RGBDS_PLATFORM_H
#define RGBDS_PLATFORM_H #define RGBDS_PLATFORM_H
/* MSVC doesn't have strncasecmp, use a suitable replacement */ // MSVC doesn't have str(n)casecmp, use a suitable replacement
#ifdef _MSC_VER #ifdef _MSC_VER
# include <string.h> # include <string.h>
# define strcasecmp _stricmp
# define strncasecmp _strnicmp # define strncasecmp _strnicmp
#else #else
# include <strings.h> # include <strings.h>

View File

@@ -11,7 +11,7 @@
#define PACKAGE_VERSION_MAJOR 0 #define PACKAGE_VERSION_MAJOR 0
#define PACKAGE_VERSION_MINOR 5 #define PACKAGE_VERSION_MINOR 5
#define PACKAGE_VERSION_PATCH 0 #define PACKAGE_VERSION_PATCH 1
const char *get_package_version_string(void); const char *get_package_version_string(void);

View File

@@ -57,7 +57,7 @@ struct CharmapStackEntry {
struct CharmapStackEntry *charmapStack; struct CharmapStackEntry *charmapStack;
static struct Charmap *charmap_Get(const char *name) static struct Charmap *charmap_Get(char const *name)
{ {
return hash_GetElement(charmaps, name); return hash_GetElement(charmaps, name);
} }
@@ -192,6 +192,16 @@ void charmap_Add(char *mapping, uint8_t value)
} }
size_t charmap_Convert(char const *input, uint8_t *output) size_t charmap_Convert(char const *input, uint8_t *output)
{
uint8_t *start = output;
while (charmap_ConvertNext(&input, &output))
;
return output - start;
}
size_t charmap_ConvertNext(char const **input, uint8_t **output)
{ {
/* /*
* The goal is to match the longest mapping possible. * The goal is to match the longest mapping possible.
@@ -199,7 +209,6 @@ size_t charmap_Convert(char const *input, uint8_t *output)
* If that would lead to a dead end, rewind characters until the last match, and output. * If that would lead to a dead end, rewind characters until the last match, and output.
* If no match, read a UTF-8 codepoint and output that. * If no match, read a UTF-8 codepoint and output that.
*/ */
size_t outputLen = 0;
struct Charmap const *charmap = *currentCharmap; struct Charmap const *charmap = *currentCharmap;
struct Charnode const *node = &charmap->nodes[0]; struct Charnode const *node = &charmap->nodes[0];
struct Charnode const *match = NULL; struct Charnode const *match = NULL;
@@ -207,10 +216,10 @@ size_t charmap_Convert(char const *input, uint8_t *output)
for (;;) { for (;;) {
/* We still want NULs to reach the `else` path, to give a chance to rewind */ /* We still want NULs to reach the `else` path, to give a chance to rewind */
uint8_t c = *input - 1; uint8_t c = **input - 1;
if (*input && node->next[c]) { if (**input && node->next[c]) {
input++; /* Consume that char */ (*input)++; /* Consume that char */
rewindDistance++; rewindDistance++;
node = &charmap->nodes[node->next[c]]; node = &charmap->nodes[node->next[c]];
@@ -220,31 +229,35 @@ size_t charmap_Convert(char const *input, uint8_t *output)
} }
} else { } else {
input -= rewindDistance; /* Rewind */ *input -= rewindDistance; /* Rewind */
rewindDistance = 0; rewindDistance = 0;
node = &charmap->nodes[0]; node = &charmap->nodes[0];
if (match) { /* Arrived at a dead end with a match found */ if (match) { /* Arrived at a dead end with a match found */
*output++ = match->value; if (output)
outputLen++; *(*output)++ = match->value;
match = NULL; /* Reset match for next round */
} else if (*input) { /* No match found */ return 1;
size_t codepointLen = readUTF8Char(output, input);
if (codepointLen == 0) { } else if (**input) { /* No match found */
size_t codepointLen = readUTF8Char(output ? *output : NULL,
*input);
if (codepointLen == 0)
error("Input string is not valid UTF-8!\n"); error("Input string is not valid UTF-8!\n");
break;
}
input += codepointLen; /* OK because UTF-8 has no NUL in multi-byte chars */
output += codepointLen;
outputLen += codepointLen;
}
if (!*input) /* OK because UTF-8 has no NUL in multi-byte chars */
break; *input += codepointLen;
if (output)
*output += codepointLen;
return codepointLen;
} else { /* End of input */
return 0;
}
} }
} }
return outputLen; unreachable_();
} }

View File

@@ -44,7 +44,7 @@ struct Context {
static struct Context *contextStack; static struct Context *contextStack;
static size_t contextDepth = 0; static size_t contextDepth = 0;
#define DEFAULT_MAX_DEPTH 64 #define DEFAULT_MAX_DEPTH 64
size_t nMaxRecursionDepth; size_t maxRecursionDepth;
static unsigned int nbIncPaths = 0; static unsigned int nbIncPaths = 0;
static char const *includePaths[MAXINCPATHS]; static char const *includePaths[MAXINCPATHS];
@@ -143,8 +143,8 @@ void fstk_AddIncludePath(char const *path)
static void printDep(char const *path) static void printDep(char const *path)
{ {
if (dependfile) { if (dependfile) {
fprintf(dependfile, "%s: %s\n", tzTargetFileName, path); fprintf(dependfile, "%s: %s\n", targetFileName, path);
if (oGeneratePhonyDeps) if (generatePhonyDeps)
fprintf(dependfile, "%s:\n", path); fprintf(dependfile, "%s:\n", path);
} }
} }
@@ -206,18 +206,18 @@ bool fstk_FindFile(char const *path, char **fullPath, size_t *size)
} }
errno = ENOENT; errno = ENOENT;
if (oGeneratedMissingIncludes) if (generatedMissingIncludes)
printDep(path); printDep(path);
return false; return false;
} }
bool yywrap(void) bool yywrap(void)
{ {
uint32_t nIFDepth = lexer_GetIFDepth(); uint32_t ifDepth = lexer_GetIFDepth();
if (nIFDepth != 0) if (ifDepth != 0)
fatalerror("Ended block with %" PRIu32 " unterminated IF construct%s\n", fatalerror("Ended block with %" PRIu32 " unterminated IF construct%s\n",
nIFDepth, nIFDepth == 1 ? "" : "s"); ifDepth, ifDepth == 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;
@@ -293,8 +293,8 @@ bool yywrap(void)
*/ */
static void newContext(struct FileStackNode *fileInfo) static void newContext(struct FileStackNode *fileInfo)
{ {
if (++contextDepth >= nMaxRecursionDepth) if (++contextDepth >= maxRecursionDepth)
fatalerror("Recursion limit (%zu) exceeded\n", nMaxRecursionDepth); fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
struct Context *context = malloc(sizeof(*context)); struct Context *context = malloc(sizeof(*context));
if (!context) if (!context)
@@ -322,11 +322,11 @@ void fstk_RunInclude(char const *path)
if (!fstk_FindFile(path, &fullPath, &size)) { if (!fstk_FindFile(path, &fullPath, &size)) {
free(fullPath); free(fullPath);
if (oGeneratedMissingIncludes) { if (generatedMissingIncludes) {
if (verbose) if (verbose)
printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n", printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n",
path, strerror(errno)); path, strerror(errno));
oFailedOnMissingInclude = true; failedOnMissingInclude = true;
} else { } else {
error("Unable to open included file '%s': %s\n", path, strerror(errno)); error("Unable to open included file '%s': %s\n", path, strerror(errno));
} }
@@ -417,7 +417,7 @@ 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);
contextStack->lexerState = lexer_OpenFileView(macro->macro, macro->macroSize, contextStack->lexerState = lexer_OpenFileView("MACRO", macro->macro, macro->macroSize,
macro->fileLine); 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");
@@ -451,7 +451,7 @@ static bool newReptContext(int32_t reptLineNo, char *body, size_t size)
/* Correct our line number, which currently points to the `ENDR` line */ /* Correct our line number, which currently points to the `ENDR` line */
contextStack->fileInfo->lineNo = reptLineNo; contextStack->fileInfo->lineNo = reptLineNo;
contextStack->lexerState = lexer_OpenFileView(body, size, reptLineNo); contextStack->lexerState = lexer_OpenFileView("REPT", 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);
@@ -528,7 +528,7 @@ bool fstk_Break(void)
return true; return true;
} }
void fstk_Init(char const *mainPath, size_t maxRecursionDepth) void fstk_Init(char const *mainPath, size_t maxDepth)
{ {
struct LexerState *state = lexer_OpenFile(mainPath); struct LexerState *state = lexer_OpenFile(mainPath);
@@ -570,12 +570,12 @@ void fstk_Init(char const *mainPath, size_t maxRecursionDepth)
* This assumes that the rept node is larger * This assumes that the rept node is larger
*/ */
#define DEPTH_LIMIT ((SIZE_MAX - sizeof(struct FileStackReptNode)) / sizeof(uint32_t)) #define DEPTH_LIMIT ((SIZE_MAX - sizeof(struct FileStackReptNode)) / sizeof(uint32_t))
if (maxRecursionDepth > DEPTH_LIMIT) { if (maxDepth > DEPTH_LIMIT) {
error("Recursion depth may not be higher than %zu, defaulting to " error("Recursion depth may not be higher than %zu, defaulting to "
EXPAND_AND_STR(DEFAULT_MAX_DEPTH) "\n", DEPTH_LIMIT); EXPAND_AND_STR(DEFAULT_MAX_DEPTH) "\n", DEPTH_LIMIT);
nMaxRecursionDepth = DEFAULT_MAX_DEPTH; maxRecursionDepth = DEFAULT_MAX_DEPTH;
} else { } else {
nMaxRecursionDepth = maxRecursionDepth; maxRecursionDepth = maxDepth;
} }
/* Make sure that the default of 64 is OK, though */ /* Make sure that the default of 64 is OK, though */
assert(DEPTH_LIMIT >= DEFAULT_MAX_DEPTH); assert(DEPTH_LIMIT >= DEFAULT_MAX_DEPTH);

View File

@@ -180,6 +180,9 @@ static struct KeywordMapping {
{"BANK", T_OP_BANK}, {"BANK", T_OP_BANK},
{"ALIGN", T_OP_ALIGN}, {"ALIGN", T_OP_ALIGN},
{"SIZEOF", T_OP_SIZEOF},
{"STARTOF", T_OP_STARTOF},
{"ROUND", T_OP_ROUND}, {"ROUND", T_OP_ROUND},
{"CEIL", T_OP_CEIL}, {"CEIL", T_OP_CEIL},
{"FLOOR", T_OP_FLOOR}, {"FLOOR", T_OP_FLOOR},
@@ -210,6 +213,9 @@ static struct KeywordMapping {
{"STRRPL", T_OP_STRRPL}, {"STRRPL", T_OP_STRRPL},
{"STRFMT", T_OP_STRFMT}, {"STRFMT", T_OP_STRFMT},
{"CHARLEN", T_OP_CHARLEN},
{"CHARSUB", T_OP_CHARSUB},
{"INCLUDE", T_POP_INCLUDE}, {"INCLUDE", T_POP_INCLUDE},
{"PRINT", T_POP_PRINT}, {"PRINT", T_POP_PRINT},
{"PRINTLN", T_POP_PRINTLN}, {"PRINTLN", T_POP_PRINTLN},
@@ -298,6 +304,8 @@ static bool isWhitespace(int c)
} }
#define LEXER_BUF_SIZE 42 /* TODO: determine a sane value for this */ #define LEXER_BUF_SIZE 42 /* TODO: determine a sane value for this */
/* The buffer needs to be large enough for the maximum `peekInternal` lookahead distance */
static_assert(LEXER_BUF_SIZE > 1, "Lexer buffer size is too small");
/* This caps the size of buffer reads, and according to POSIX, passing more than SSIZE_MAX is UB */ /* This caps the size of buffer reads, and according to POSIX, passing more than SSIZE_MAX is UB */
static_assert(LEXER_BUF_SIZE <= SSIZE_MAX, "Lexer buffer size is too large"); static_assert(LEXER_BUF_SIZE <= SSIZE_MAX, "Lexer buffer size is too large");
@@ -347,7 +355,6 @@ struct LexerState {
uint32_t lineNo; uint32_t lineNo;
uint32_t colNo; uint32_t colNo;
int lastToken; int lastToken;
int nextToken;
struct IfStack *ifStack; struct IfStack *ifStack;
@@ -371,7 +378,6 @@ static void initState(struct LexerState *state)
state->mode = LEXER_NORMAL; state->mode = LEXER_NORMAL;
state->atLineStart = true; /* yylex() will init colNo due to this */ state->atLineStart = true; /* yylex() will init colNo due to this */
state->lastToken = T_EOF; state->lastToken = T_EOF;
state->nextToken = 0;
state->ifStack = NULL; state->ifStack = NULL;
@@ -525,7 +531,7 @@ struct LexerState *lexer_OpenFile(char const *path)
return state; return state;
} }
struct LexerState *lexer_OpenFileView(char *buf, size_t size, uint32_t lineNo) struct LexerState *lexer_OpenFileView(char const *path, char *buf, size_t size, uint32_t lineNo)
{ {
dbgPrint("Opening view on buffer \"%.*s\"[...]\n", size < 16 ? (int)size : 16, buf); dbgPrint("Opening view on buffer \"%.*s\"[...]\n", size < 16 ? (int)size : 16, buf);
@@ -535,8 +541,8 @@ struct LexerState *lexer_OpenFileView(char *buf, size_t size, uint32_t lineNo)
error("Failed to allocate memory for lexer state: %s\n", strerror(errno)); error("Failed to allocate memory for lexer state: %s\n", strerror(errno));
return NULL; return NULL;
} }
// TODO: init `path`
state->path = path; /* Used to report read errors in `peekInternal` */
state->isFile = false; state->isFile = false;
state->isMmapped = true; /* It's not *really* mmap()ed, but it behaves the same */ state->isMmapped = true; /* It's not *really* mmap()ed, but it behaves the same */
state->ptr = buf; state->ptr = buf;
@@ -563,7 +569,7 @@ void lexer_DeleteState(struct LexerState *state)
// that lexer states lifetimes are always properly managed, since they're handled solely // that lexer states lifetimes are always properly managed, since they're handled solely
// by the fstack... with *one* exception. // by the fstack... with *one* exception.
// Assume a context is pushed on top of the fstack, and the corresponding lexer state gets // Assume a context is pushed on top of the fstack, and the corresponding lexer state gets
// scheduled at EOF; `lexerStateAtEOL` thus becomes a (weak) ref to that lexer state... // scheduled at EOF; `lexerStateEOL` thus becomes a (weak) ref to that lexer state...
// It has been possible, due to a bug, that the corresponding fstack context gets popped // It has been possible, due to a bug, that the corresponding fstack context gets popped
// before EOL, deleting the associated state... but it would still be switched to at EOL. // before EOL, deleting the associated state... but it would still be switched to at EOL.
// This assertion checks that this doesn't happen again. // This assertion checks that this doesn't happen again.
@@ -589,7 +595,7 @@ struct KeywordDictNode {
uint16_t children[0x60 - ' ']; uint16_t children[0x60 - ' '];
struct KeywordMapping const *keyword; struct KeywordMapping const *keyword;
/* Since the keyword structure is invariant, the min number of nodes is known at compile time */ /* Since the keyword structure is invariant, the min number of nodes is known at compile time */
} keywordDict[351] = {0}; /* Make sure to keep this correct when adding keywords! */ } keywordDict[365] = {0}; /* Make sure to keep this correct when adding keywords! */
/* Convert a char into its index into the dict */ /* Convert a char into its index into the dict */
static uint8_t dictIndex(char c) static uint8_t dictIndex(char c)
@@ -678,11 +684,11 @@ static void beginExpansion(char const *str, bool owned, char const *name)
return; return;
if (name) { if (name) {
unsigned int depth = 0; size_t depth = 0;
for (struct Expansion *exp = lexerState->expansions; exp; exp = exp->parent) { for (struct Expansion *exp = lexerState->expansions; exp; exp = exp->parent) {
if (depth++ >= nMaxRecursionDepth) if (depth++ >= maxRecursionDepth)
fatalerror("Recursion limit (%zu) exceeded\n", nMaxRecursionDepth); fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
} }
} }
@@ -711,24 +717,110 @@ static void freeExpansion(struct Expansion *expansion)
static bool isMacroChar(char c) static bool isMacroChar(char c)
{ {
return c == '@' || c == '#' || (c >= '0' && c <= '9'); return c == '@' || c == '#' || c == '<' || (c >= '0' && c <= '9');
}
/* forward declarations for readBracketedMacroArgNum */
static int peek(void);
static void shiftChar(void);
static uint32_t readNumber(int radix, uint32_t baseValue);
static bool startsIdentifier(int c);
static bool continuesIdentifier(int c);
static uint32_t readBracketedMacroArgNum(void)
{
dbgPrint("Reading bracketed macro arg\n");
bool disableMacroArgs = lexerState->disableMacroArgs;
bool disableInterpolation = lexerState->disableInterpolation;
lexerState->disableMacroArgs = false;
lexerState->disableInterpolation = false;
uint32_t num = 0;
int c = peek();
bool empty = false;
bool symbolError = false;
if (c >= '0' && c <= '9') {
num = readNumber(10, 0);
} else if (startsIdentifier(c)) {
char symName[MAXSYMLEN + 1];
size_t i = 0;
for (; continuesIdentifier(c); c = peek()) {
if (i < sizeof(symName))
symName[i++] = c;
shiftChar();
}
if (i == sizeof(symName)) {
warning(WARNING_LONG_STR, "Bracketed symbol name too long\n");
i--;
}
symName[i] = '\0';
struct Symbol const *sym = sym_FindScopedSymbol(symName);
if (!sym) {
error("Bracketed symbol \"%s\" does not exist\n", symName);
num = 0;
symbolError = true;
} else if (!sym_IsNumeric(sym)) {
error("Bracketed symbol \"%s\" is not numeric\n", symName);
num = 0;
symbolError = true;
} else {
num = sym_GetConstantSymValue(sym);
}
} else {
empty = true;
}
c = peek();
shiftChar();
if (c != '>') {
error("Invalid character in bracketed macro argument %s\n", printChar(c));
return 0;
} else if (empty) {
error("Empty bracketed macro argument\n");
return 0;
} else if (num == 0 && !symbolError) {
error("Invalid bracketed macro argument '\\<0>'\n");
return 0;
}
lexerState->disableMacroArgs = disableMacroArgs;
lexerState->disableInterpolation = disableInterpolation;
return num;
} }
static char const *readMacroArg(char name) static char const *readMacroArg(char name)
{ {
char const *str; char const *str = NULL;
if (name == '@') if (name == '@') {
str = macro_GetUniqueIDStr(); str = macro_GetUniqueIDStr();
else if (name == '#') } else if (name == '#') {
str = macro_GetAllArgs(); str = macro_GetAllArgs();
else if (name == '0') } else if (name == '<') {
fatalerror("Invalid macro argument '\\0'\n"); uint32_t num = readBracketedMacroArgNum();
else
str = macro_GetArg(name - '0');
if (!str)
fatalerror("Macro argument '\\%c' not defined\n", name);
if (num == 0)
return NULL;
str = macro_GetArg(num);
if (!str)
error("Macro argument '\\<%" PRIu32 ">' not defined\n", num);
return str;
} else if (name == '0') {
error("Invalid macro argument '\\0'\n");
return NULL;
} else {
assert(name > '0' && name <= '9');
str = macro_GetArg(name - '0');
}
if (!str)
error("Macro argument '\\%c' not defined\n", name);
return str; return str;
} }
@@ -804,7 +896,7 @@ static int peekInternal(uint8_t distance)
/* forward declarations for peek */ /* forward declarations for peek */
static void shiftChar(void); static void shiftChar(void);
static char const *readInterpolation(unsigned int depth); static char const *readInterpolation(size_t depth);
static int peek(void) static int peek(void)
{ {
@@ -828,10 +920,10 @@ restart:
char const *str = readMacroArg(c); char const *str = readMacroArg(c);
/* /*
* If the macro arg is an empty string, it cannot be * If the macro arg is invalid or an empty string, it cannot be
* expanded, so skip it and keep peeking. * expanded, so skip it and keep peeking.
*/ */
if (!str[0]) if (!str || !str[0])
goto restart; goto restart;
beginExpansion(str, c == '#', NULL); beginExpansion(str, c == '#', NULL);
@@ -849,13 +941,12 @@ restart:
} else if (c == '{' && !lexerState->disableInterpolation) { } else if (c == '{' && !lexerState->disableInterpolation) {
/* If character is an open brace, do symbol interpolation */ /* If character is an open brace, do symbol interpolation */
shiftChar(); shiftChar();
char const *ptr = readInterpolation(0); char const *str = readInterpolation(0);
if (ptr) { if (str && str[0])
beginExpansion(ptr, false, ptr); beginExpansion(str, false, str);
goto restart; goto restart;
} }
}
return c; return c;
} }
@@ -1050,12 +1141,12 @@ static void readAnonLabelRef(char c)
n++; n++;
} while (peek() == c); } while (peek() == c);
sym_WriteAnonLabelName(yylval.tzSym, n, c == '-'); sym_WriteAnonLabelName(yylval.symName, n, c == '-');
} }
/* Functions to lex numbers of various radixes */ /* Functions to lex numbers of various radixes */
static void readNumber(int radix, int32_t baseValue) static uint32_t readNumber(int radix, uint32_t baseValue)
{ {
uint32_t value = baseValue; uint32_t value = baseValue;
@@ -1071,10 +1162,10 @@ static void readNumber(int radix, int32_t baseValue)
value = value * radix + (c - '0'); value = value * radix + (c - '0');
} }
yylval.nConstValue = value; return value;
} }
static void readFractionalPart(void) static uint32_t readFractionalPart(int32_t integer)
{ {
uint32_t value = 0, divisor = 1; uint32_t value = 0, divisor = 1;
@@ -1099,20 +1190,20 @@ static void readFractionalPart(void)
divisor *= 10; divisor *= 10;
} }
if (yylval.nConstValue > INT16_MAX || yylval.nConstValue < INT16_MIN) if (integer > INT16_MAX || integer < INT16_MIN)
warning(WARNING_LARGE_CONSTANT, "Magnitude of fixed-point constant is too large\n"); warning(WARNING_LARGE_CONSTANT, "Magnitude of fixed-point constant is too large\n");
/* Cast to unsigned avoids UB if shifting discards bits */ /* Cast to unsigned avoids UB if shifting discards bits */
yylval.nConstValue = (uint32_t)yylval.nConstValue << 16; integer = (uint32_t)integer << 16;
/* Cast to unsigned avoids undefined overflow behavior */ /* Cast to unsigned avoids undefined overflow behavior */
uint16_t fractional = (uint16_t)round(value * 65536.0 / divisor); uint16_t fractional = (uint16_t)round(value * 65536.0 / divisor);
yylval.nConstValue |= fractional * (yylval.nConstValue >= 0 ? 1 : -1); return (uint32_t)integer | (fractional * (integer >= 0 ? 1 : -1));
} }
char binDigits[2]; char binDigits[2];
static void readBinaryNumber(void) static uint32_t readBinaryNumber(void)
{ {
uint32_t value = 0; uint32_t value = 0;
@@ -1134,10 +1225,10 @@ static void readBinaryNumber(void)
value = value * 2 + bit; value = value * 2 + bit;
} }
yylval.nConstValue = value; return value;
} }
static void readHexNumber(void) static uint32_t readHexNumber(void)
{ {
uint32_t value = 0; uint32_t value = 0;
bool empty = true; bool empty = true;
@@ -1167,12 +1258,12 @@ static void readHexNumber(void)
if (empty) if (empty)
error("Invalid integer constant, no digits after '$'\n"); error("Invalid integer constant, no digits after '$'\n");
yylval.nConstValue = value; return value;
} }
char gfxDigits[4]; char gfxDigits[4];
static void readGfxConstant(void) static uint32_t readGfxConstant(void)
{ {
uint32_t bp0 = 0, bp1 = 0; uint32_t bp0 = 0, bp1 = 0;
uint8_t width = 0; uint8_t width = 0;
@@ -1209,7 +1300,7 @@ static void readGfxConstant(void)
warning(WARNING_LARGE_CONSTANT, warning(WARNING_LARGE_CONSTANT,
"Graphics constant is too long, only 8 first pixels considered\n"); "Graphics constant is too long, only 8 first pixels considered\n");
yylval.nConstValue = bp1 << 8 | bp0; return bp1 << 8 | bp0;
} }
/* Functions to read identifiers & keywords */ /* Functions to read identifiers & keywords */
@@ -1228,7 +1319,7 @@ static int readIdentifier(char firstChar)
{ {
dbgPrint("Reading identifier or keyword\n"); dbgPrint("Reading identifier or keyword\n");
/* Lex while checking for a keyword */ /* Lex while checking for a keyword */
yylval.tzSym[0] = firstChar; yylval.symName[0] = firstChar;
uint16_t nodeID = keywordDict[0].children[dictIndex(firstChar)]; uint16_t nodeID = keywordDict[0].children[dictIndex(firstChar)];
int tokenType = firstChar == '.' ? T_LOCAL_ID : T_ID; int tokenType = firstChar == '.' ? T_LOCAL_ID : T_ID;
size_t i = 1; size_t i = 1;
@@ -1237,9 +1328,9 @@ static int readIdentifier(char firstChar)
for (int c = peek(); continuesIdentifier(c); i++, c = peek()) { for (int c = peek(); continuesIdentifier(c); i++, c = peek()) {
shiftChar(); shiftChar();
if (i < sizeof(yylval.tzSym) - 1) { if (i < sizeof(yylval.symName) - 1) {
/* Write the char to the identifier's name */ /* Write the char to the identifier's name */
yylval.tzSym[i] = c; yylval.symName[i] = c;
/* If the char was a dot, mark the identifier as local */ /* If the char was a dot, mark the identifier as local */
if (c == '.') if (c == '.')
@@ -1251,12 +1342,12 @@ static int readIdentifier(char firstChar)
} }
} }
if (i > sizeof(yylval.tzSym) - 1) { if (i > sizeof(yylval.symName) - 1) {
warning(WARNING_LONG_STR, "Symbol name too long, got truncated\n"); warning(WARNING_LONG_STR, "Symbol name too long, got truncated\n");
i = sizeof(yylval.tzSym) - 1; i = sizeof(yylval.symName) - 1;
} }
yylval.tzSym[i] = '\0'; /* Terminate the string */ yylval.symName[i] = '\0'; /* Terminate the string */
dbgPrint("Ident/keyword = \"%s\"\n", yylval.tzSym); dbgPrint("Ident/keyword = \"%s\"\n", yylval.symName);
if (keywordDict[nodeID].keyword) if (keywordDict[nodeID].keyword)
return keywordDict[nodeID].keyword->token; return keywordDict[nodeID].keyword->token;
@@ -1266,26 +1357,33 @@ static int readIdentifier(char firstChar)
/* Functions to read strings */ /* Functions to read strings */
static char const *readInterpolation(unsigned int depth) static char const *readInterpolation(size_t depth)
{ {
if (depth >= nMaxRecursionDepth) if (depth >= maxRecursionDepth)
fatalerror("Recursion limit (%zu) exceeded\n", nMaxRecursionDepth); fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
char symName[MAXSYMLEN + 1]; char symName[MAXSYMLEN + 1];
size_t i = 0; size_t i = 0;
struct FormatSpec fmt = fmt_NewSpec(); struct FormatSpec fmt = fmt_NewSpec();
bool disableInterpolation = lexerState->disableInterpolation;
/*
* In a context where `lexerState->disableInterpolation` is true, `peek` will expand
* nested interpolations itself, which can lead to stack overflow. This lets
* `readInterpolation` handle its own nested expansions, increasing `depth` each time.
*/
lexerState->disableInterpolation = true;
for (;;) { for (;;) {
int c = peek(); int c = peek();
if (c == '{') { /* Nested interpolation */ if (c == '{') { /* Nested interpolation */
shiftChar(); shiftChar();
char const *ptr = readInterpolation(depth + 1); char const *str = readInterpolation(depth + 1);
if (ptr) { if (str && str[0])
beginExpansion(ptr, false, ptr); beginExpansion(str, false, str);
continue; /* Restart, reading from the new buffer */ continue; /* Restart, reading from the new buffer */
}
} else if (c == EOF || c == '\r' || c == '\n' || c == '"') { } else if (c == EOF || c == '\r' || c == '\n' || c == '"') {
error("Missing }\n"); error("Missing }\n");
break; break;
@@ -1309,11 +1407,14 @@ static char const *readInterpolation(unsigned int depth)
} }
if (i == sizeof(symName)) { if (i == sizeof(symName)) {
warning(WARNING_LONG_STR, "Symbol name too long\n"); warning(WARNING_LONG_STR, "Interpolated symbol name too long\n");
i--; i--;
} }
symName[i] = '\0'; symName[i] = '\0';
/* Don't return before `lexerState->disableInterpolation` is reset! */
lexerState->disableInterpolation = disableInterpolation;
static char buf[MAXSTRLEN + 1]; static char buf[MAXSTRLEN + 1];
struct Symbol const *sym = sym_FindScopedSymbol(symName); struct Symbol const *sym = sym_FindScopedSymbol(symName);
@@ -1340,10 +1441,10 @@ static char const *readInterpolation(unsigned int depth)
return NULL; return NULL;
} }
#define append_yylval_tzString(c) do { \ #define append_yylval_string(c) do { \
char v = (c); /* Evaluate c exactly once in case it has side effects. */ \ char v = (c); /* Evaluate c exactly once in case it has side effects. */ \
if (i < sizeof(yylval.tzString)) \ if (i < sizeof(yylval.string)) \
yylval.tzString[i++] = v; \ yylval.string[i++] = v; \
} while (0) } while (0)
static size_t appendEscapedSubstring(char const *str, size_t i) static size_t appendEscapedSubstring(char const *str, size_t i)
@@ -1357,23 +1458,23 @@ static size_t appendEscapedSubstring(char const *str, size_t i)
case '\\': case '\\':
case '"': case '"':
case '{': case '{':
append_yylval_tzString('\\'); append_yylval_string('\\');
break; break;
case '\n': case '\n':
append_yylval_tzString('\\'); append_yylval_string('\\');
c = 'n'; c = 'n';
break; break;
case '\r': case '\r':
append_yylval_tzString('\\'); append_yylval_string('\\');
c = 'r'; c = 'r';
break; break;
case '\t': case '\t':
append_yylval_tzString('\\'); append_yylval_string('\\');
c = 't'; c = 't';
break; break;
} }
append_yylval_tzString(c); append_yylval_string(c);
} }
return i; return i;
@@ -1387,6 +1488,7 @@ static void readString(void)
size_t i = 0; size_t i = 0;
bool multiline = false; bool multiline = false;
char const *str;
// We reach this function after reading a single quote, but we also support triple quotes // We reach this function after reading a single quote, but we also support triple quotes
if (peek() == '"') { if (peek() == '"') {
@@ -1429,7 +1531,7 @@ static void readString(void)
break; break;
shiftChar(); shiftChar();
if (peek() != '"') { if (peek() != '"') {
append_yylval_tzString('"'); append_yylval_string('"');
break; break;
} }
shiftChar(); shiftChar();
@@ -1479,11 +1581,13 @@ static void readString(void)
case '7': case '7':
case '8': case '8':
case '9': case '9':
case '<':
shiftChar(); shiftChar();
char const *str = readMacroArg(c); str = readMacroArg(c);
if (str) {
while (*str) while (*str)
append_yylval_tzString(*str++); append_yylval_string(*str++);
}
continue; // Do not copy an additional character continue; // Do not copy an additional character
case EOF: // Can't really print that one case EOF: // Can't really print that one
@@ -1502,28 +1606,28 @@ static void readString(void)
// We'll be exiting the string scope, so re-enable expansions // We'll be exiting the string scope, so re-enable expansions
// (Not interpolations, since they're handled by the function itself...) // (Not interpolations, since they're handled by the function itself...)
lexerState->disableMacroArgs = false; lexerState->disableMacroArgs = false;
char const *ptr = readInterpolation(0); str = readInterpolation(0);
if (str) {
if (ptr) while (*str)
while (*ptr) append_yylval_string(*str++);
append_yylval_tzString(*ptr++); }
lexerState->disableMacroArgs = true; lexerState->disableMacroArgs = true;
continue; // Do not copy an additional character continue; // Do not copy an additional character
// Regular characters will just get copied // Regular characters will just get copied
} }
append_yylval_tzString(c); append_yylval_string(c);
} }
finish: finish:
if (i == sizeof(yylval.tzString)) { if (i == sizeof(yylval.string)) {
i--; i--;
warning(WARNING_LONG_STR, "String constant too long\n"); warning(WARNING_LONG_STR, "String constant too long\n");
} }
yylval.tzString[i] = '\0'; yylval.string[i] = '\0';
dbgPrint("Read string \"%s\"\n", yylval.tzString); dbgPrint("Read string \"%s\"\n", yylval.string);
lexerState->disableMacroArgs = false; lexerState->disableMacroArgs = false;
lexerState->disableInterpolation = false; lexerState->disableInterpolation = false;
} }
@@ -1535,15 +1639,16 @@ static size_t appendStringLiteral(size_t i)
lexerState->disableInterpolation = true; lexerState->disableInterpolation = true;
bool multiline = false; bool multiline = false;
char const *str;
// We reach this function after reading a single quote, but we also support triple quotes // We reach this function after reading a single quote, but we also support triple quotes
append_yylval_tzString('"'); append_yylval_string('"');
if (peek() == '"') { if (peek() == '"') {
append_yylval_tzString('"'); append_yylval_string('"');
shiftChar(); shiftChar();
if (peek() == '"') { if (peek() == '"') {
// """ begins a multi-line string // """ begins a multi-line string
append_yylval_tzString('"'); append_yylval_string('"');
shiftChar(); shiftChar();
multiline = true; multiline = true;
} else { } else {
@@ -1578,14 +1683,14 @@ static size_t appendStringLiteral(size_t i)
// Only """ ends a multi-line string // Only """ ends a multi-line string
if (peek() != '"') if (peek() != '"')
break; break;
append_yylval_tzString('"'); append_yylval_string('"');
shiftChar(); shiftChar();
if (peek() != '"') if (peek() != '"')
break; break;
append_yylval_tzString('"'); append_yylval_string('"');
shiftChar(); shiftChar();
} }
append_yylval_tzString('"'); append_yylval_string('"');
goto finish; goto finish;
case '\\': // Character escape or macro arg case '\\': // Character escape or macro arg
@@ -1600,7 +1705,7 @@ static size_t appendStringLiteral(size_t i)
case 'r': case 'r':
case 't': case 't':
// Return that character unchanged // Return that character unchanged
append_yylval_tzString('\\'); append_yylval_string('\\');
shiftChar(); shiftChar();
break; break;
@@ -1624,9 +1729,10 @@ static size_t appendStringLiteral(size_t i)
case '7': case '7':
case '8': case '8':
case '9': case '9':
case '<':
shiftChar(); shiftChar();
char const *str = readMacroArg(c); str = readMacroArg(c);
if (str && str[0])
i = appendEscapedSubstring(str, i); i = appendEscapedSubstring(str, i);
continue; // Do not copy an additional character continue; // Do not copy an additional character
@@ -1652,27 +1758,26 @@ static size_t appendStringLiteral(size_t i)
// We'll be exiting the string scope, so re-enable expansions // We'll be exiting the string scope, so re-enable expansions
// (Not interpolations, since they're handled by the function itself...) // (Not interpolations, since they're handled by the function itself...)
lexerState->disableMacroArgs = false; lexerState->disableMacroArgs = false;
char const *ptr = readInterpolation(0); str = readInterpolation(0);
if (str && str[0])
if (ptr) i = appendEscapedSubstring(str, i);
i = appendEscapedSubstring(ptr, i);
lexerState->disableMacroArgs = true; lexerState->disableMacroArgs = true;
continue; // Do not copy an additional character continue; // Do not copy an additional character
// Regular characters will just get copied // Regular characters will just get copied
} }
append_yylval_tzString(c); append_yylval_string(c);
} }
finish: finish:
if (i == sizeof(yylval.tzString)) { if (i == sizeof(yylval.string)) {
i--; i--;
warning(WARNING_LONG_STR, "String constant too long\n"); warning(WARNING_LONG_STR, "String constant too long\n");
} }
yylval.tzString[i] = '\0'; yylval.string[i] = '\0';
dbgPrint("Read string \"%s\"\n", yylval.tzString); dbgPrint("Read string \"%s\"\n", yylval.string);
lexerState->disableMacroArgs = false; lexerState->disableMacroArgs = false;
lexerState->disableInterpolation = false; lexerState->disableInterpolation = false;
@@ -1688,13 +1793,6 @@ static int yylex_NORMAL(void)
dbgPrint("Lexing in normal mode, line=%" PRIu32 ", col=%" PRIu32 "\n", dbgPrint("Lexing in normal mode, line=%" PRIu32 ", col=%" PRIu32 "\n",
lexer_GetLineNo(), lexer_GetColNo()); lexer_GetLineNo(), lexer_GetColNo());
if (lexerState->nextToken) {
int token = lexerState->nextToken;
lexerState->nextToken = 0;
return token;
}
for (;;) { for (;;) {
int c = nextChar(); int c = nextChar();
char secondChar; char secondChar;
@@ -1721,8 +1819,8 @@ static int yylex_NORMAL(void)
return T_OP_NOT; return T_OP_NOT;
case '@': case '@':
yylval.tzSym[0] = '@'; yylval.symName[0] = '@';
yylval.tzSym[1] = '\0'; yylval.symName[1] = '\0';
return T_ID; return T_ID;
case '[': case '[':
@@ -1811,25 +1909,7 @@ static int yylex_NORMAL(void)
/* Handle numbers */ /* Handle numbers */
case '$': case '$':
yylval.nConstValue = 0; yylval.constValue = readHexNumber();
readHexNumber();
/* Attempt to match `$ff00+c` */
if (yylval.nConstValue == 0xff00) {
/* Whitespace is ignored anyways */
while (isWhitespace(c = peek()))
shiftChar();
if (c == '+') {
shiftChar();
while (isWhitespace(c = peek()))
shiftChar();
if (c == 'c' || c == 'C') {
shiftChar();
return T_MODE_HW_C;
}
/* Retroactively lex the plus after the $ff00 */
lexerState->nextToken = T_OP_ADD;
}
}
return T_NUMBER; return T_NUMBER;
case '0': /* Decimal number */ case '0': /* Decimal number */
@@ -1842,10 +1922,10 @@ static int yylex_NORMAL(void)
case '7': case '7':
case '8': case '8':
case '9': case '9':
readNumber(10, c - '0'); yylval.constValue = readNumber(10, c - '0');
if (peek() == '.') { if (peek() == '.') {
shiftChar(); shiftChar();
readFractionalPart(); yylval.constValue = readFractionalPart(yylval.constValue);
} }
return T_NUMBER; return T_NUMBER;
@@ -1855,7 +1935,7 @@ static int yylex_NORMAL(void)
shiftChar(); shiftChar();
return T_OP_LOGICAND; return T_OP_LOGICAND;
} else if (secondChar >= '0' && secondChar <= '7') { } else if (secondChar >= '0' && secondChar <= '7') {
readNumber(8, 0); yylval.constValue = readNumber(8, 0);
return T_NUMBER; return T_NUMBER;
} }
return T_OP_AND; return T_OP_AND;
@@ -1865,12 +1945,11 @@ static int yylex_NORMAL(void)
if (secondChar != binDigits[0] && secondChar != binDigits[1]) if (secondChar != binDigits[0] && secondChar != binDigits[1])
return T_OP_MOD; return T_OP_MOD;
yylval.nConstValue = 0; yylval.constValue = readBinaryNumber();
readBinaryNumber();
return T_NUMBER; return T_NUMBER;
case '`': /* Gfx constant */ case '`': /* Gfx constant */
readGfxConstant(); yylval.constValue = readGfxConstant();
return T_NUMBER; return T_NUMBER;
/* Handle strings */ /* Handle strings */
@@ -1917,11 +1996,13 @@ static int yylex_NORMAL(void)
/* Local symbols cannot be string expansions */ /* Local symbols cannot be string expansions */
if (tokenType == T_ID && lexerState->expandStrings) { if (tokenType == T_ID && lexerState->expandStrings) {
/* Attempt string expansion */ /* Attempt string expansion */
struct Symbol const *sym = sym_FindExactSymbol(yylval.tzSym); struct Symbol const *sym = sym_FindExactSymbol(yylval.symName);
if (sym && sym->type == SYM_EQUS) { if (sym && sym->type == SYM_EQUS) {
char const *s = sym_GetStringValue(sym); char const *s = sym_GetStringValue(sym);
assert(s);
if (s[0])
beginExpansion(s, false, sym->name); beginExpansion(s, false, sym->name);
continue; /* Restart, reading from the new buffer */ continue; /* Restart, reading from the new buffer */
} }
@@ -1949,6 +2030,7 @@ static int yylex_RAW(void)
lexer_GetLineNo(), lexer_GetColNo()); lexer_GetLineNo(), lexer_GetColNo());
/* This is essentially a modified `appendStringLiteral` */ /* This is essentially a modified `appendStringLiteral` */
size_t parenDepth = 0;
size_t i = 0; size_t i = 0;
int c; int c;
@@ -1969,8 +2051,7 @@ static int yylex_RAW(void)
discardComment(); discardComment();
c = peek(); c = peek();
/* fallthrough */ /* fallthrough */
case ',': /* End of macro arg */ case '\r': /* End of line */
case '\r':
case '\n': case '\n':
case EOF: case EOF:
goto finish; goto finish;
@@ -1982,15 +2063,32 @@ static int yylex_RAW(void)
discardBlockComment(); discardBlockComment();
continue; continue;
} }
append_yylval_tzString(c); /* Append the slash */ append_yylval_string(c); /* Append the slash */
break; break;
case ',': /* End of macro arg */
if (parenDepth == 0)
goto finish;
goto append;
case '(': /* Open parentheses inside macro args */
if (parenDepth < UINT_MAX)
parenDepth++;
goto append;
case ')': /* Close parentheses inside macro args */
if (parenDepth > 0)
parenDepth--;
goto append;
case '\\': /* Character escape */ case '\\': /* Character escape */
shiftChar(); shiftChar();
c = peek(); c = peek();
switch (c) { switch (c) {
case ',': /* Escape `\,` only inside a macro arg */ case ',': /* Escapes only valid inside a macro arg */
case '(':
case ')':
case '\\': /* Escapes shared with string literals */ case '\\': /* Escapes shared with string literals */
case '"': case '"':
case '{': case '{':
@@ -2030,23 +2128,24 @@ static int yylex_RAW(void)
/* fallthrough */ /* fallthrough */
default: /* Regular characters will just get copied */ default: /* Regular characters will just get copied */
append_yylval_tzString(c); append:
append_yylval_string(c);
shiftChar(); shiftChar();
break; break;
} }
} }
finish: finish:
if (i == sizeof(yylval.tzString)) { if (i == sizeof(yylval.string)) {
i--; i--;
warning(WARNING_LONG_STR, "Macro argument too long\n"); warning(WARNING_LONG_STR, "Macro argument too long\n");
} }
/* Trim right whitespace */ /* Trim right whitespace */
while (i && isWhitespace(yylval.tzString[i - 1])) while (i && isWhitespace(yylval.string[i - 1]))
i--; i--;
yylval.tzString[i] = '\0'; yylval.string[i] = '\0';
dbgPrint("Read raw string \"%s\"\n", yylval.tzString); dbgPrint("Read raw string \"%s\"\n", yylval.string);
// Returning T_COMMAs to the parser would mean that two consecutive commas // Returning T_COMMAs to the parser would mean that two consecutive commas
// (i.e. an empty argument) need to return two different tokens (T_STRING // (i.e. an empty argument) need to return two different tokens (T_STRING
@@ -2076,7 +2175,7 @@ finish:
return T_EOF; return T_EOF;
} }
#undef append_yylval_tzString #undef append_yylval_string
/* /*
* This function uses the fact that `if`, etc. constructs are only valid when * This function uses the fact that `if`, etc. constructs are only valid when
@@ -2261,11 +2360,17 @@ finish:
int yylex(void) int yylex(void)
{ {
restart:
if (lexerState->atLineStart && lexerStateEOL) { if (lexerState->atLineStart && lexerStateEOL) {
lexer_SetState(lexerStateEOL); lexer_SetState(lexerStateEOL);
lexerStateEOL = NULL; lexerStateEOL = NULL;
} }
/* `lexer_SetState` updates `lexerState`, so check for EOF after it */
if (lexerState->lastToken == T_EOB) {
if (yywrap()) {
dbgPrint("Reached end of input.\n");
return T_EOF;
}
}
if (lexerState->atLineStart) { if (lexerState->atLineStart) {
/* Newlines read within an expansion should not increase the line count */ /* Newlines read within an expansion should not increase the line count */
if (!lexerState->expansions) if (!lexerState->expansions)
@@ -2282,29 +2387,20 @@ restart:
int token = lexerModeFuncs[lexerState->mode](); int token = lexerModeFuncs[lexerState->mode]();
if (token == T_EOF) { if (token == T_EOF) {
if (lexerState->lastToken != T_NEWLINE) { dbgPrint("Reached EOB!\n");
dbgPrint("Forcing EOL at EOF\n");
token = T_NEWLINE;
} else {
/* Try to switch to new buffer; if it succeeds, scan again */
dbgPrint("Reached EOF!\n");
/* Captures end at their buffer's boundary no matter what */ /* Captures end at their buffer's boundary no matter what */
if (!lexerState->capturing) { if (!lexerState->capturing)
if (!yywrap()) token = T_EOB;
goto restart;
dbgPrint("Reached end of input.\n");
return T_EOF;
}
}
} }
lexerState->lastToken = token; lexerState->lastToken = token;
lexerState->atLineStart = token == T_NEWLINE; lexerState->atLineStart = token == T_NEWLINE || token == T_EOB;
return token; return token;
} }
static char *startCapture(void) static char *startCapture(void)
{ {
assert(!lexerState->capturing);
lexerState->capturing = true; lexerState->capturing = true;
lexerState->captureSize = 0; lexerState->captureSize = 0;
lexerState->disableMacroArgs = true; lexerState->disableMacroArgs = true;
@@ -2314,18 +2410,19 @@ static char *startCapture(void)
return &lexerState->ptr[lexerState->offset]; return &lexerState->ptr[lexerState->offset];
} else { } else {
lexerState->captureCapacity = 128; /* The initial size will be twice that */ lexerState->captureCapacity = 128; /* The initial size will be twice that */
assert(lexerState->captureBuf == NULL);
reallocCaptureBuf(); reallocCaptureBuf();
return lexerState->captureBuf; return NULL; // Indicate to retrieve the capture buffer when done capturing
} }
} }
void lexer_CaptureRept(struct CaptureBody *capture) bool lexer_CaptureRept(struct CaptureBody *capture)
{ {
capture->lineNo = lexer_GetLineNo(); capture->lineNo = lexer_GetLineNo();
capture->body = startCapture();
char *captureStart = startCapture(); size_t depth = 0;
unsigned int level = 0; int c = EOF;
int c;
/* /*
* Due to parser internals, it reads the EOL after the expression before calling this. * Due to parser internals, it reads the EOL after the expression before calling this.
@@ -2344,21 +2441,20 @@ void lexer_CaptureRept(struct CaptureBody *capture)
switch (readIdentifier(c)) { switch (readIdentifier(c)) {
case T_POP_REPT: case T_POP_REPT:
case T_POP_FOR: case T_POP_FOR:
level++; depth++;
/* Ignore the rest of that line */ /* Ignore the rest of that line */
break; break;
case T_POP_ENDR: case T_POP_ENDR:
if (!level) { if (!depth) {
/* /*
* The final ENDR has been captured, but we don't want it! * The final ENDR has been captured, but we don't want it!
* We know we have read exactly "ENDR", not e.g. an EQUS * We know we have read exactly "ENDR", not e.g. an EQUS
*/ */
lexerState->captureSize -= strlen("ENDR"); lexerState->captureSize -= strlen("ENDR");
lexerState->lastToken = T_POP_ENDR; // Force EOL at EOF
goto finish; goto finish;
} }
level--; depth--;
} }
} }
@@ -2376,21 +2472,27 @@ void lexer_CaptureRept(struct CaptureBody *capture)
} }
finish: finish:
capture->body = captureStart; // This being NULL means we're capturing from the capture buf, which is `realloc`'d during
// the whole capture process, and so MUST be retrieved at the end
if (!capture->body)
capture->body = lexerState->captureBuf;
capture->size = lexerState->captureSize; capture->size = lexerState->captureSize;
lexerState->capturing = false; lexerState->capturing = false;
lexerState->captureBuf = NULL; lexerState->captureBuf = NULL;
lexerState->disableMacroArgs = false; lexerState->disableMacroArgs = false;
lexerState->disableInterpolation = false; lexerState->disableInterpolation = false;
lexerState->atLineStart = false; lexerState->atLineStart = false;
/* Returns true if an ENDR terminated the block, false if it reached EOF first */
return c != EOF;
} }
void lexer_CaptureMacroBody(struct CaptureBody *capture) bool lexer_CaptureMacroBody(struct CaptureBody *capture)
{ {
capture->lineNo = lexer_GetLineNo(); capture->lineNo = lexer_GetLineNo();
capture->body = startCapture();
char *captureStart = startCapture(); int c = EOF;
int c;
/* If the file is `mmap`ed, we need not to unmap it to keep access to the macro */ /* If the file is `mmap`ed, we need not to unmap it to keep access to the macro */
if (lexerState->isMmapped) if (lexerState->isMmapped)
@@ -2417,7 +2519,6 @@ void lexer_CaptureMacroBody(struct CaptureBody *capture)
* We know we have read exactly "ENDM", not e.g. an EQUS * We know we have read exactly "ENDM", not e.g. an EQUS
*/ */
lexerState->captureSize -= strlen("ENDM"); lexerState->captureSize -= strlen("ENDM");
lexerState->lastToken = T_POP_ENDM; // Force EOL at EOF
goto finish; goto finish;
} }
} }
@@ -2436,11 +2537,17 @@ void lexer_CaptureMacroBody(struct CaptureBody *capture)
} }
finish: finish:
capture->body = captureStart; // This being NULL means we're capturing from the capture buf, which is `realloc`'d during
// the whole capture process, and so MUST be retrieved at the end
if (!capture->body)
capture->body = lexerState->captureBuf;
capture->size = lexerState->captureSize; capture->size = lexerState->captureSize;
lexerState->capturing = false; lexerState->capturing = false;
lexerState->captureBuf = NULL; lexerState->captureBuf = NULL;
lexerState->disableMacroArgs = false; lexerState->disableMacroArgs = false;
lexerState->disableInterpolation = false; lexerState->disableInterpolation = false;
lexerState->atLineStart = false; lexerState->atLineStart = false;
/* Returns true if an ENDM terminated the block, false if it reached EOF first */
return c != EOF;
} }

View File

@@ -36,6 +36,18 @@
#include "helpers.h" #include "helpers.h"
#include "version.h" #include "version.h"
#ifdef __clang__
#if __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__)
#define __SANITIZE_ADDRESS__
#endif /* __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__) */
#endif /* __clang__ */
#ifdef __SANITIZE_ADDRESS__
// There are known, non-trivial to fix leaks. We would still like to have `make develop'
// detect memory corruption, though.
const char *__asan_default_options(void) { return "detect_leaks=0"; }
#endif
// Old Bison versions (confirmed for 2.3) do not forward-declare `yyparse` in the generated header // Old Bison versions (confirmed for 2.3) do not forward-declare `yyparse` in the generated header
// 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);
@@ -45,13 +57,13 @@ extern int yydebug;
#endif #endif
FILE * dependfile; FILE * dependfile;
bool oGeneratedMissingIncludes; bool generatedMissingIncludes;
bool oFailedOnMissingInclude; bool failedOnMissingInclude;
bool oGeneratePhonyDeps; bool generatePhonyDeps;
char *tzTargetFileName; char *targetFileName;
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. */
@@ -158,21 +170,21 @@ int main(int argc, char *argv[])
// Set defaults // Set defaults
oGeneratePhonyDeps = false; generatePhonyDeps = false;
oGeneratedMissingIncludes = false; generatedMissingIncludes = false;
oFailedOnMissingInclude = false; failedOnMissingInclude = false;
tzTargetFileName = NULL; targetFileName = NULL;
opt_B("01"); opt_B("01");
opt_G("0123"); opt_G("0123");
opt_P(0); opt_P(0);
optimizeloads = true; optimizeLoads = true;
haltnop = true; haltnop = true;
verbose = false; verbose = false;
warnings = true; warnings = true;
sym_SetExportAll(false); sym_SetExportAll(false);
uint32_t maxRecursionDepth = 64; uint32_t maxDepth = 64;
size_t nTargetFileNameLen = 0; size_t targetFileNameLen = 0;
while ((ch = musl_getopt_long_only(argc, argv, optstring, longopts, NULL)) != -1) { while ((ch = musl_getopt_long_only(argc, argv, optstring, longopts, NULL)) != -1) {
switch (ch) { switch (ch) {
@@ -214,7 +226,7 @@ int main(int argc, char *argv[])
break; break;
case 'L': case 'L':
optimizeloads = false; optimizeLoads = false;
break; break;
case 'M': case 'M':
@@ -244,7 +256,7 @@ int main(int argc, char *argv[])
break; break;
case 'r': case 'r':
maxRecursionDepth = strtoul(musl_optarg, &ep, 0); maxDepth = strtoul(musl_optarg, &ep, 0);
if (musl_optarg[0] == '\0' || *ep != '\0') if (musl_optarg[0] == '\0' || *ep != '\0')
errx(1, "Invalid argument for option 'r'"); errx(1, "Invalid argument for option 'r'");
@@ -269,11 +281,11 @@ int main(int argc, char *argv[])
case 0: case 0:
switch (depType) { switch (depType) {
case 'G': case 'G':
oGeneratedMissingIncludes = true; generatedMissingIncludes = true;
break; break;
case 'P': case 'P':
oGeneratePhonyDeps = true; generatePhonyDeps = true;
break; break;
case 'Q': case 'Q':
@@ -284,22 +296,22 @@ int main(int argc, char *argv[])
if (depType == 'Q') if (depType == 'Q')
ep = make_escape(ep); ep = make_escape(ep);
nTargetFileNameLen += strlen(ep) + 1; targetFileNameLen += strlen(ep) + 1;
if (!tzTargetFileName) { if (!targetFileName) {
/* On first alloc, make an empty str */ /* On first alloc, make an empty str */
tzTargetFileName = malloc(nTargetFileNameLen + 1); targetFileName = malloc(targetFileNameLen + 1);
if (tzTargetFileName) if (targetFileName)
*tzTargetFileName = '\0'; *targetFileName = '\0';
} else { } else {
tzTargetFileName = realloc(tzTargetFileName, targetFileName = realloc(targetFileName,
nTargetFileNameLen + 1); targetFileNameLen + 1);
} }
if (tzTargetFileName == NULL) if (targetFileName == NULL)
err(1, "Cannot append new file to target file list"); err(1, "Cannot append new file to target file list");
strcat(tzTargetFileName, ep); strcat(targetFileName, ep);
if (depType == 'Q') if (depType == 'Q')
free(ep); free(ep);
char *ptr = tzTargetFileName + strlen(tzTargetFileName); char *ptr = targetFileName + strlen(targetFileName);
*ptr++ = ' '; *ptr++ = ' ';
*ptr = '\0'; *ptr = '\0';
@@ -314,8 +326,8 @@ int main(int argc, char *argv[])
} }
} }
if (tzTargetFileName == NULL) if (targetFileName == NULL)
tzTargetFileName = tzObjectname; targetFileName = objectName;
if (argc == musl_optind) { if (argc == musl_optind) {
fputs("FATAL: No input files\n", stderr); fputs("FATAL: No input files\n", stderr);
@@ -331,17 +343,17 @@ int main(int argc, char *argv[])
printf("Assembling %s\n", mainFileName); printf("Assembling %s\n", mainFileName);
if (dependfile) { if (dependfile) {
if (!tzTargetFileName) if (!targetFileName)
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, mainFileName); fprintf(dependfile, "%s: %s\n", targetFileName, mainFileName);
} }
charmap_New("main", NULL); charmap_New("main", NULL);
// Init lexer and file stack, prodiving file info // Init lexer and file stack, prodiving file info
lexer_Init(); lexer_Init();
fstk_Init(mainFileName, maxRecursionDepth); fstk_Init(mainFileName, maxDepth);
// Perform parse (yyparse is auto-generated from `parser.y`) // Perform parse (yyparse is auto-generated from `parser.y`)
if (yyparse() != 0 && nbErrors == 0) if (yyparse() != 0 && nbErrors == 0)
@@ -357,11 +369,11 @@ int main(int argc, char *argv[])
nbErrors == 1 ? "" : "s"); nbErrors == 1 ? "" : "s");
// If parse aborted due to missing an include, and `-MG` was given, exit normally // If parse aborted due to missing an include, and `-MG` was given, exit normally
if (oFailedOnMissingInclude) if (failedOnMissingInclude)
return 0; return 0;
/* If no path specified, don't write file */ /* If no path specified, don't write file */
if (tzObjectname != NULL) if (objectName != NULL)
out_WriteObject(); out_WriteObject();
return 0; return 0;
} }

View File

@@ -1,4 +1,3 @@
#include <errno.h> #include <errno.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
@@ -7,6 +6,7 @@
#include <string.h> #include <string.h>
#include "asm/lexer.h" #include "asm/lexer.h"
#include "asm/main.h"
#include "asm/section.h" #include "asm/section.h"
#include "asm/warning.h" #include "asm/warning.h"
@@ -14,6 +14,10 @@ struct OptStackEntry {
char binary[2]; char binary[2];
char gbgfx[4]; char gbgfx[4];
int32_t fillByte; int32_t fillByte;
bool haltnop;
bool optimizeLoads;
bool warningsAreErrors;
enum WarningState warningStates[NB_WARNINGS];
struct OptStackEntry *next; struct OptStackEntry *next;
}; };
@@ -34,6 +38,21 @@ void opt_P(uint8_t fill)
fillByte = fill; fillByte = fill;
} }
void opt_h(bool halt)
{
haltnop = halt;
}
void opt_L(bool optimize)
{
optimizeLoads = optimize;
}
void opt_W(char const *flag)
{
processWarningFlag(flag);
}
void opt_Parse(char *s) void opt_Parse(char *s)
{ {
switch (s[0]) { switch (s[0]) {
@@ -66,6 +85,49 @@ void opt_Parse(char *s)
} }
break; break;
case 'h':
if (s[1] == '\0')
opt_h(false);
else
error("Option 'h' does not take an argument\n");
break;
case 'L':
if (s[1] == '\0')
opt_L(false);
else
error("Option 'L' does not take an argument\n");
break;
case 'W':
if (strlen(&s[1]) > 0)
opt_W(&s[1]);
else
error("Must specify an argument for option 'W'\n");
break;
case '!': // negates flag options that do not take an argument
switch (s[1]) {
case 'h':
if (s[2] == '\0')
opt_h(true);
else
error("Option '!h' does not take an argument\n");
break;
case 'L':
if (s[2] == '\0')
opt_L(true);
else
error("Option '!L' does not take an argument\n");
break;
default:
error("Unknown option '!%c'\n", s[1]);
break;
}
break;
default: default:
error("Unknown option '%c'\n", s[0]); error("Unknown option '%c'\n", s[0]);
break; break;
@@ -90,6 +152,14 @@ void opt_Push(void)
entry->fillByte = fillByte; // Pulled from section.h entry->fillByte = fillByte; // Pulled from section.h
entry->haltnop = haltnop; // Pulled from main.h
entry->optimizeLoads = optimizeLoads; // Pulled from main.h
// Both of these pulled from warning.h
entry->warningsAreErrors = warningsAreErrors;
memcpy(entry->warningStates, warningStates, sizeof(warningStates));
entry->next = stack; entry->next = stack;
stack = entry; stack = entry;
} }
@@ -106,6 +176,13 @@ void opt_Pop(void)
opt_B(entry->binary); opt_B(entry->binary);
opt_G(entry->gbgfx); opt_G(entry->gbgfx);
opt_P(entry->fillByte); opt_P(entry->fillByte);
opt_h(entry->haltnop);
opt_L(entry->optimizeLoads);
// opt_W does not apply a whole warning state; it processes one flag string
warningsAreErrors = entry->warningsAreErrors;
memcpy(warningStates, entry->warningStates, sizeof(warningStates));
stack = entry->next; stack = entry->next;
free(entry); free(entry);
} }

View File

@@ -35,12 +35,12 @@
struct Patch { struct Patch {
struct FileStackNode const *src; struct FileStackNode const *src;
uint32_t lineNo; uint32_t lineNo;
uint32_t nOffset; uint32_t offset;
struct Section *pcSection; struct Section *pcSection;
uint32_t pcOffset; uint32_t pcOffset;
uint8_t type; uint8_t type;
uint32_t nRPNSize; uint32_t rpnSize;
uint8_t *pRPN; uint8_t *rpn;
struct Patch *next; struct Patch *next;
}; };
@@ -51,10 +51,9 @@ struct Assertion {
struct Assertion *next; struct Assertion *next;
}; };
char *tzObjectname; char *objectName;
/* TODO: shouldn't `pCurrentSection` be somewhere else? */ struct Section *sectionList;
struct Section *pSectionList, *pCurrentSection;
/* Linked list of symbols to put in the object file */ /* Linked list of symbols to put in the object file */
static struct Symbol *objectSymbols = NULL; static struct Symbol *objectSymbols = NULL;
@@ -72,7 +71,7 @@ static uint32_t countSections(void)
{ {
uint32_t count = 0; uint32_t count = 0;
for (struct Section const *sect = pSectionList; sect; sect = sect->next) for (struct Section const *sect = sectionList; sect; sect = sect->next)
count++; count++;
return count; return count;
@@ -179,7 +178,7 @@ This is code intended to replace a node, which is pretty useless until ref count
*/ */
static uint32_t getsectid(struct Section const *sect) static uint32_t getsectid(struct Section const *sect)
{ {
struct Section const *sec = pSectionList; struct Section const *sec = sectionList;
uint32_t ID = 0; uint32_t ID = 0;
while (sec) { while (sec) {
@@ -205,12 +204,12 @@ static void writepatch(struct Patch const *patch, FILE *f)
assert(patch->src->ID != -1); assert(patch->src->ID != -1);
putlong(patch->src->ID, f); putlong(patch->src->ID, f);
putlong(patch->lineNo, f); putlong(patch->lineNo, f);
putlong(patch->nOffset, f); putlong(patch->offset, f);
putlong(getSectIDIfAny(patch->pcSection), f); putlong(getSectIDIfAny(patch->pcSection), f);
putlong(patch->pcOffset, f); putlong(patch->pcOffset, f);
putc(patch->type, f); putc(patch->type, f);
putlong(patch->nRPNSize, f); putlong(patch->rpnSize, f);
fwrite(patch->pRPN, 1, patch->nRPNSize, f); fwrite(patch->rpn, 1, patch->rpnSize, f);
} }
/* /*
@@ -242,6 +241,21 @@ static void writesection(struct Section const *sect, FILE *f)
} }
} }
static void freesection(struct Section const *sect)
{
if (sect_HasData(sect->type)) {
struct Patch *patch = sect->patches;
while (patch != NULL) {
struct Patch *next = patch->next;
free(patch->rpn);
free(patch);
patch = next;
}
}
}
/* /*
* Write a symbol to a file * Write a symbol to a file
*/ */
@@ -286,7 +300,7 @@ static uint32_t getSymbolID(struct Symbol *sym)
static void writerpn(uint8_t *rpnexpr, uint32_t *rpnptr, uint8_t *rpn, static void writerpn(uint8_t *rpnexpr, uint32_t *rpnptr, uint8_t *rpn,
uint32_t rpnlen) uint32_t rpnlen)
{ {
char tzSym[512]; char symName[512];
for (size_t offset = 0; offset < rpnlen; ) { for (size_t offset = 0; offset < rpnlen; ) {
#define popbyte() rpn[offset++] #define popbyte() rpn[offset++]
@@ -310,14 +324,14 @@ static void writerpn(uint8_t *rpnexpr, uint32_t *rpnptr, uint8_t *rpn,
case RPN_SYM: case RPN_SYM:
i = 0; i = 0;
do { do {
tzSym[i] = popbyte(); symName[i] = popbyte();
} while (tzSym[i++]); } while (symName[i++]);
// The symbol name is always written expanded // The symbol name is always written expanded
sym = sym_FindExactSymbol(tzSym); sym = sym_FindExactSymbol(symName);
if (sym_IsConstant(sym)) { if (sym_IsConstant(sym)) {
writebyte(RPN_CONST); writebyte(RPN_CONST);
value = sym_GetConstantValue(tzSym); value = sym_GetConstantValue(symName);
} else { } else {
writebyte(RPN_SYM); writebyte(RPN_SYM);
value = getSymbolID(sym); value = getSymbolID(sym);
@@ -332,11 +346,11 @@ static void writerpn(uint8_t *rpnexpr, uint32_t *rpnptr, uint8_t *rpn,
case RPN_BANK_SYM: case RPN_BANK_SYM:
i = 0; i = 0;
do { do {
tzSym[i] = popbyte(); symName[i] = popbyte();
} while (tzSym[i++]); } while (symName[i++]);
// The symbol name is always written expanded // The symbol name is always written expanded
sym = sym_FindExactSymbol(tzSym); sym = sym_FindExactSymbol(symName);
value = getSymbolID(sym); value = getSymbolID(sym);
writebyte(RPN_BANK_SYM); writebyte(RPN_BANK_SYM);
@@ -354,6 +368,22 @@ static void writerpn(uint8_t *rpnexpr, uint32_t *rpnptr, uint8_t *rpn,
} while (b != 0); } while (b != 0);
break; break;
case RPN_SIZEOF_SECT:
writebyte(RPN_SIZEOF_SECT);
do {
b = popbyte();
writebyte(b);
} while (b != 0);
break;
case RPN_STARTOF_SECT:
writebyte(RPN_STARTOF_SECT);
do {
b = popbyte();
writebyte(b);
} while (b != 0);
break;
default: default:
writebyte(rpndata); writebyte(rpndata);
break; break;
@@ -370,38 +400,38 @@ static void writerpn(uint8_t *rpnexpr, uint32_t *rpnptr, uint8_t *rpn,
static struct Patch *allocpatch(uint32_t type, struct Expression const *expr, uint32_t ofs) static struct Patch *allocpatch(uint32_t type, struct Expression const *expr, uint32_t ofs)
{ {
struct Patch *patch = malloc(sizeof(struct Patch)); struct Patch *patch = malloc(sizeof(struct Patch));
uint32_t rpnSize = expr->isKnown ? 5 : expr->nRPNPatchSize; uint32_t rpnSize = expr->isKnown ? 5 : expr->rpnPatchSize;
struct FileStackNode *node = fstk_GetFileStack(); struct FileStackNode *node = fstk_GetFileStack();
if (!patch) if (!patch)
fatalerror("No memory for patch: %s\n", strerror(errno)); fatalerror("No memory for patch: %s\n", strerror(errno));
patch->pRPN = malloc(sizeof(*patch->pRPN) * rpnSize); patch->rpn = malloc(sizeof(*patch->rpn) * rpnSize);
if (!patch->pRPN) if (!patch->rpn)
fatalerror("No memory for patch's RPN expression: %s\n", strerror(errno)); fatalerror("No memory for patch's RPN rpnSize: %s\n", strerror(errno));
patch->type = type; patch->type = type;
patch->src = node; patch->src = node;
out_RegisterNode(node); out_RegisterNode(node);
patch->lineNo = lexer_GetLineNo(); patch->lineNo = lexer_GetLineNo();
patch->nOffset = ofs; patch->offset = ofs;
patch->pcSection = sect_GetSymbolSection(); patch->pcSection = sect_GetSymbolSection();
patch->pcOffset = sect_GetSymbolOffset(); patch->pcOffset = sect_GetSymbolOffset();
/* If the expression's value is known, output a constant RPN expression directly */ /* If the rpnSize's value is known, output a constant RPN rpnSize directly */
if (expr->isKnown) { if (expr->isKnown) {
patch->nRPNSize = rpnSize; patch->rpnSize = rpnSize;
/* Make sure to update `rpnSize` above if modifying this! */ /* Make sure to update `rpnSize` above if modifying this! */
patch->pRPN[0] = RPN_CONST; patch->rpn[0] = RPN_CONST;
patch->pRPN[1] = (uint32_t)(expr->nVal) & 0xFF; patch->rpn[1] = (uint32_t)(expr->val) & 0xFF;
patch->pRPN[2] = (uint32_t)(expr->nVal) >> 8; patch->rpn[2] = (uint32_t)(expr->val) >> 8;
patch->pRPN[3] = (uint32_t)(expr->nVal) >> 16; patch->rpn[3] = (uint32_t)(expr->val) >> 16;
patch->pRPN[4] = (uint32_t)(expr->nVal) >> 24; patch->rpn[4] = (uint32_t)(expr->val) >> 24;
} else { } else {
patch->nRPNSize = 0; patch->rpnSize = 0;
writerpn(patch->pRPN, &patch->nRPNSize, expr->tRPN, expr->nRPNLength); writerpn(patch->rpn, &patch->rpnSize, expr->rpn, expr->rpnLength);
} }
assert(patch->nRPNSize == rpnSize); assert(patch->rpnSize == rpnSize);
return patch; return patch;
} }
@@ -418,8 +448,8 @@ void out_CreatePatch(uint32_t type, struct Expression const *expr, uint32_t ofs,
// before those bytes. // before those bytes.
patch->pcOffset -= pcShift; patch->pcOffset -= pcShift;
patch->next = pCurrentSection->patches; patch->next = currentSection->patches;
pCurrentSection->patches = patch; currentSection->patches = patch;
} }
/** /**
@@ -452,6 +482,13 @@ static void writeassert(struct Assertion *assert, FILE *f)
putstring(assert->message, f); putstring(assert->message, f);
} }
static void freeassert(struct Assertion *assert)
{
free(assert->patch->rpn);
free(assert->patch);
free(assert);
}
static void writeFileStackNode(struct FileStackNode const *node, FILE *f) static void writeFileStackNode(struct FileStackNode const *node, FILE *f)
{ {
putlong(node->parent ? node->parent->ID : -1, f); putlong(node->parent ? node->parent->ID : -1, f);
@@ -485,13 +522,13 @@ static void registerUnregisteredSymbol(struct Symbol *symbol, void *arg)
void out_WriteObject(void) void out_WriteObject(void)
{ {
FILE *f; FILE *f;
if (strcmp(tzObjectname, "-") != 0) if (strcmp(objectName, "-") != 0)
f = fopen(tzObjectname, "wb"); f = fopen(objectName, "wb");
else else
f = fdopen(1, "wb"); f = fdopen(1, "wb");
if (!f) if (!f)
err(1, "Couldn't write file '%s'", tzObjectname); err(1, "Couldn't write file '%s'", objectName);
/* Also write symbols that weren't written above */ /* Also write symbols that weren't written above */
sym_ForEach(registerUnregisteredSymbol, NULL); sym_ForEach(registerUnregisteredSymbol, NULL);
@@ -514,13 +551,21 @@ void out_WriteObject(void)
for (struct Symbol const *sym = objectSymbols; sym; sym = sym->next) for (struct Symbol const *sym = objectSymbols; sym; sym = sym->next)
writesymbol(sym, f); writesymbol(sym, f);
for (struct Section *sect = pSectionList; sect; sect = sect->next) for (struct Section *sect = sectionList; sect; sect = sect->next) {
writesection(sect, f); writesection(sect, f);
freesection(sect);
}
putlong(countAsserts(), f); putlong(countAsserts(), f);
for (struct Assertion *assert = assertions; assert; struct Assertion *assert = assertions;
assert = assert->next)
while (assert != NULL) {
struct Assertion *next = assert->next;
writeassert(assert, f); writeassert(assert, f);
freeassert(assert);
assert = next;
}
fclose(f); fclose(f);
} }
@@ -530,7 +575,7 @@ void out_WriteObject(void)
*/ */
void out_SetFileName(char *s) void out_SetFileName(char *s)
{ {
tzObjectname = s; objectName = s;
if (verbose) if (verbose)
printf("Output filename %s\n", s); printf("Output filename %s\n", s);
} }

File diff suppressed because it is too large Load Diff

View File

@@ -91,13 +91,13 @@ section.
The instructions in the macro-language generally require constant expressions. The instructions in the macro-language generally require constant expressions.
.Ss Numeric Formats .Ss Numeric Formats
There are a number of numeric formats. There are a number of numeric formats.
.Bl -column -offset indent "Fixed point (16.16)" "Prefix" .Bl -column -offset indent "Fixed point (Q16.16)" "Prefix"
.It Sy Format type Ta Sy Prefix Ta Sy Accepted characters .It Sy Format type Ta Sy Prefix Ta Sy Accepted characters
.It Hexadecimal Ta $ Ta 0123456789ABCDEF .It Hexadecimal Ta $ Ta 0123456789ABCDEF
.It Decimal Ta none Ta 0123456789 .It Decimal Ta none Ta 0123456789
.It Octal Ta & Ta 01234567 .It Octal Ta & Ta 01234567
.It Binary Ta % Ta 01 .It Binary Ta % Ta 01
.It Fixed point (16.16) Ta none Ta 01234.56789 .It Fixed point (Q16.16) Ta none Ta 01234.56789
.It Character constant Ta none Ta \(dqABYZ\(dq .It Character constant Ta none Ta \(dqABYZ\(dq
.It Gameboy graphics Ta \` Ta 0123 .It Gameboy graphics Ta \` Ta 0123
.El .El
@@ -394,11 +394,13 @@ Most of them return a string, however some of these functions actually return an
.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 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 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 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 No (first character is position 1, last is position -1) and Ar len No characters long. If Ar len No is not specified the substring continues to the end of Ar str .
.It Fn STRUPR str Ta Returns Ar str No with all letters in uppercase. .It Fn STRUPR str Ta Returns Ar str No with all letters in uppercase.
.It Fn STRLWR str Ta Returns Ar str No with all letters in lowercase. .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 STRRPL str old new Ta Returns Ar str No with each non-overlapping occurrence of the substring Ar old No replaced with Ar new .
.It Fn STRFMT fmt args... Ta Returns the string Ar fmt No with each .It Fn STRFMT fmt args... Ta Returns the string Ar fmt No with each
.It Fn CHARLEN str Ta Returns the number of charmap entries in Ar str No with the current charmap.
.It Fn CHARSUB str pos Ta Returns the substring for the charmap entry at Ar pos No in Ar str No (first character is position 1, last is position -1) with the current charmap.
.Ql %spec .Ql %spec
pattern replaced by interpolating the format pattern replaced by interpolating the format
.Ar spec .Ar spec
@@ -459,6 +461,12 @@ 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 SIZEOF arg Ta Returns the size of the section named
.Ar arg .
The result is not constant, since only RGBLINK can compute its value.
.It Fn STARTOF arg Ta Returns the starting address of the section named
.Ar arg .
The result is not constant, since only RGBLINK can compute its value.
.It Fn DEF symbol Ta Returns TRUE (1) if .It Fn DEF symbol Ta Returns TRUE (1) if
.Ar symbol .Ar symbol
has been defined, FALSE (0) otherwise. has been defined, FALSE (0) otherwise.
@@ -950,7 +958,7 @@ is used to define numerical constant symbols.
Unlike Unlike
.Ic SET .Ic SET
below, constants defined this way cannot be redefined. below, constants defined this way cannot be redefined.
They can, for example, be used for things such as bit definitions of hardware registers. These constants can be used for unchanging values such as properties of the hardware.
.Bd -literal -offset indent .Bd -literal -offset indent
def SCREEN_WIDTH equ 160 ;\ In pixels def SCREEN_WIDTH equ 160 ;\ In pixels
def SCREEN_HEIGHT equ 144 def SCREEN_HEIGHT equ 144
@@ -959,6 +967,26 @@ def SCREEN_HEIGHT equ 144
Note that colons Note that colons
.Ql \&: .Ql \&:
following the name are not allowed. following the name are not allowed.
.Pp
If you
.Em really
need to, the
.Ic REDEF
keyword will define or redefine a constant symbol.
This can be used, for example, to update a constant using a macro, without making it mutable in general.
.Bd -literal -offset indent
def NUM_ITEMS equ 0
MACRO add_item
redef NUM_ITEMS equ NUM_ITEMS + 1
def ITEM_{02x:NUM_ITEMS} equ \1
ENDM
add_item 1
add_item 4
add_item 9
add_item 16
assert NUM_ITEMS == 4
assert ITEM_04 == 16
.Ed
.Ss Mutable constants .Ss Mutable constants
.Ic SET , .Ic SET ,
or its synonym or its synonym
@@ -1067,7 +1095,7 @@ For example:
DEF s EQUS "Hello, " DEF s EQUS "Hello, "
REDEF s EQUS "{s}world!" REDEF s EQUS "{s}world!"
; prints "Hello, world!" ; prints "Hello, world!"
PRINTT "{s}\n" PRINTLN "{s}\n"
.Ed .Ed
.Pp .Pp
.Sy Important note : .Sy Important note :
@@ -1176,7 +1204,11 @@ ENDM
.Pp .Pp
Macro arguments support all the escape sequences of strings, as well as Macro arguments support all the escape sequences of strings, as well as
.Ql \[rs], .Ql \[rs],
to escape commas, since those otherwise separate arguments. to escape commas, as well as
.Ql \[rs](
and
.Ql \[rs])
to escape parentheses, since those otherwise separate and enclose arguments, respectively.
.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
@@ -1545,29 +1577,57 @@ which will print 5 and not 6 as you might have expected.
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
MACRO PrintMacro MACRO PrintMacro1
PRINTLN STRCAT(\[rs]1)
ENDM
PrintMacro1 "Hello "\[rs], \[rs]
"world"
MACRO PrintMacro2
PRINT \[rs]1 PRINT \[rs]1
ENDM ENDM
PrintMacro2 STRCAT("Hello ", \[rs]
PrintMacro STRCAT("Hello "\[rs], \[rs]
"world\[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 in
.Ql PrintMacro1
needs to be escaped to prevent it from starting another macro argument.
The comma in
.Ql PrintMacro2
does not need escaping because it is inside parentheses, similar to macro arguments in C.
The backslash in The backslash in
.Ql \[rs]n .Ql \[rs]n
does not need to be escaped because string literals also work as usual inside macro arguments. also does not need escaping because string literals work as usual inside 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. Since there are only nine digits, you can only access the first nine macro arguments like this.
If you want to use the rest, you need to use the To use the rest, you need to put the multi-digit argument number in angle brackets, like
.Ic SHIFT .Ql \[rs]<10> .
command. This bracketed syntax supports decimal numbers and numeric symbol names.
For example,
.Ql \[rs]<_NARG>
will get the last argument.
.Pp .Pp
Other macro arguments and symbol interpolations will be expanded inside the angle brackets.
For example, if
.Ql \[rs]1
is
.Ql 13 ,
then
.Ql \[rs]<\[rs]1>
will expand to
.Ql \[rs]<13> .
Or if
.Ql v10 = 42
and
.Ql x = 10 ,
then
.Ql \[rs]<v{d:x}>
will expand to
.Ql \[rs]<42> .
.Pp
Another way to access more than nine macro arguments is the
.Ic SHIFT .Ic SHIFT
is a special command only available in macros. command, a special command only available in macros.
Very useful in
.Ic REPT
blocks.
It will shift the arguments by one to the left, and decrease It will shift the arguments by one to the left, and decrease
.Dv _NARG .Dv _NARG
by 1. by 1.
@@ -1578,11 +1638,14 @@ will get the value of
.Ic \[rs]3 , .Ic \[rs]3 ,
and so forth. and so forth.
.Pp .Pp
This is the only way of accessing the value of arguments from 10 to 256.
.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. A negative parameter will shift the arguments in reverse.
.Pp
.Ic SHIFT
is useful in
.Ic REPT
blocks to repeat the same commands with multiple arguments.
.Ss Printing things during assembly .Ss Printing things during assembly
The The
.Ic PRINT .Ic PRINT
@@ -1883,16 +1946,29 @@ can be used to change some of the options during assembling from within the sour
takes a comma-separated list of options as its argument: takes a comma-separated list of options as its argument:
.Bd -literal -offset indent .Bd -literal -offset indent
PUSHO PUSHO
OPT g.oOX ;Set the GB graphics constants to use these characters OPT g.oOX, Wdiv, L ; acts like command-line -g.oOX -Wdiv -L
DW `..ooOOXX DW `..ooOOXX ; uses the graphics constant characters from OPT g
PRINTLN $80000000/-1 ; prints a warning about division
LD [$FF88], A ; encoded as LD, not LDH
POPO POPO
DW `00112233 DW `00112233 ; uses the default graphics constant characters
PRINTLN $80000000/-1 ; no warning by default
LD [$FF88], A ; optimized to use LDH by default
.Ed .Ed
.Pp .Pp
The options that OPT can modify are currently: The options that OPT can modify are currently:
.Cm b , g .Cm b , g , p , h , L ,
and and
.Cm p . .Cm W .
The Boolean flag options
.Cm h
and
.Cm L
can be negated as
.Ql OPT !h
and
.Ql OPT !L
to act like omitting them from the command-line.
.Pp .Pp
.Ic POPO .Ic POPO
and and

View File

@@ -13,6 +13,7 @@
#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 <stdlib.h> #include <stdlib.h>
@@ -45,47 +46,47 @@
static uint8_t *reserveSpace(struct Expression *expr, uint32_t size) static uint8_t *reserveSpace(struct Expression *expr, uint32_t size)
{ {
/* This assumes the RPN length is always less than the capacity */ /* This assumes the RPN length is always less than the capacity */
if (expr->nRPNCapacity - expr->nRPNLength < size) { if (expr->rpnCapacity - expr->rpnLength < size) {
/* If there isn't enough room to reserve the space, realloc */ /* If there isn't enough room to reserve the space, realloc */
if (!expr->tRPN) if (!expr->rpn)
expr->nRPNCapacity = 256; /* Initial size */ expr->rpnCapacity = 256; /* Initial size */
while (expr->nRPNCapacity - expr->nRPNLength < size) { while (expr->rpnCapacity - expr->rpnLength < size) {
if (expr->nRPNCapacity >= MAXRPNLEN) if (expr->rpnCapacity >= MAXRPNLEN)
/* /*
* To avoid generating humongous object files, cap the * To avoid generating humongous object files, cap the
* size of RPN expressions * size of RPN expressions
*/ */
fatalerror("RPN expression cannot grow larger than " fatalerror("RPN expression cannot grow larger than "
EXPAND_AND_STR(MAXRPNLEN) " bytes\n"); EXPAND_AND_STR(MAXRPNLEN) " bytes\n");
else if (expr->nRPNCapacity > MAXRPNLEN / 2) else if (expr->rpnCapacity > MAXRPNLEN / 2)
expr->nRPNCapacity = MAXRPNLEN; expr->rpnCapacity = MAXRPNLEN;
else else
expr->nRPNCapacity *= 2; expr->rpnCapacity *= 2;
} }
expr->tRPN = realloc(expr->tRPN, expr->nRPNCapacity); expr->rpn = realloc(expr->rpn, expr->rpnCapacity);
if (!expr->tRPN) if (!expr->rpn)
fatalerror("Failed to grow RPN expression: %s\n", strerror(errno)); fatalerror("Failed to grow RPN expression: %s\n", strerror(errno));
} }
uint8_t *ptr = expr->tRPN + expr->nRPNLength; uint8_t *ptr = expr->rpn + expr->rpnLength;
expr->nRPNLength += size; expr->rpnLength += size;
return ptr; return ptr;
} }
/* /*
* Init the RPN expression * Init a RPN expression
*/ */
static void rpn_Init(struct Expression *expr) static void rpn_Init(struct Expression *expr)
{ {
expr->reason = NULL; expr->reason = NULL;
expr->isKnown = true; expr->isKnown = true;
expr->isSymbol = false; expr->isSymbol = false;
expr->tRPN = NULL; expr->rpn = NULL;
expr->nRPNCapacity = 0; expr->rpnCapacity = 0;
expr->nRPNLength = 0; expr->rpnLength = 0;
expr->nRPNPatchSize = 0; expr->rpnPatchSize = 0;
} }
/* /*
@@ -93,7 +94,7 @@ static void rpn_Init(struct Expression *expr)
*/ */
void rpn_Free(struct Expression *expr) void rpn_Free(struct Expression *expr)
{ {
free(expr->tRPN); free(expr->rpn);
free(expr->reason); free(expr->reason);
rpn_Init(expr); rpn_Init(expr);
} }
@@ -104,12 +105,12 @@ void rpn_Free(struct Expression *expr)
void rpn_Number(struct Expression *expr, uint32_t i) void rpn_Number(struct Expression *expr, uint32_t i)
{ {
rpn_Init(expr); rpn_Init(expr);
expr->nVal = i; expr->val = i;
} }
void rpn_Symbol(struct Expression *expr, char const *tzSym) void rpn_Symbol(struct Expression *expr, char const *symName)
{ {
struct Symbol *sym = sym_FindScopedSymbol(tzSym); struct Symbol *sym = sym_FindScopedSymbol(symName);
if (sym_IsPC(sym) && !sect_GetSymbolSection()) { if (sym_IsPC(sym) && !sect_GetSymbolSection()) {
error("PC has no value outside a section\n"); error("PC has no value outside a section\n");
@@ -119,16 +120,16 @@ void rpn_Symbol(struct Expression *expr, char const *tzSym)
expr->isSymbol = true; expr->isSymbol = true;
makeUnknown(expr, sym_IsPC(sym) ? "PC is not constant at assembly time" makeUnknown(expr, sym_IsPC(sym) ? "PC is not constant at assembly time"
: "'%s' is not constant at assembly time", tzSym); : "'%s' is not constant at assembly time", symName);
sym = sym_Ref(tzSym); sym = sym_Ref(symName);
expr->nRPNPatchSize += 5; /* 1-byte opcode + 4-byte symbol ID */ expr->rpnPatchSize += 5; /* 1-byte opcode + 4-byte symbol ID */
size_t nameLen = strlen(sym->name) + 1; /* Don't forget NUL! */ size_t nameLen = strlen(sym->name) + 1; /* Don't forget NUL! */
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);
} else { } else {
rpn_Number(expr, sym_GetConstantValue(tzSym)); rpn_Number(expr, sym_GetConstantValue(symName));
} }
} }
@@ -136,21 +137,21 @@ void rpn_BankSelf(struct Expression *expr)
{ {
rpn_Init(expr); rpn_Init(expr);
if (!pCurrentSection) { if (!currentSection) {
error("PC has no bank outside a section\n"); error("PC has no bank outside a section\n");
expr->nVal = 1; expr->val = 1;
} else if (pCurrentSection->bank == (uint32_t)-1) { } else if (currentSection->bank == (uint32_t)-1) {
makeUnknown(expr, "Current section's bank is not known"); makeUnknown(expr, "Current section's bank is not known");
expr->nRPNPatchSize++; expr->rpnPatchSize++;
*reserveSpace(expr, 1) = RPN_BANK_SELF; *reserveSpace(expr, 1) = RPN_BANK_SELF;
} else { } else {
expr->nVal = pCurrentSection->bank; expr->val = currentSection->bank;
} }
} }
void rpn_BankSymbol(struct Expression *expr, char const *tzSym) void rpn_BankSymbol(struct Expression *expr, char const *symName)
{ {
struct Symbol const *sym = sym_FindScopedSymbol(tzSym); struct Symbol const *sym = sym_FindScopedSymbol(symName);
/* The @ symbol is treated differently. */ /* The @ symbol is treated differently. */
if (sym_IsPC(sym)) { if (sym_IsPC(sym)) {
@@ -162,15 +163,15 @@ void rpn_BankSymbol(struct Expression *expr, char const *tzSym)
if (sym && !sym_IsLabel(sym)) { if (sym && !sym_IsLabel(sym)) {
error("BANK argument must be a label\n"); error("BANK argument must be a label\n");
} else { } else {
sym = sym_Ref(tzSym); sym = sym_Ref(symName);
assert(sym); // If the symbol didn't exist, it should have been created assert(sym); // If the symbol didn't exist, it should have been created
if (sym_GetSection(sym) && sym_GetSection(sym)->bank != (uint32_t)-1) { if (sym_GetSection(sym) && sym_GetSection(sym)->bank != (uint32_t)-1) {
/* Symbol's section is known and bank is fixed */ /* Symbol's section is known and bank is fixed */
expr->nVal = sym_GetSection(sym)->bank; expr->val = sym_GetSection(sym)->bank;
} else { } else {
makeUnknown(expr, "\"%s\"'s bank is not known", tzSym); makeUnknown(expr, "\"%s\"'s bank is not known", symName);
expr->nRPNPatchSize += 5; /* opcode + 4-byte sect ID */ expr->rpnPatchSize += 5; /* opcode + 4-byte sect ID */
size_t nameLen = strlen(sym->name) + 1; /* Room for NUL! */ size_t nameLen = strlen(sym->name) + 1; /* Room for NUL! */
uint8_t *ptr = reserveSpace(expr, nameLen + 1); uint8_t *ptr = reserveSpace(expr, nameLen + 1);
@@ -180,40 +181,67 @@ void rpn_BankSymbol(struct Expression *expr, char const *tzSym)
} }
} }
void rpn_BankSection(struct Expression *expr, char const *tzSectionName) void rpn_BankSection(struct Expression *expr, char const *sectionName)
{ {
rpn_Init(expr); rpn_Init(expr);
struct Section *pSection = out_FindSectionByName(tzSectionName); struct Section *section = sect_FindSectionByName(sectionName);
if (pSection && pSection->bank != (uint32_t)-1) { if (section && section->bank != (uint32_t)-1) {
expr->nVal = pSection->bank; expr->val = section->bank;
} else { } else {
makeUnknown(expr, "Section \"%s\"'s bank is not known", makeUnknown(expr, "Section \"%s\"'s bank is not known", sectionName);
tzSectionName);
size_t nameLen = strlen(tzSectionName) + 1; /* Room for NUL! */ size_t nameLen = strlen(sectionName) + 1; /* Room for NUL! */
uint8_t *ptr = reserveSpace(expr, nameLen + 1); uint8_t *ptr = reserveSpace(expr, nameLen + 1);
expr->nRPNPatchSize += nameLen + 1; expr->rpnPatchSize += nameLen + 1;
*ptr++ = RPN_BANK_SECT; *ptr++ = RPN_BANK_SECT;
memcpy(ptr, tzSectionName, nameLen); memcpy(ptr, sectionName, nameLen);
} }
} }
void rpn_SizeOfSection(struct Expression *expr, char const *sectionName)
{
rpn_Init(expr);
makeUnknown(expr, "Section \"%s\"'s size is not known", sectionName);
size_t nameLen = strlen(sectionName) + 1; /* Room for NUL! */
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
expr->rpnPatchSize += nameLen + 1;
*ptr++ = RPN_SIZEOF_SECT;
memcpy(ptr, sectionName, nameLen);
}
void rpn_StartOfSection(struct Expression *expr, char const *sectionName)
{
rpn_Init(expr);
makeUnknown(expr, "Section \"%s\"'s start is not known", sectionName);
size_t nameLen = strlen(sectionName) + 1; /* Room for NUL! */
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
expr->rpnPatchSize += nameLen + 1;
*ptr++ = RPN_STARTOF_SECT;
memcpy(ptr, sectionName, nameLen);
}
void rpn_CheckHRAM(struct Expression *expr, const struct Expression *src) void rpn_CheckHRAM(struct Expression *expr, const struct Expression *src)
{ {
*expr = *src; *expr = *src;
expr->isSymbol = false; expr->isSymbol = false;
if (!rpn_isKnown(expr)) { if (!rpn_isKnown(expr)) {
expr->nRPNPatchSize++; expr->rpnPatchSize++;
*reserveSpace(expr, 1) = RPN_HRAM; *reserveSpace(expr, 1) = RPN_HRAM;
} else if (expr->nVal >= 0xFF00 && expr->nVal <= 0xFFFF) { } else if (expr->val >= 0xFF00 && expr->val <= 0xFFFF) {
/* That range is valid, but only keep the lower byte */ /* That range is valid, but only keep the lower byte */
expr->nVal &= 0xFF; expr->val &= 0xFF;
} else if (expr->nVal < 0 || expr->nVal > 0xFF) { } else if (expr->val < 0 || expr->val > 0xFF) {
error("Source address $%" PRIx32 " not between $FF00 to $FFFF\n", expr->nVal); error("Source address $%" PRIx32 " not between $FF00 to $FFFF\n", expr->val);
} }
} }
@@ -223,25 +251,50 @@ void rpn_CheckRST(struct Expression *expr, const struct Expression *src)
if (rpn_isKnown(expr)) { if (rpn_isKnown(expr)) {
/* A valid RST address must be masked with 0x38 */ /* A valid RST address must be masked with 0x38 */
if (expr->nVal & ~0x38) if (expr->val & ~0x38)
error("Invalid address $%" PRIx32 " for RST\n", expr->nVal); error("Invalid address $%" PRIx32 " for RST\n", expr->val);
/* The target is in the "0x38" bits, all other bits are set */ /* The target is in the "0x38" bits, all other bits are set */
expr->nVal |= 0xC7; expr->val |= 0xC7;
} else { } else {
expr->nRPNPatchSize++; expr->rpnPatchSize++;
*reserveSpace(expr, 1) = RPN_RST; *reserveSpace(expr, 1) = RPN_RST;
} }
} }
/*
* Checks that an RPN expression's value fits within N bits (signed or unsigned)
*/
void rpn_CheckNBit(struct Expression const *expr, uint8_t n)
{
assert(n != 0); // That doesn't make sense
assert(n < CHAR_BIT * sizeof(int)); // Otherwise `1 << n` is UB
if (rpn_isKnown(expr)) {
int32_t val = expr->val;
if (val < -(1 << (n - 1)) || val >= 1 << n)
warning(WARNING_TRUNCATION, "Expression must be %u-bit\n", n);
}
}
int32_t rpn_GetConstVal(struct Expression const *expr)
{
if (!rpn_isKnown(expr)) {
error("Expected constant expression: %s\n", expr->reason);
return 0;
}
return expr->val;
}
void rpn_LOGNOT(struct Expression *expr, const struct Expression *src) void rpn_LOGNOT(struct Expression *expr, const struct Expression *src)
{ {
*expr = *src; *expr = *src;
expr->isSymbol = false; expr->isSymbol = false;
if (rpn_isKnown(expr)) { if (rpn_isKnown(expr)) {
expr->nVal = !expr->nVal; expr->val = !expr->val;
} else { } else {
expr->nRPNPatchSize++; expr->rpnPatchSize++;
*reserveSpace(expr, 1) = RPN_LOGUNNOT; *reserveSpace(expr, 1) = RPN_LOGUNNOT;
} }
} }
@@ -250,7 +303,7 @@ struct Symbol const *rpn_SymbolOf(struct Expression const *expr)
{ {
if (!rpn_isSymbol(expr)) if (!rpn_isSymbol(expr))
return NULL; return NULL;
return sym_FindScopedSymbol((char *)expr->tRPN + 1); return sym_FindScopedSymbol((char *)expr->rpn + 1);
} }
bool rpn_IsDiffConstant(struct Expression const *src, struct Symbol const *sym) bool rpn_IsDiffConstant(struct Expression const *src, struct Symbol const *sym)
@@ -283,111 +336,110 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
rpn_Init(expr); /* Init the expression to something sane */ rpn_Init(expr); /* Init the expression to something sane */
/* If both expressions are known, just compute the value */ /* If both expressions are known, just compute the value */
uint32_t uleft = src1->nVal, uright = src2->nVal; uint32_t uleft = src1->val, uright = src2->val;
switch (op) { switch (op) {
case RPN_LOGOR: case RPN_LOGOR:
expr->nVal = src1->nVal || src2->nVal; expr->val = src1->val || src2->val;
break; break;
case RPN_LOGAND: case RPN_LOGAND:
expr->nVal = src1->nVal && src2->nVal; expr->val = src1->val && src2->val;
break; break;
case RPN_LOGEQ: case RPN_LOGEQ:
expr->nVal = src1->nVal == src2->nVal; expr->val = src1->val == src2->val;
break; break;
case RPN_LOGGT: case RPN_LOGGT:
expr->nVal = src1->nVal > src2->nVal; expr->val = src1->val > src2->val;
break; break;
case RPN_LOGLT: case RPN_LOGLT:
expr->nVal = src1->nVal < src2->nVal; expr->val = src1->val < src2->val;
break; break;
case RPN_LOGGE: case RPN_LOGGE:
expr->nVal = src1->nVal >= src2->nVal; expr->val = src1->val >= src2->val;
break; break;
case RPN_LOGLE: case RPN_LOGLE:
expr->nVal = src1->nVal <= src2->nVal; expr->val = src1->val <= src2->val;
break; break;
case RPN_LOGNE: case RPN_LOGNE:
expr->nVal = src1->nVal != src2->nVal; expr->val = src1->val != src2->val;
break; break;
case RPN_ADD: case RPN_ADD:
expr->nVal = uleft + uright; expr->val = uleft + uright;
break; break;
case RPN_SUB: case RPN_SUB:
expr->nVal = uleft - uright; expr->val = uleft - uright;
break; break;
case RPN_XOR: case RPN_XOR:
expr->nVal = src1->nVal ^ src2->nVal; expr->val = src1->val ^ src2->val;
break; break;
case RPN_OR: case RPN_OR:
expr->nVal = src1->nVal | src2->nVal; expr->val = src1->val | src2->val;
break; break;
case RPN_AND: case RPN_AND:
expr->nVal = src1->nVal & src2->nVal; expr->val = src1->val & src2->val;
break; break;
case RPN_SHL: case RPN_SHL:
if (src2->nVal < 0) if (src2->val < 0)
warning(WARNING_SHIFT_AMOUNT, warning(WARNING_SHIFT_AMOUNT,
"Shifting left by negative amount %" PRId32 "\n", "Shifting left by negative amount %" PRId32 "\n",
src2->nVal); src2->val);
if (src2->nVal >= 32) if (src2->val >= 32)
warning(WARNING_SHIFT_AMOUNT, warning(WARNING_SHIFT_AMOUNT,
"Shifting left by large amount %" PRId32 "\n", "Shifting left by large amount %" PRId32 "\n", src2->val);
src2->nVal);
expr->nVal = op_shift_left(src1->nVal, src2->nVal); expr->val = op_shift_left(src1->val, src2->val);
break; break;
case RPN_SHR: case RPN_SHR:
if (src1->nVal < 0) if (src1->val < 0)
warning(WARNING_SHIFT, "Shifting right negative value %" warning(WARNING_SHIFT,
PRId32 "\n", "Shifting right negative value %" PRId32 "\n", src1->val);
src1->nVal);
if (src2->nVal < 0) if (src2->val < 0)
warning(WARNING_SHIFT_AMOUNT, warning(WARNING_SHIFT_AMOUNT,
"Shifting right by negative amount %" PRId32 "\n", "Shifting right by negative amount %" PRId32 "\n",
src2->nVal); src2->val);
if (src2->nVal >= 32) if (src2->val >= 32)
warning(WARNING_SHIFT_AMOUNT, warning(WARNING_SHIFT_AMOUNT,
"Shifting right by large amount %" PRId32 "\n", "Shifting right by large amount %" PRId32 "\n",
src2->nVal); src2->val);
expr->nVal = op_shift_right(src1->nVal, src2->nVal); expr->val = op_shift_right(src1->val, src2->val);
break; break;
case RPN_MUL: case RPN_MUL:
expr->nVal = uleft * uright; expr->val = uleft * uright;
break; break;
case RPN_DIV: case RPN_DIV:
if (src2->nVal == 0) if (src2->val == 0)
fatalerror("Division by zero\n"); fatalerror("Division by zero\n");
if (src1->nVal == INT32_MIN && src2->nVal == -1) { if (src1->val == INT32_MIN && src2->val == -1) {
warning(WARNING_DIV, "Division of %" PRId32 " by -1 yields %" warning(WARNING_DIV,
PRId32 "\n", INT32_MIN, INT32_MIN); "Division of %" PRId32 " by -1 yields %" PRId32 "\n",
expr->nVal = INT32_MIN; INT32_MIN, INT32_MIN);
expr->val = INT32_MIN;
} else { } else {
expr->nVal = op_divide(src1->nVal, src2->nVal); expr->val = op_divide(src1->val, src2->val);
} }
break; break;
case RPN_MOD: case RPN_MOD:
if (src2->nVal == 0) if (src2->val == 0)
fatalerror("Modulo by zero\n"); fatalerror("Modulo by zero\n");
if (src1->nVal == INT32_MIN && src2->nVal == -1) if (src1->val == INT32_MIN && src2->val == -1)
expr->nVal = 0; expr->val = 0;
else else
expr->nVal = op_modulo(src1->nVal, src2->nVal); expr->val = op_modulo(src1->val, src2->val);
break; break;
case RPN_EXP: case RPN_EXP:
if (src2->nVal < 0) if (src2->val < 0)
fatalerror("Exponentiation by negative power\n"); fatalerror("Exponentiation by negative power\n");
if (src1->nVal == INT32_MIN && src2->nVal == -1) if (src1->val == INT32_MIN && src2->val == -1)
expr->nVal = 0; expr->val = 0;
else else
expr->nVal = op_exponent(src1->nVal, src2->nVal); expr->val = op_exponent(src1->val, src2->val);
break; break;
case RPN_UNSUB: case RPN_UNSUB:
@@ -396,6 +448,8 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
case RPN_BANK_SYM: case RPN_BANK_SYM:
case RPN_BANK_SECT: case RPN_BANK_SECT:
case RPN_BANK_SELF: case RPN_BANK_SELF:
case RPN_SIZEOF_SECT:
case RPN_STARTOF_SECT:
case RPN_HRAM: case RPN_HRAM:
case RPN_RST: case RPN_RST:
case RPN_CONST: case RPN_CONST:
@@ -407,20 +461,20 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
struct Symbol const *symbol1 = rpn_SymbolOf(src1); struct Symbol const *symbol1 = rpn_SymbolOf(src1);
struct Symbol const *symbol2 = rpn_SymbolOf(src2); struct Symbol const *symbol2 = rpn_SymbolOf(src2);
expr->nVal = sym_GetValue(symbol1) - sym_GetValue(symbol2); expr->val = sym_GetValue(symbol1) - sym_GetValue(symbol2);
expr->isKnown = true; expr->isKnown = true;
} else { } else {
/* If it's not known, start computing the RPN expression */ /* If it's not known, start computing the RPN expression */
/* Convert the left-hand expression if it's constant */ /* Convert the left-hand expression if it's constant */
if (src1->isKnown) { if (src1->isKnown) {
uint32_t lval = src1->nVal; uint32_t lval = src1->val;
uint8_t bytes[] = {RPN_CONST, lval, lval >> 8, uint8_t bytes[] = {RPN_CONST, lval, lval >> 8,
lval >> 16, lval >> 24}; lval >> 16, lval >> 24};
expr->nRPNPatchSize = sizeof(bytes); expr->rpnPatchSize = sizeof(bytes);
expr->tRPN = NULL; expr->rpn = NULL;
expr->nRPNCapacity = 0; expr->rpnCapacity = 0;
expr->nRPNLength = 0; expr->rpnLength = 0;
memcpy(reserveSpace(expr, sizeof(bytes)), bytes, memcpy(reserveSpace(expr, sizeof(bytes)), bytes,
sizeof(bytes)); sizeof(bytes));
@@ -429,21 +483,21 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
free(src1->reason); free(src1->reason);
} else { } else {
/* Otherwise just reuse its RPN buffer */ /* Otherwise just reuse its RPN buffer */
expr->nRPNPatchSize = src1->nRPNPatchSize; expr->rpnPatchSize = src1->rpnPatchSize;
expr->tRPN = src1->tRPN; expr->rpn = src1->rpn;
expr->nRPNCapacity = src1->nRPNCapacity; expr->rpnCapacity = src1->rpnCapacity;
expr->nRPNLength = src1->nRPNLength; expr->rpnLength = src1->rpnLength;
expr->reason = src1->reason; expr->reason = src1->reason;
free(src2->reason); free(src2->reason);
} }
/* Now, merge the right expression into the left one */ /* Now, merge the right expression into the left one */
uint8_t *ptr = src2->tRPN; /* Pointer to the right RPN */ uint8_t *ptr = src2->rpn; /* Pointer to the right RPN */
uint32_t len = src2->nRPNLength; /* Size of the right RPN */ uint32_t len = src2->rpnLength; /* Size of the right RPN */
uint32_t patchSize = src2->nRPNPatchSize; uint32_t patchSize = src2->rpnPatchSize;
/* If the right expression is constant, merge a shim instead */ /* If the right expression is constant, merge a shim instead */
uint32_t rval = src2->nVal; uint32_t rval = src2->val;
uint8_t bytes[] = {RPN_CONST, rval, rval >> 8, rval >> 16, uint8_t bytes[] = {RPN_CONST, rval, rval >> 8, rval >> 16,
rval >> 24}; rval >> 24};
if (src2->isKnown) { if (src2->isKnown) {
@@ -457,8 +511,8 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
memcpy(buf, ptr, len); memcpy(buf, ptr, len);
buf[len] = op; buf[len] = op;
free(src2->tRPN); /* If there was none, this is `free(NULL)` */ free(src2->rpn); /* If there was none, this is `free(NULL)` */
expr->nRPNPatchSize += patchSize + 1; expr->rpnPatchSize += patchSize + 1;
} }
} }
@@ -468,11 +522,11 @@ void rpn_HIGH(struct Expression *expr, const struct Expression *src)
expr->isSymbol = false; expr->isSymbol = false;
if (rpn_isKnown(expr)) { if (rpn_isKnown(expr)) {
expr->nVal = (uint32_t)expr->nVal >> 8 & 0xFF; expr->val = (uint32_t)expr->val >> 8 & 0xFF;
} else { } else {
uint8_t bytes[] = {RPN_CONST, 8, 0, 0, 0, RPN_SHR, uint8_t bytes[] = {RPN_CONST, 8, 0, 0, 0, RPN_SHR,
RPN_CONST, 0xFF, 0, 0, 0, RPN_AND}; RPN_CONST, 0xFF, 0, 0, 0, RPN_AND};
expr->nRPNPatchSize += sizeof(bytes); expr->rpnPatchSize += sizeof(bytes);
memcpy(reserveSpace(expr, sizeof(bytes)), bytes, sizeof(bytes)); memcpy(reserveSpace(expr, sizeof(bytes)), bytes, sizeof(bytes));
} }
} }
@@ -483,11 +537,11 @@ void rpn_LOW(struct Expression *expr, const struct Expression *src)
expr->isSymbol = false; expr->isSymbol = false;
if (rpn_isKnown(expr)) { if (rpn_isKnown(expr)) {
expr->nVal = expr->nVal & 0xFF; expr->val = expr->val & 0xFF;
} else { } else {
uint8_t bytes[] = {RPN_CONST, 0xFF, 0, 0, 0, RPN_AND}; uint8_t bytes[] = {RPN_CONST, 0xFF, 0, 0, 0, RPN_AND};
expr->nRPNPatchSize += sizeof(bytes); expr->rpnPatchSize += sizeof(bytes);
memcpy(reserveSpace(expr, sizeof(bytes)), bytes, sizeof(bytes)); memcpy(reserveSpace(expr, sizeof(bytes)), bytes, sizeof(bytes));
} }
} }
@@ -495,7 +549,7 @@ void rpn_LOW(struct Expression *expr, const struct Expression *src)
void rpn_ISCONST(struct Expression *expr, const struct Expression *src) void rpn_ISCONST(struct Expression *expr, const struct Expression *src)
{ {
rpn_Init(expr); rpn_Init(expr);
expr->nVal = rpn_isKnown(src); expr->val = rpn_isKnown(src);
expr->isKnown = true; expr->isKnown = true;
expr->isSymbol = false; expr->isSymbol = false;
} }
@@ -506,9 +560,9 @@ void rpn_UNNEG(struct Expression *expr, const struct Expression *src)
expr->isSymbol = false; expr->isSymbol = false;
if (rpn_isKnown(expr)) { if (rpn_isKnown(expr)) {
expr->nVal = -(uint32_t)expr->nVal; expr->val = -(uint32_t)expr->val;
} else { } else {
expr->nRPNPatchSize++; expr->rpnPatchSize++;
*reserveSpace(expr, 1) = RPN_UNSUB; *reserveSpace(expr, 1) = RPN_UNSUB;
} }
} }
@@ -519,9 +573,9 @@ void rpn_UNNOT(struct Expression *expr, const struct Expression *src)
expr->isSymbol = false; expr->isSymbol = false;
if (rpn_isKnown(expr)) { if (rpn_isKnown(expr)) {
expr->nVal = ~expr->nVal; expr->val = ~expr->val;
} else { } else {
expr->nRPNPatchSize++; expr->rpnPatchSize++;
*reserveSpace(expr, 1) = RPN_UNNOT; *reserveSpace(expr, 1) = RPN_UNNOT;
} }
} }

View File

@@ -22,13 +22,16 @@ uint8_t fillByte;
struct SectionStackEntry { struct SectionStackEntry {
struct Section *section; struct Section *section;
struct Section *loadSection;
char const *scope; /* Section's symbol scope */ char const *scope; /* Section's symbol scope */
uint32_t offset; uint32_t offset;
int32_t loadOffset;
struct SectionStackEntry *next; struct SectionStackEntry *next;
}; };
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) */
struct Section *currentSection = NULL;
static struct Section *currentLoadSection = NULL; static struct Section *currentLoadSection = NULL;
int32_t loadOffset; /* Offset into the LOAD section's parent (see sect_GetOutputOffset) */ int32_t loadOffset; /* Offset into the LOAD section's parent (see sect_GetOutputOffset) */
@@ -41,53 +44,74 @@ struct UnionStackEntry {
/* /*
* A quick check to see if we have an initialized section * A quick check to see if we have an initialized section
*/ */
static void checksection(void) attr_(warn_unused_result) static bool checksection(void)
{ {
if (pCurrentSection == NULL) if (currentSection)
fatalerror("Code generation before SECTION directive\n"); return true;
error("Cannot output data outside of a SECTION\n");
return false;
} }
/* /*
* A quick check to see if we have an initialized section that can contain * A quick check to see if we have an initialized section that can contain
* this much initialized data * this much initialized data
*/ */
static void checkcodesection(void) attr_(warn_unused_result) static bool checkcodesection(void)
{ {
checksection(); if (!checksection())
return false;
if (!sect_HasData(pCurrentSection->type)) if (sect_HasData(currentSection->type))
fatalerror("Section '%s' cannot contain code or data (not ROM0 or ROMX)\n", return true;
pCurrentSection->name);
error("Section '%s' cannot contain code or data (not ROM0 or ROMX)\n",
currentSection->name);
return false;
} }
static void checkSectionSize(struct Section const *sect, uint32_t size) attr_(warn_unused_result) static bool checkSectionSize(struct Section const *sect, uint32_t size)
{ {
uint32_t maxSize = maxsize[sect->type]; uint32_t maxSize = maxsize[sect->type];
if (size > maxSize) // If the new size is reasonable, keep going
fatalerror("Section '%s' grew too big (max size = 0x%" PRIX32 if (size <= maxSize)
return true;
error("Section '%s' grew too big (max size = 0x%" PRIX32
" bytes, reached 0x%" PRIX32 ").\n", sect->name, maxSize, size); " bytes, reached 0x%" PRIX32 ").\n", sect->name, maxSize, size);
return false;
} }
/* /*
* Check if the section has grown too much. * Check if the section has grown too much.
*/ */
static void reserveSpace(uint32_t delta_size) attr_(warn_unused_result) static bool reserveSpace(uint32_t delta_size)
{ {
/* /*
* This check is here to trap broken code that generates sections that * This check is here to trap broken code that generates sections that are too big and to
* are too big and to prevent the assembler from generating huge object * prevent the assembler from generating huge object files or trying to allocate too much
* files or trying to allocate too much memory. * memory.
* A check at the linking stage is still necessary. * A check at the linking stage is still necessary.
*/ */
checkSectionSize(pCurrentSection, curOffset + loadOffset + delta_size);
if (currentLoadSection) // If the section has already overflowed, skip the check to avoid erroring out ad nauseam
checkSectionSize(currentLoadSection, curOffset + delta_size); if (currentSection->size != UINT32_MAX
&& !checkSectionSize(currentSection, curOffset + loadOffset + delta_size))
// Mark the section as overflowed, to avoid repeating the error
currentSection->size = UINT32_MAX;
if (currentLoadSection && currentLoadSection->size != UINT32_MAX
&& !checkSectionSize(currentLoadSection, curOffset + delta_size))
currentLoadSection->size = UINT32_MAX;
return currentSection->size != UINT32_MAX
&& (!currentLoadSection || currentLoadSection->size != UINT32_MAX);
} }
struct Section *out_FindSectionByName(const char *name) struct Section *sect_FindSectionByName(const char *name)
{ {
for (struct Section *sect = pSectionList; sect; sect = sect->next) { for (struct Section *sect = sectionList; sect; sect = sect->next) {
if (strcmp(name, sect->name) == 0) if (strcmp(name, sect->name) == 0)
return sect; return sect;
} }
@@ -351,15 +375,15 @@ static struct Section *getSection(char const *name, enum SectionType type, uint3
// Check if another section exists with the same name; merge if yes, otherwise create one // Check if another section exists with the same name; merge if yes, otherwise create one
struct Section *sect = out_FindSectionByName(name); struct Section *sect = sect_FindSectionByName(name);
if (sect) { if (sect) {
mergeSections(sect, type, org, bank, alignment, alignOffset, mod); mergeSections(sect, type, org, bank, alignment, alignOffset, mod);
} else { } else {
sect = createSection(name, type, org, bank, alignment, alignOffset, mod); sect = createSection(name, type, org, bank, alignment, alignOffset, mod);
// Add the new section to the list (order doesn't matter) // Add the new section to the list (order doesn't matter)
sect->next = pSectionList; sect->next = sectionList;
pSectionList = sect; sectionList = sect;
} }
return sect; return sect;
@@ -379,7 +403,7 @@ static void changeSection(void)
/* /*
* Set the current section by name and type * Set the current section by name and type
*/ */
void out_NewSection(char const *name, uint32_t type, uint32_t org, void sect_NewSection(char const *name, uint32_t type, uint32_t org,
struct SectionSpec const *attribs, enum SectionModifier mod) struct SectionSpec const *attribs, enum SectionModifier mod)
{ {
if (currentLoadSection) if (currentLoadSection)
@@ -394,23 +418,32 @@ void out_NewSection(char const *name, uint32_t type, uint32_t org,
changeSection(); changeSection();
curOffset = mod == SECTION_UNION ? 0 : sect->size; curOffset = mod == SECTION_UNION ? 0 : sect->size;
pCurrentSection = sect; currentSection = sect;
} }
/* /*
* 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 sect_SetLoadSection(char const *name, uint32_t type, uint32_t org,
struct SectionSpec const *attribs, struct SectionSpec const *attribs, enum SectionModifier mod)
enum SectionModifier mod)
{ {
checkcodesection(); if (!checkcodesection())
return;
if (currentLoadSection) if (currentLoadSection) {
fatalerror("`LOAD` blocks cannot be nested\n"); error("`LOAD` blocks cannot be nested\n");
return;
}
if (sect_HasData(type)) if (sect_HasData(type)) {
error("`LOAD` blocks cannot create a ROM section\n"); error("`LOAD` blocks cannot create a ROM section\n");
return;
}
if (mod == SECTION_FRAGMENT) {
error("`LOAD FRAGMENT` is not allowed\n");
return;
}
struct Section *sect = getSection(name, type, org, attribs, mod); struct Section *sect = getSection(name, type, org, attribs, mod);
@@ -420,10 +453,12 @@ void out_SetLoadSection(char const *name, uint32_t type, uint32_t org,
currentLoadSection = sect; currentLoadSection = sect;
} }
void out_EndLoadSection(void) void sect_EndLoadSection(void)
{ {
if (!currentLoadSection) if (!currentLoadSection) {
error("Found `ENDL` outside of a `LOAD` block\n"); error("Found `ENDL` outside of a `LOAD` block\n");
return;
}
changeSection(); changeSection();
curOffset += loadOffset; curOffset += loadOffset;
@@ -433,7 +468,7 @@ void out_EndLoadSection(void)
struct Section *sect_GetSymbolSection(void) struct Section *sect_GetSymbolSection(void)
{ {
return currentLoadSection ? currentLoadSection : pCurrentSection; return currentLoadSection ? currentLoadSection : currentSection;
} }
/* /*
@@ -451,7 +486,9 @@ uint32_t sect_GetOutputOffset(void)
void sect_AlignPC(uint8_t alignment, uint16_t offset) void sect_AlignPC(uint8_t alignment, uint16_t offset)
{ {
checksection(); if (!checksection())
return;
struct Section *sect = sect_GetSymbolSection(); struct Section *sect = sect_GetSymbolSection();
uint16_t alignSize = 1 << alignment; // Size of an aligned "block" uint16_t alignSize = 1 << alignment; // Size of an aligned "block"
@@ -477,15 +514,15 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset)
static void growSection(uint32_t growth) static void growSection(uint32_t growth)
{ {
curOffset += growth; curOffset += growth;
if (curOffset + loadOffset > pCurrentSection->size) if (curOffset + loadOffset > currentSection->size)
pCurrentSection->size = curOffset + loadOffset; currentSection->size = curOffset + loadOffset;
if (currentLoadSection && curOffset > currentLoadSection->size) if (currentLoadSection && curOffset > currentLoadSection->size)
currentLoadSection->size = curOffset; currentLoadSection->size = curOffset;
} }
static void writebyte(uint8_t byte) static void writebyte(uint8_t byte)
{ {
pCurrentSection->data[sect_GetOutputOffset()] = byte; currentSection->data[sect_GetOutputOffset()] = byte;
growSection(1); growSection(1);
} }
@@ -510,10 +547,14 @@ static void createPatch(enum PatchType type, struct Expression const *expr, uint
void sect_StartUnion(void) void sect_StartUnion(void)
{ {
if (!pCurrentSection) if (!currentSection) {
fatalerror("UNIONs must be inside a SECTION\n"); error("UNIONs must be inside a SECTION\n");
if (sect_HasData(pCurrentSection->type)) return;
fatalerror("Cannot use UNION inside of ROM0 or ROMX sections\n"); }
if (sect_HasData(currentSection->type)) {
error("Cannot use UNION inside of ROM0 or ROMX sections\n");
return;
}
struct UnionStackEntry *entry = malloc(sizeof(*entry)); struct UnionStackEntry *entry = malloc(sizeof(*entry));
if (!entry) if (!entry)
@@ -535,15 +576,19 @@ static void endUnionMember(void)
void sect_NextUnionMember(void) void sect_NextUnionMember(void)
{ {
if (!unionStack) if (!unionStack) {
fatalerror("Found NEXTU outside of a UNION construct\n"); error("Found NEXTU outside of a UNION construct\n");
return;
}
endUnionMember(); endUnionMember();
} }
void sect_EndUnion(void) void sect_EndUnion(void)
{ {
if (!unionStack) if (!unionStack) {
fatalerror("Found ENDU outside of a UNION construct\n"); error("Found ENDU outside of a UNION construct\n");
return;
}
endUnionMember(); endUnionMember();
curOffset += unionStack->size; curOffset += unionStack->size;
struct UnionStackEntry *next = unionStack->next; struct UnionStackEntry *next = unionStack->next;
@@ -561,36 +606,44 @@ void sect_CheckUnionClosed(void)
/* /*
* Output an absolute byte * Output an absolute byte
*/ */
void out_AbsByte(uint8_t b) void sect_AbsByte(uint8_t b)
{ {
checkcodesection(); if (!checkcodesection())
reserveSpace(1); return;
if (!reserveSpace(1))
return;
writebyte(b); writebyte(b);
} }
void out_AbsByteGroup(uint8_t const *s, int32_t length) void sect_AbsByteGroup(uint8_t const *s, int32_t length)
{ {
checkcodesection(); if (!checkcodesection())
reserveSpace(length); return;
if (!reserveSpace(length))
return;
while (length--) while (length--)
writebyte(*s++); writebyte(*s++);
} }
void out_AbsWordGroup(uint8_t const *s, int32_t length) void sect_AbsWordGroup(uint8_t const *s, int32_t length)
{ {
checkcodesection(); if (!checkcodesection())
reserveSpace(length * 2); return;
if (!reserveSpace(length * 2))
return;
while (length--) while (length--)
writeword(*s++); writeword(*s++);
} }
void out_AbsLongGroup(uint8_t const *s, int32_t length) void sect_AbsLongGroup(uint8_t const *s, int32_t length)
{ {
checkcodesection(); if (!checkcodesection())
reserveSpace(length * 4); return;
if (!reserveSpace(length * 4))
return;
while (length--) while (length--)
writelong(*s++); writelong(*s++);
@@ -599,19 +652,20 @@ void out_AbsLongGroup(uint8_t const *s, int32_t length)
/* /*
* Skip this many bytes * Skip this many bytes
*/ */
void out_Skip(int32_t skip, bool ds) void sect_Skip(int32_t skip, bool ds)
{ {
checksection(); if (!checksection())
reserveSpace(skip); return;
if (!reserveSpace(skip))
return;
if (!ds && sect_HasData(pCurrentSection->type)) if (!sect_HasData(currentSection->type)) {
warning(WARNING_EMPTY_DATA_DIRECTIVE, "%s directive without data in ROM\n",
(skip == 4) ? "DL" : (skip == 2) ? "DW" : "DB");
if (!sect_HasData(pCurrentSection->type)) {
growSection(skip); growSection(skip);
} else { } else {
checkcodesection(); if (!ds)
warning(WARNING_EMPTY_DATA_DIRECTIVE, "%s directive without data in ROM\n",
(skip == 4) ? "DL" : (skip == 2) ? "DW" : "DB");
// We know we're in a code SECTION
while (skip--) while (skip--)
writebyte(fillByte); writebyte(fillByte);
} }
@@ -620,10 +674,12 @@ void out_Skip(int32_t skip, bool ds)
/* /*
* Output a NULL terminated string (excluding the NULL-character) * Output a NULL terminated string (excluding the NULL-character)
*/ */
void out_String(char const *s) void sect_String(char const *s)
{ {
checkcodesection(); if (!checkcodesection())
reserveSpace(strlen(s)); return;
if (!reserveSpace(strlen(s)))
return;
while (*s) while (*s)
writebyte(*s++); writebyte(*s++);
@@ -633,16 +689,18 @@ 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, uint32_t pcShift) void sect_RelByte(struct Expression *expr, uint32_t pcShift)
{ {
checkcodesection(); if (!checkcodesection())
reserveSpace(1); return;
if (!reserveSpace(1))
return;
if (!rpn_isKnown(expr)) { if (!rpn_isKnown(expr)) {
createPatch(PATCHTYPE_BYTE, expr, pcShift); createPatch(PATCHTYPE_BYTE, expr, pcShift);
writebyte(0); writebyte(0);
} else { } else {
writebyte(expr->nVal); writebyte(expr->val);
} }
rpn_Free(expr); rpn_Free(expr);
} }
@@ -651,10 +709,12 @@ void out_RelByte(struct Expression *expr, uint32_t pcShift)
* 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(uint32_t n, struct Expression *exprs, size_t size) void sect_RelBytes(uint32_t n, struct Expression *exprs, size_t size)
{ {
checkcodesection(); if (!checkcodesection())
reserveSpace(n); return;
if (!reserveSpace(n))
return;
for (uint32_t i = 0; i < n; i++) { for (uint32_t i = 0; i < n; i++) {
struct Expression *expr = &exprs[i % size]; struct Expression *expr = &exprs[i % size];
@@ -663,7 +723,7 @@ void out_RelBytes(uint32_t n, struct Expression *exprs, size_t size)
createPatch(PATCHTYPE_BYTE, expr, i); createPatch(PATCHTYPE_BYTE, expr, i);
writebyte(0); writebyte(0);
} else { } else {
writebyte(expr->nVal); writebyte(expr->val);
} }
} }
@@ -675,16 +735,18 @@ void out_RelBytes(uint32_t n, struct Expression *exprs, size_t size)
* 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, uint32_t pcShift) void sect_RelWord(struct Expression *expr, uint32_t pcShift)
{ {
checkcodesection(); if (!checkcodesection())
reserveSpace(2); return;
if (!reserveSpace(2))
return;
if (!rpn_isKnown(expr)) { if (!rpn_isKnown(expr)) {
createPatch(PATCHTYPE_WORD, expr, pcShift); createPatch(PATCHTYPE_WORD, expr, pcShift);
writeword(0); writeword(0);
} else { } else {
writeword(expr->nVal); writeword(expr->val);
} }
rpn_Free(expr); rpn_Free(expr);
} }
@@ -693,16 +755,18 @@ void out_RelWord(struct Expression *expr, uint32_t pcShift)
* 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, uint32_t pcShift) void sect_RelLong(struct Expression *expr, uint32_t pcShift)
{ {
checkcodesection(); if (!checkcodesection())
reserveSpace(2); return;
if (!reserveSpace(2))
return;
if (!rpn_isKnown(expr)) { if (!rpn_isKnown(expr)) {
createPatch(PATCHTYPE_LONG, expr, pcShift); createPatch(PATCHTYPE_LONG, expr, pcShift);
writelong(0); writelong(0);
} else { } else {
writelong(expr->nVal); writelong(expr->val);
} }
rpn_Free(expr); rpn_Free(expr);
} }
@@ -711,10 +775,12 @@ void out_RelLong(struct Expression *expr, uint32_t pcShift)
* 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, uint32_t pcShift) void sect_PCRelByte(struct Expression *expr, uint32_t pcShift)
{ {
checkcodesection(); if (!checkcodesection())
reserveSpace(1); return;
if (!reserveSpace(1))
return;
struct Symbol const *pc = sym_GetPC(); struct Symbol const *pc = sym_GetPC();
if (!rpn_IsDiffConstant(expr, pc)) { if (!rpn_IsDiffConstant(expr, pc)) {
@@ -745,12 +811,14 @@ void out_PCRelByte(struct Expression *expr, uint32_t pcShift)
/* /*
* Output a binary file * Output a binary file
*/ */
void out_BinaryFile(char const *s, int32_t startPos) void sect_BinaryFile(char const *s, int32_t startPos)
{ {
if (startPos < 0) { if (startPos < 0) {
error("Start position cannot be negative (%" PRId32 ")\n", startPos); error("Start position cannot be negative (%" PRId32 ")\n", startPos);
startPos = 0; startPos = 0;
} }
if (!checkcodesection())
return;
char *fullPath = NULL; char *fullPath = NULL;
size_t size = 0; size_t size = 0;
@@ -761,10 +829,10 @@ void out_BinaryFile(char const *s, int32_t startPos)
free(fullPath); free(fullPath);
if (!f) { if (!f) {
if (oGeneratedMissingIncludes) { if (generatedMissingIncludes) {
if (verbose) if (verbose)
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", s, strerror(errno)); printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", s, strerror(errno));
oFailedOnMissingInclude = true; failedOnMissingInclude = true;
return; return;
} }
error("Error opening INCBIN file '%s': %s\n", s, strerror(errno)); error("Error opening INCBIN file '%s': %s\n", s, strerror(errno));
@@ -774,7 +842,6 @@ void out_BinaryFile(char const *s, int32_t startPos)
int32_t fsize = -1; int32_t fsize = -1;
int byte; int byte;
checkcodesection();
if (fseek(f, 0, SEEK_END) != -1) { if (fseek(f, 0, SEEK_END) != -1) {
fsize = ftell(f); fsize = ftell(f);
@@ -784,7 +851,8 @@ void out_BinaryFile(char const *s, int32_t startPos)
} }
fseek(f, startPos, SEEK_SET); fseek(f, startPos, SEEK_SET);
reserveSpace(fsize - startPos); if (!reserveSpace(fsize - startPos))
goto cleanup;
} else { } else {
if (errno != ESPIPE) if (errno != ESPIPE)
error("Error determining size of INCBIN file '%s': %s\n", error("Error determining size of INCBIN file '%s': %s\n",
@@ -807,7 +875,7 @@ cleanup:
fclose(f); fclose(f);
} }
void out_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length) void sect_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
{ {
if (start_pos < 0) { if (start_pos < 0) {
error("Start position cannot be negative (%" PRId32 ")\n", start_pos); error("Start position cannot be negative (%" PRId32 ")\n", start_pos);
@@ -818,8 +886,13 @@ void out_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
error("Number of bytes to read cannot be negative (%" PRId32 ")\n", length); error("Number of bytes to read cannot be negative (%" PRId32 ")\n", length);
length = 0; length = 0;
} }
if (!checkcodesection())
return;
if (length == 0) /* Don't even bother with 0-byte slices */ if (length == 0) /* Don't even bother with 0-byte slices */
return; return;
if (!reserveSpace(length))
return;
char *fullPath = NULL; char *fullPath = NULL;
size_t size = 0; size_t size = 0;
@@ -830,19 +903,16 @@ void out_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
free(fullPath); free(fullPath);
if (!f) { if (!f) {
if (oGeneratedMissingIncludes) { if (generatedMissingIncludes) {
if (verbose) if (verbose)
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", s, strerror(errno)); printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", s, strerror(errno));
oFailedOnMissingInclude = true; failedOnMissingInclude = true;
} else { } else {
error("Error opening INCBIN file '%s': %s\n", s, strerror(errno)); error("Error opening INCBIN file '%s': %s\n", s, strerror(errno));
} }
return; return;
} }
checkcodesection();
reserveSpace(length);
int32_t fsize; int32_t fsize;
if (fseek(f, 0, SEEK_END) != -1) { if (fseek(f, 0, SEEK_END) != -1) {
@@ -891,21 +961,27 @@ cleanup:
/* /*
* Section stack routines * Section stack routines
*/ */
void out_PushSection(void) void sect_PushSection(void)
{ {
struct SectionStackEntry *sect = malloc(sizeof(*sect)); struct SectionStackEntry *sect = malloc(sizeof(*sect));
if (sect == NULL) if (sect == NULL)
fatalerror("No memory for section stack: %s\n", strerror(errno)); fatalerror("No memory for section stack: %s\n", strerror(errno));
sect->section = pCurrentSection; sect->section = currentSection;
sect->loadSection = currentLoadSection;
sect->scope = sym_GetCurrentSymbolScope(); sect->scope = sym_GetCurrentSymbolScope();
sect->offset = curOffset; sect->offset = curOffset;
sect->loadOffset = loadOffset;
sect->next = sectionStack; sect->next = sectionStack;
sectionStack = sect; sectionStack = sect;
/* TODO: maybe set current section to NULL? */
// Reset the section scope
currentSection = NULL;
currentLoadSection = NULL;
sym_SetCurrentSymbolScope(NULL);
} }
void out_PopSection(void) void sect_PopSection(void)
{ {
if (!sectionStack) if (!sectionStack)
fatalerror("No entries in the section stack\n"); fatalerror("No entries in the section stack\n");
@@ -917,9 +993,11 @@ void out_PopSection(void)
sect = sectionStack; sect = sectionStack;
changeSection(); changeSection();
pCurrentSection = sect->section; currentSection = sect->section;
currentLoadSection = sect->loadSection;
sym_SetCurrentSymbolScope(sect->scope); sym_SetCurrentSymbolScope(sect->scope);
curOffset = sect->offset; curOffset = sect->offset;
loadOffset = sect->loadOffset;
sectionStack = sect->next; sectionStack = sect->next;
free(sect); free(sect);

View File

@@ -51,16 +51,16 @@ bool sym_IsPC(struct Symbol const *sym)
} }
struct ForEachArgs { struct ForEachArgs {
void (*func)(struct Symbol *symbol, void *arg); void (*func)(struct Symbol *sym, void *arg);
void *arg; void *arg;
}; };
static void forEachWrapper(void *_symbol, void *_argWrapper) static void forEachWrapper(void *_sym, void *_argWrapper)
{ {
struct ForEachArgs *argWrapper = _argWrapper; struct ForEachArgs *argWrapper = _argWrapper;
struct Symbol *symbol = _symbol; struct Symbol *sym = _sym;
argWrapper->func(symbol, argWrapper->arg); argWrapper->func(sym, argWrapper->arg);
} }
void sym_ForEach(void (*func)(struct Symbol *, void *), void *arg) void sym_ForEach(void (*func)(struct Symbol *, void *), void *arg)
@@ -96,8 +96,6 @@ static char const *Callback__FILE__(void)
char const *fileName = fstk_GetFileName(); char const *fileName = fstk_GetFileName();
size_t j = 1; size_t j = 1;
/* TODO: is there a way for a file name to be empty? */
assert(fileName[0]);
/* The assertion above ensures the loop runs at least once */ /* The assertion above ensures the loop runs at least once */
for (size_t i = 0; fileName[i]; i++, j++) { for (size_t i = 0; fileName[i]; i++, j++) {
/* Account for the extra backslash inserted below */ /* Account for the extra backslash inserted below */
@@ -175,32 +173,32 @@ static void updateSymbolFilename(struct Symbol *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 && oldSrc->referenced && oldSrc->ID != (uint32_t)-1) if (oldSrc && oldSrc->referenced && oldSrc->ID != (uint32_t)-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 of deleting it */
} }
/* /*
* Create a new symbol by name * Create a new symbol by name
*/ */
static struct Symbol *createsymbol(char const *s) static struct Symbol *createsymbol(char const *symName)
{ {
struct Symbol *symbol = malloc(sizeof(*symbol)); struct Symbol *sym = malloc(sizeof(*sym));
if (!symbol) if (!sym)
fatalerror("Failed to create symbol '%s': %s\n", s, strerror(errno)); fatalerror("Failed to create symbol '%s': %s\n", symName, strerror(errno));
if (snprintf(symbol->name, MAXSYMLEN + 1, "%s", s) > MAXSYMLEN) if (snprintf(sym->name, MAXSYMLEN + 1, "%s", symName) > MAXSYMLEN)
warning(WARNING_LONG_STR, "Symbol name is too long: '%s'\n", s); warning(WARNING_LONG_STR, "Symbol name is too long: '%s'\n", symName);
symbol->isExported = false; sym->isExported = false;
symbol->isBuiltin = false; sym->isBuiltin = false;
symbol->hasCallback = false; sym->hasCallback = false;
symbol->section = NULL; sym->section = NULL;
setSymbolFilename(symbol); setSymbolFilename(sym);
symbol->ID = -1; sym->ID = -1;
symbol->next = NULL; sym->next = NULL;
hash_AddElement(symbols, symbol->name, symbol); hash_AddElement(symbols, sym->name, sym);
return symbol; return sym;
} }
/* /*
@@ -222,46 +220,45 @@ static void assignStringSymbol(struct Symbol *sym, char const *value)
{ {
char *string = strdup(value); char *string = strdup(value);
if (string == NULL) if (!string)
fatalerror("No memory for string equate: %s\n", strerror(errno)); fatalerror("No memory for string equate: %s\n", strerror(errno));
sym->type = SYM_EQUS; sym->type = SYM_EQUS;
/* TODO: use other fields */
sym->macro = string; sym->macro = string;
sym->macroSize = strlen(string); sym->macroSize = strlen(string);
} }
struct Symbol *sym_FindExactSymbol(char const *name) struct Symbol *sym_FindExactSymbol(char const *symName)
{ {
return hash_GetElement(symbols, name); return hash_GetElement(symbols, symName);
} }
struct Symbol *sym_FindUnscopedSymbol(char const *name) struct Symbol *sym_FindUnscopedSymbol(char const *symName)
{ {
if (strchr(name, '.')) { if (strchr(symName, '.')) {
error("Expected non-scoped symbol name, not \"%s\"\n", name); error("Expected non-scoped symbol name, not \"%s\"\n", symName);
return NULL; return NULL;
} }
return sym_FindExactSymbol(name); return sym_FindExactSymbol(symName);
} }
struct Symbol *sym_FindScopedSymbol(char const *name) struct Symbol *sym_FindScopedSymbol(char const *symName)
{ {
char const *dotPtr = strchr(name, '.'); char const *dotPtr = strchr(symName, '.');
if (dotPtr) { if (dotPtr) {
if (strchr(dotPtr + 1, '.')) if (strchr(dotPtr + 1, '.'))
fatalerror("'%s' is a nonsensical reference to a nested local symbol\n", fatalerror("'%s' is a nonsensical reference to a nested local symbol\n",
name); symName);
/* If auto-scoped local label, expand the name */ /* If auto-scoped local label, expand the name */
if (dotPtr == name) { /* Meaning, the name begins with the dot */ if (dotPtr == symName) { /* Meaning, the name begins with the dot */
char fullname[MAXSYMLEN + 1]; char fullname[MAXSYMLEN + 1];
fullSymbolName(fullname, sizeof(fullname), name, labelScope); fullSymbolName(fullname, sizeof(fullname), symName, labelScope);
return sym_FindExactSymbol(fullname); return sym_FindExactSymbol(fullname);
} }
} }
return sym_FindExactSymbol(name); return sym_FindExactSymbol(symName);
} }
struct Symbol const *sym_GetPC(void) struct Symbol const *sym_GetPC(void)
@@ -279,27 +276,26 @@ static bool isReferenced(struct Symbol const *sym)
*/ */
void sym_Purge(char const *symName) void sym_Purge(char const *symName)
{ {
struct Symbol *symbol = sym_FindScopedSymbol(symName); struct Symbol *sym = sym_FindScopedSymbol(symName);
if (!symbol) { if (!sym) {
error("'%s' not defined\n", symName); error("'%s' not defined\n", symName);
} else if (symbol->isBuiltin) { } else if (sym->isBuiltin) {
error("Built-in symbol '%s' cannot be purged\n", symName); error("Built-in symbol '%s' cannot be purged\n", symName);
} else if (isReferenced(symbol)) { } else if (isReferenced(sym)) {
error("Symbol \"%s\" is referenced and thus cannot be purged\n", symName); error("Symbol \"%s\" is referenced and thus cannot be purged\n", symName);
} else { } else {
/* Do not keep a reference to the label's name after purging it */ /* Do not keep a reference to the label's name after purging it */
if (symbol->name == labelScope) if (sym->name == labelScope)
labelScope = NULL; sym_SetCurrentSymbolScope(NULL);
/* /*
* FIXME: this leaks symbol->macro for SYM_EQUS and SYM_MACRO, but this can't * FIXME: this leaks sym->macro for SYM_EQUS and SYM_MACRO, but this can't
* free(symbol->macro) because the expansion may be purging itself. * free(sym->macro) because the expansion may be purging itself.
*/ */
hash_RemoveElement(symbols, sym->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(sym);
} }
} }
@@ -334,12 +330,12 @@ uint32_t sym_GetConstantSymValue(struct Symbol const *sym)
/* /*
* Return a constant symbol's value * Return a constant symbol's value
*/ */
uint32_t sym_GetConstantValue(char const *s) uint32_t sym_GetConstantValue(char const *symName)
{ {
struct Symbol const *sym = sym_FindScopedSymbol(s); struct Symbol const *sym = sym_FindScopedSymbol(symName);
if (sym == NULL) if (!sym)
error("'%s' not defined\n", s); error("'%s' not defined\n", symName);
else else
return sym_GetConstantSymValue(sym); return sym_GetConstantSymValue(sym);
@@ -360,29 +356,29 @@ 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 symName The name of the symbol to create
* @param numeric If false, the symbol may not have been referenced earlier * @param numeric If false, the symbol may not have been referenced earlier
*/ */
static struct Symbol *createNonrelocSymbol(char const *symbolName, bool numeric) static struct Symbol *createNonrelocSymbol(char const *symName, bool numeric)
{ {
struct Symbol *symbol = sym_FindExactSymbol(symbolName); struct Symbol *sym = sym_FindExactSymbol(symName);
if (!symbol) { if (!sym) {
symbol = createsymbol(symbolName); sym = createsymbol(symName);
} else if (sym_IsDefined(symbol)) { } else if (sym_IsDefined(sym)) {
error("'%s' already defined at ", symbolName); error("'%s' already defined at ", symName);
dumpFilename(symbol); dumpFilename(sym);
putc('\n', stderr); putc('\n', stderr);
return NULL; // Don't allow overriding the symbol, that'd be bad! return NULL; // Don't allow overriding the symbol, that'd be bad!
} else if (!numeric) { } else if (!numeric) {
// The symbol has already been referenced, but it's not allowed // The symbol has already been referenced, but it's not allowed
error("'%s' already referenced at ", symbolName); error("'%s' already referenced at ", symName);
dumpFilename(symbol); dumpFilename(sym);
putc('\n', stderr); putc('\n', stderr);
return NULL; // Don't allow overriding the symbol, that'd be bad! return NULL; // Don't allow overriding the symbol, that'd be bad!
} }
return symbol; return sym;
} }
/* /*
@@ -401,6 +397,30 @@ struct Symbol *sym_AddEqu(char const *symName, int32_t value)
return sym; return sym;
} }
struct Symbol *sym_RedefEqu(char const *symName, int32_t value)
{
struct Symbol *sym = sym_FindExactSymbol(symName);
if (!sym)
return sym_AddEqu(symName, value);
if (sym_IsDefined(sym) && sym->type != SYM_EQU) {
error("'%s' already defined as non-EQU at ", symName);
dumpFilename(sym);
putc('\n', stderr);
return NULL;
} else if (sym->isBuiltin) {
error("Built-in symbol '%s' cannot be redefined\n", symName);
return NULL;
}
updateSymbolFilename(sym);
sym->type = SYM_EQU;
sym->value = value;
return sym;
}
/* /*
* Add a string equated symbol. * Add a string equated symbol.
* *
@@ -428,10 +448,14 @@ struct Symbol *sym_RedefString(char const *symName, char const *value)
{ {
struct Symbol *sym = sym_FindExactSymbol(symName); struct Symbol *sym = sym_FindExactSymbol(symName);
if (!sym) { if (!sym)
sym = createsymbol(symName); return sym_AddString(symName, value);
} else if (sym->type != SYM_EQUS) {
if (sym->type != SYM_EQUS) {
if (sym_IsDefined(sym))
error("'%s' already defined as non-EQUS at ", symName); error("'%s' already defined as non-EQUS at ", symName);
else
error("'%s' already referenced at ", symName);
dumpFilename(sym); dumpFilename(sym);
putc('\n', stderr); putc('\n', stderr);
return NULL; return NULL;
@@ -440,24 +464,24 @@ struct Symbol *sym_RedefString(char const *symName, char const *value)
return NULL; return NULL;
} }
updateSymbolFilename(sym);
/* /*
* FIXME: this leaks the previous sym->macro value, but this can't * FIXME: this leaks the previous sym->macro value, but this can't
* free(sym->macro) because the expansion may be redefining itself. * free(sym->macro) because the expansion may be redefining itself.
*/ */
assignStringSymbol(sym, value); assignStringSymbol(sym, value);
return sym; return sym;
} }
/* /*
* Alter a SET symbols value * Alter a SET symbol's value
*/ */
struct Symbol *sym_AddSet(char const *symName, int32_t value) struct Symbol *sym_AddSet(char const *symName, int32_t value)
{ {
struct Symbol *sym = sym_FindExactSymbol(symName); struct Symbol *sym = sym_FindExactSymbol(symName);
if (sym == NULL) { if (!sym) {
sym = createsymbol(symName); sym = createsymbol(symName);
} else if (sym_IsDefined(sym) && sym->type != SYM_SET) { } else if (sym_IsDefined(sym) && sym->type != SYM_SET) {
error("'%s' already defined as %s at ", error("'%s' already defined as %s at ",
@@ -477,18 +501,18 @@ struct Symbol *sym_AddSet(char const *symName, int32_t value)
/* /*
* Add a label (aka "relocatable symbol") * Add a label (aka "relocatable symbol")
* @param name The label's full name (so `.name` is invalid) * @param symName The label's full name (so `.name` is invalid)
* @return The created symbol * @return The created symbol
*/ */
static struct Symbol *addLabel(char const *name) static struct Symbol *addLabel(char const *symName)
{ {
assert(name[0] != '.'); /* The symbol name must have been expanded prior */ assert(symName[0] != '.'); /* The symbol name must have been expanded prior */
struct Symbol *sym = sym_FindExactSymbol(name); struct Symbol *sym = sym_FindExactSymbol(symName);
if (!sym) { if (!sym) {
sym = createsymbol(name); sym = createsymbol(symName);
} else if (sym_IsDefined(sym)) { } else if (sym_IsDefined(sym)) {
error("'%s' already defined at ", name); error("'%s' already defined at ", symName);
dumpFilename(sym); dumpFilename(sym);
putc('\n', stderr); putc('\n', stderr);
return NULL; return NULL;
@@ -503,62 +527,62 @@ static struct Symbol *addLabel(char const *name)
sym->section = sect_GetSymbolSection(); sym->section = sect_GetSymbolSection();
if (sym && !sym->section) if (sym && !sym->section)
error("Label \"%s\" created outside of a SECTION\n", name); error("Label \"%s\" created outside of a SECTION\n", symName);
return sym; return sym;
} }
/* /*
* Add a local (.name or Parent.name) relocatable symbol * Add a local (.name or Parent.name) relocatable symbol
*/ */
struct Symbol *sym_AddLocalLabel(char const *name) struct Symbol *sym_AddLocalLabel(char const *symName)
{ {
if (!labelScope) { if (!labelScope) {
error("Local label '%s' in main scope\n", name); error("Local label '%s' in main scope\n", symName);
return NULL; return NULL;
} }
char fullname[MAXSYMLEN + 1]; char fullname[MAXSYMLEN + 1];
if (name[0] == '.') { if (symName[0] == '.') {
/* If symbol is of the form `.name`, expand to the full `Parent.name` name */ /* If symbol is of the form `.name`, expand to the full `Parent.name` name */
fullSymbolName(fullname, sizeof(fullname), name, labelScope); fullSymbolName(fullname, sizeof(fullname), symName, labelScope);
name = fullname; /* Use the expanded name instead */ symName = fullname; /* Use the expanded name instead */
} else { } else {
size_t i = 0; size_t i = 0;
/* Otherwise, check that `Parent` is in fact the current scope */ /* Otherwise, check that `Parent` is in fact the current scope */
while (labelScope[i] && name[i] == labelScope[i]) while (labelScope[i] && symName[i] == labelScope[i])
i++; i++;
/* Assuming no dots in `labelScope` */ /* Assuming no dots in `labelScope` */
assert(strchr(&name[i], '.')); /* There should be at least one dot, though */ assert(strchr(&symName[i], '.')); /* There should be at least one dot, though */
size_t parentLen = i + (strchr(&name[i], '.') - name); size_t parentLen = i + (strchr(&symName[i], '.') - symName);
/* /*
* Check that `labelScope[i]` ended the check, guaranteeing that `name` is at least * Check that `labelScope[i]` ended the check, guaranteeing that `symName` is at
* as long, and then that this was the entirety of the `Parent` part of `name`. * least as long, and then that this was the entire `Parent` part of `symName`.
*/ */
if (labelScope[i] != '\0' || name[i] != '.') { if (labelScope[i] != '\0' || symName[i] != '.') {
assert(parentLen <= INT_MAX); assert(parentLen <= INT_MAX);
error("Not currently in the scope of '%.*s'\n", (int)parentLen, name); error("Not currently in the scope of '%.*s'\n", (int)parentLen, symName);
} }
if (strchr(&name[parentLen + 1], '.')) /* There will at least be a terminator */ if (strchr(&symName[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); symName);
} }
return addLabel(name); return addLabel(symName);
} }
/* /*
* Add a relocatable symbol * Add a relocatable symbol
*/ */
struct Symbol *sym_AddLabel(char const *name) struct Symbol *sym_AddLabel(char const *symName)
{ {
struct Symbol *sym = addLabel(name); struct Symbol *sym = addLabel(symName);
/* Set the symbol as the new scope */ /* Set the symbol as the new scope */
if (sym) if (sym)
labelScope = sym->name; sym_SetCurrentSymbolScope(sym->name);
return sym; return sym;
} }
@@ -653,9 +677,9 @@ struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body,
*/ */
struct Symbol *sym_Ref(char const *symName) struct Symbol *sym_Ref(char const *symName)
{ {
struct Symbol *nsym = sym_FindScopedSymbol(symName); struct Symbol *sym = sym_FindScopedSymbol(symName);
if (nsym == NULL) { if (!sym) {
char fullname[MAXSYMLEN + 1]; char fullname[MAXSYMLEN + 1];
if (symName[0] == '.') { if (symName[0] == '.') {
@@ -665,11 +689,11 @@ struct Symbol *sym_Ref(char const *symName)
symName = fullname; symName = fullname;
} }
nsym = createsymbol(symName); sym = createsymbol(symName);
nsym->type = SYM_REF; sym->type = SYM_REF;
} }
return nsym; return sym;
} }
/* /*
@@ -680,9 +704,9 @@ void sym_SetExportAll(bool set)
exportall = set; exportall = set;
} }
static struct Symbol *createBuiltinSymbol(char const *name) static struct Symbol *createBuiltinSymbol(char const *symName)
{ {
struct Symbol *sym = createsymbol(name); struct Symbol *sym = createsymbol(symName);
sym->isBuiltin = true; sym->isBuiltin = true;
sym->hasCallback = true; sym->hasCallback = true;
@@ -758,7 +782,7 @@ void sym_Init(time_t now)
#undef addNumber #undef addNumber
#undef addString #undef addString
labelScope = NULL; sym_SetCurrentSymbolScope(NULL);
anonLabelID = 0; anonLabelID = 0;
/* _PI is deprecated */ /* _PI is deprecated */

View File

@@ -67,6 +67,7 @@ size_t readUTF8Char(uint8_t *dest, char const *src)
if (decode(&state, &codep, src[i]) == 1) if (decode(&state, &codep, src[i]) == 1)
return 0; return 0;
if (dest)
dest[i] = src[i]; dest[i] = src[i];
i++; i++;

View File

@@ -21,13 +21,6 @@
unsigned int nbErrors = 0; unsigned int nbErrors = 0;
enum WarningState {
WARNING_DEFAULT,
WARNING_DISABLED,
WARNING_ENABLED,
WARNING_ERROR
};
static enum WarningState const defaultWarnings[NB_WARNINGS] = { static enum WarningState const defaultWarnings[NB_WARNINGS] = {
[WARNING_ASSERT] = WARNING_ENABLED, [WARNING_ASSERT] = WARNING_ENABLED,
[WARNING_BACKWARDS_FOR] = WARNING_DISABLED, [WARNING_BACKWARDS_FOR] = WARNING_DISABLED,
@@ -48,9 +41,9 @@ static enum WarningState const defaultWarnings[NB_WARNINGS] = {
[WARNING_USER] = WARNING_ENABLED, [WARNING_USER] = WARNING_ENABLED,
}; };
static enum WarningState warningStates[NB_WARNINGS]; enum WarningState warningStates[NB_WARNINGS];
static bool warningsAreErrors; /* Set if `-Werror` was specified */ bool warningsAreErrors; /* Set if `-Werror` was specified */
static enum WarningState warningState(enum WarningID id) static enum WarningState warningState(enum WarningID id)
{ {
@@ -159,8 +152,7 @@ void processWarningFlag(char const *flag)
errx(1, "Cannot make meta warning \"%s\" into an error", errx(1, "Cannot make meta warning \"%s\" into an error",
flag); flag);
uint8_t const *ptr = uint8_t const *ptr = metaWarningCommands[id - NB_WARNINGS];
metaWarningCommands[id - NB_WARNINGS];
for (;;) { for (;;) {
if (*ptr == META_WARNING_DONE) if (*ptr == META_WARNING_DONE)

View File

@@ -25,7 +25,7 @@
#include "platform.h" #include "platform.h"
#include "version.h" #include "version.h"
#define UNSPECIFIED 0x100 // May not be in byte range #define UNSPECIFIED 0x200 // Should not be in byte range
#define BANK_SIZE 0x4000 #define BANK_SIZE 0x4000
@@ -79,6 +79,20 @@ static void printUsage(void)
stderr); stderr);
} }
static uint8_t nbErrors;
static format_(printf, 1, 2) void report(char const *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
if (nbErrors != UINT8_MAX)
nbErrors++;
}
enum MbcType { enum MbcType {
ROM = 0x00, ROM = 0x00,
ROM_RAM = 0x08, ROM_RAM = 0x08,
@@ -120,6 +134,28 @@ enum MbcType {
HUC1_RAM_BATTERY = 0xFF, HUC1_RAM_BATTERY = 0xFF,
// "Extended" values (still valid, but not directly actionable)
// A high byte of 0x01 means TPP1, the low byte is the requested features
// This does not include SRAM, which is instead implied by a non-zero SRAM size
// Note: Multiple rumble speeds imply rumble
TPP1 = 0x100,
TPP1_RUMBLE = 0x101,
TPP1_MULTIRUMBLE = 0x102, // Should not be possible
TPP1_MULTIRUMBLE_RUMBLE = 0x103,
TPP1_RTC = 0x104,
TPP1_RTC_RUMBLE = 0x105,
TPP1_RTC_MULTIRUMBLE = 0x106, // Should not be possible
TPP1_RTC_MULTIRUMBLE_RUMBLE = 0x107,
TPP1_BATTERY = 0x108,
TPP1_BATTERY_RUMBLE = 0x109,
TPP1_BATTERY_MULTIRUMBLE = 0x10a, // Should not be possible
TPP1_BATTERY_MULTIRUMBLE_RUMBLE = 0x10b,
TPP1_BATTERY_RTC = 0x10c,
TPP1_BATTERY_RTC_RUMBLE = 0x10d,
TPP1_BATTERY_RTC_MULTIRUMBLE = 0x10e, // Should not be possible
TPP1_BATTERY_RTC_MULTIRUMBLE_RUMBLE = 0x10f,
// Error values // Error values
MBC_NONE = UNSPECIFIED, // No MBC specified, do not act on it MBC_NONE = UNSPECIFIED, // No MBC specified, do not act on it
MBC_BAD, // Specified MBC does not exist / syntax error MBC_BAD, // Specified MBC does not exist / syntax error
@@ -127,6 +163,33 @@ enum MbcType {
MBC_BAD_RANGE, // MBC number out of range MBC_BAD_RANGE, // MBC number out of range
}; };
static void printAcceptedMBCNames(void)
{
fputs("\tROM ($00) [aka ROM_ONLY]\n", stderr);
fputs("\tMBC1 ($02), MBC1+RAM ($02), MBC1+RAM+BATTERY ($03)\n", stderr);
fputs("\tMBC2 ($05), MBC2+BATTERY ($06)\n", stderr);
fputs("\tROM+RAM ($08) [deprecated], ROM+RAM+BATTERY ($09) [deprecated]\n", stderr);
fputs("\tMMM01 ($0B), MMM01+RAM ($0C), MMM01+RAM+BATTERY ($0D)\n", stderr);
fputs("\tMBC3+TIMER+BATTERY ($0F), MBC3+TIMER+RAM+BATTERY ($10)\n", stderr);
fputs("\tMBC3 ($11), MBC3+RAM ($12), MBC3+RAM+BATTERY ($13)\n", stderr);
fputs("\tMBC5 ($19), MBC5+RAM ($1A), MBC5+RAM+BATTERY ($1B)\n", stderr);
fputs("\tMBC5+RUMBLE ($1C), MBC5+RUMBLE+RAM ($1D), MBC5+RUMBLE+RAM+BATTERY ($1E)\n", stderr);
fputs("\tMBC6 ($20)\n", stderr);
fputs("\tMBC7+SENSOR+RUMBLE+RAM+BATTERY ($22)\n", stderr);
fputs("\tPOCKET_CAMERA ($FC)\n", stderr);
fputs("\tBANDAI_TAMA5 ($FD)\n", stderr);
fputs("\tHUC3 ($FE)\n", stderr);
fputs("\tHUC1+RAM+BATTERY ($FF)\n", stderr);
fputs("\n\tTPP1_1.0, TPP1_1.0+RUMBLE, TPP1_1.0+MULTIRUMBLE, TPP1_1.0+RTC,\n", stderr);
fputs("\tTPP1_1.0+RTC+RUMBLE, TPP1_1.0+RTC+MULTIRUMBLE, TPP1_1.0+BATTERY,\n", stderr);
fputs("\tTPP1_1.0+BATTERY+RUMBLE, TPP1_1.0+BATTERY+MULTIRUMBLE,\n", stderr);
fputs("\tTPP1_1.0+BATTERY+RTC, TPP1_1.0+BATTERY+RTC+RUMBLE,\n", stderr);
fputs("\tTPP1_1.0+BATTERY+RTC+MULTIRUMBLE\n", stderr);
}
static uint8_t tpp1Rev[2];
/** /**
* @return False on failure * @return False on failure
*/ */
@@ -151,10 +214,22 @@ static bool readMBCSlice(char const **name, char const *expected)
static enum MbcType parseMBC(char const *name) static enum MbcType parseMBC(char const *name)
{ {
if (name[0] >= '0' && name[0] <= '9') { if (!strcasecmp(name, "help")) {
fputs("Accepted MBC names:\n", stderr);
printAcceptedMBCNames();
exit(0);
}
if ((name[0] >= '0' && name[0] <= '9') || name[0] == '$') {
int base = 0;
if (name[0] == '$') {
name++;
base = 16;
}
// Parse number, and return it as-is (unless it's too large) // Parse number, and return it as-is (unless it's too large)
char *endptr; char *endptr;
unsigned long mbc = strtoul(name, &endptr, 0); unsigned long mbc = strtoul(name, &endptr, base);
if (*endptr) if (*endptr)
return MBC_BAD; return MBC_BAD;
@@ -248,11 +323,51 @@ do { \
mbc = BANDAI_TAMA5; mbc = BANDAI_TAMA5;
break; break;
case 'T': // TAMA5 case 'T': // TAMA5 / TPP1
case 't': case 't':
tryReadSlice("AMA5"); switch (*ptr++) {
case 'A':
tryReadSlice("MA5");
mbc = BANDAI_TAMA5; mbc = BANDAI_TAMA5;
break; break;
case 'P':
tryReadSlice("P1");
// Parse version
while (*ptr == ' ' || *ptr == '_')
ptr++;
// Major
char *endptr;
unsigned long val = strtoul(ptr, &endptr, 10);
if (endptr == ptr) {
report("error: Failed to parse TPP1 major revision number\n");
return MBC_BAD;
}
ptr = endptr;
if (val != 1) {
report("error: RGBFIX only supports TPP1 versions 1.0\n");
return MBC_BAD;
}
tpp1Rev[0] = val;
tryReadSlice(".");
// Minor
val = strtoul(ptr, &endptr, 10);
if (endptr == ptr) {
report("error: Failed to parse TPP1 minor revision number\n");
return MBC_BAD;
}
ptr = endptr;
if (val > 0xFF) {
report("error: TPP1 minor revision number must be 8-bit\n");
return MBC_BAD;
}
tpp1Rev[1] = val;
mbc = TPP1;
break;
default:
return MBC_BAD;
}
break;
case 'H': // HuC{1, 3} case 'H': // HuC{1, 3}
case 'h': case 'h':
@@ -280,6 +395,7 @@ do { \
#define TIMER 0x20 #define TIMER 0x20
#define RUMBLE 0x10 #define RUMBLE 0x10
#define SENSOR 0x08 #define SENSOR 0x08
#define MULTIRUMBLE 0x04
for (;;) { for (;;) {
// Trim off trailing whitespace // Trim off trailing whitespace
@@ -303,6 +419,12 @@ do { \
features |= BATTERY; features |= BATTERY;
break; break;
case 'M':
case 'm':
tryReadSlice("ULTIRUMBLE");
features |= MULTIRUMBLE;
break;
case 'R': // RAM or RUMBLE case 'R': // RAM or RUMBLE
case 'r': case 'r':
switch (*ptr++) { switch (*ptr++) {
@@ -424,6 +546,22 @@ do { \
if (features != (RAM | BATTERY)) // HuC1 expects RAM+BATTERY if (features != (RAM | BATTERY)) // HuC1 expects RAM+BATTERY
return MBC_WRONG_FEATURES; return MBC_WRONG_FEATURES;
break; break;
case TPP1:
if (features & RAM)
fprintf(stderr,
"warning: TPP1 requests RAM implicitly if given a non-zero RAM size");
if (features & BATTERY)
mbc |= 0x08;
if (features & TIMER)
mbc |= 0x04;
if (features & MULTIRUMBLE)
mbc |= 0x03; // Also set the rumble flag
if (features & RUMBLE)
mbc |= 0x01;
if (features & SENSOR)
return MBC_WRONG_FEATURES;
break;
} }
// Trim off trailing whitespace // Trim off trailing whitespace
@@ -497,6 +635,34 @@ static char const *mbcName(enum MbcType type)
return "HUC3"; return "HUC3";
case HUC1_RAM_BATTERY: case HUC1_RAM_BATTERY:
return "HUC1+RAM+BATTERY"; return "HUC1+RAM+BATTERY";
case TPP1:
return "TPP1";
case TPP1_RUMBLE:
return "TPP1+RUMBLE";
case TPP1_MULTIRUMBLE:
case TPP1_MULTIRUMBLE_RUMBLE:
return "TPP1+MULTIRUMBLE";
case TPP1_RTC:
return "TPP1+RTC";
case TPP1_RTC_RUMBLE:
return "TPP1+RTC+RUMBLE";
case TPP1_RTC_MULTIRUMBLE:
case TPP1_RTC_MULTIRUMBLE_RUMBLE:
return "TPP1+RTC+MULTIRUMBLE";
case TPP1_BATTERY:
return "TPP1+BATTERY";
case TPP1_BATTERY_RUMBLE:
return "TPP1+BATTERY+RUMBLE";
case TPP1_BATTERY_MULTIRUMBLE:
case TPP1_BATTERY_MULTIRUMBLE_RUMBLE:
return "TPP1+BATTERY+MULTIRUMBLE";
case TPP1_BATTERY_RTC:
return "TPP1+BATTERY+RTC";
case TPP1_BATTERY_RTC_RUMBLE:
return "TPP1+BATTERY+RTC+RUMBLE";
case TPP1_BATTERY_RTC_MULTIRUMBLE:
case TPP1_BATTERY_RTC_MULTIRUMBLE_RUMBLE:
return "TPP1+BATTERY+RTC+MULTIRUMBLE";
// Error values // Error values
case MBC_NONE: case MBC_NONE:
@@ -547,25 +713,30 @@ static bool hasRAM(enum MbcType type)
case HUC3: case HUC3:
case HUC1_RAM_BATTERY: case HUC1_RAM_BATTERY:
return true; return true;
// TPP1 may or may not have RAM, don't call this function for it
case TPP1:
case TPP1_RUMBLE:
case TPP1_MULTIRUMBLE:
case TPP1_MULTIRUMBLE_RUMBLE:
case TPP1_RTC:
case TPP1_RTC_RUMBLE:
case TPP1_RTC_MULTIRUMBLE:
case TPP1_RTC_MULTIRUMBLE_RUMBLE:
case TPP1_BATTERY:
case TPP1_BATTERY_RUMBLE:
case TPP1_BATTERY_MULTIRUMBLE:
case TPP1_BATTERY_MULTIRUMBLE_RUMBLE:
case TPP1_BATTERY_RTC:
case TPP1_BATTERY_RTC_RUMBLE:
case TPP1_BATTERY_RTC_MULTIRUMBLE:
case TPP1_BATTERY_RTC_MULTIRUMBLE_RUMBLE:
break;
} }
unreachable_(); unreachable_();
} }
static uint8_t nbErrors;
static format_(printf, 1, 2) void report(char const *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
if (nbErrors != UINT8_MAX)
nbErrors++;
}
static const uint8_t ninLogo[] = { static const uint8_t ninLogo[] = {
0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, 0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B,
0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D, 0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D,
@@ -653,6 +824,22 @@ static ssize_t writeBytes(int fd, void *buf, size_t len)
return total; return total;
} }
/**
* @param rom0 A pointer to rom0
* @param startAddr What address to begin checking from
* @param size How many bytes to check
* @param areaName Name to be displayed in the warning message
*/
static void warnNonZero(uint8_t *rom0, uint16_t startAddr, uint8_t size, char const *areaName)
{
for (uint8_t i = 0; i < size; i++) {
if (rom0[i + startAddr] != 0) {
fprintf(stderr, "warning: Overwrote a non-zero byte in the %s\n", areaName);
break;
}
}
}
/** /**
* @param input File descriptor to be used for reading * @param input File descriptor to be used for reading
* @param output File descriptor to be used for writing, may be equal to `input` * @param output File descriptor to be used for writing, may be equal to `input`
@@ -669,18 +856,21 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
uint8_t rom0[BANK_SIZE]; uint8_t rom0[BANK_SIZE];
ssize_t rom0Len = readBytes(input, rom0, sizeof(rom0)); ssize_t rom0Len = readBytes(input, rom0, sizeof(rom0));
// Also used as how many bytes to write back when fixing in-place
ssize_t headerSize = (cartridgeType & 0xff00) == TPP1 ? 0x154 : 0x150;
if (rom0Len == -1) { if (rom0Len == -1) {
report("FATAL: Failed to read \"%s\"'s header: %s\n", name, strerror(errno)); report("FATAL: Failed to read \"%s\"'s header: %s\n", name, strerror(errno));
return; return;
} else if (rom0Len < 0x150) { } else if (rom0Len < headerSize) {
report("FATAL: \"%s\" too short, expected at least 336 ($150) bytes, got only %ld\n", report("FATAL: \"%s\" too short, expected at least %jd ($%jx) bytes, got only %jd\n",
name, rom0Len); name, (intmax_t)headerSize, (intmax_t)headerSize, (intmax_t)rom0Len);
return; return;
} }
// Accept partial reads if the file contains at least the header // Accept partial reads if the file contains at least the header
if (fixSpec & (FIX_LOGO | TRASH_LOGO)) { if (fixSpec & (FIX_LOGO | TRASH_LOGO)) {
warnNonZero(rom0, 0x0104, sizeof(ninLogo), "Nintendo logo");
if (fixSpec & FIX_LOGO) { if (fixSpec & FIX_LOGO) {
memcpy(&rom0[0x104], ninLogo, sizeof(ninLogo)); memcpy(&rom0[0x104], ninLogo, sizeof(ninLogo));
} else { } else {
@@ -689,36 +879,85 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
} }
} }
if (title) if (title) {
warnNonZero(rom0, 0x134, titleLen, "title");
memcpy(&rom0[0x134], title, titleLen); memcpy(&rom0[0x134], title, titleLen);
}
if (gameID) if (gameID) {
warnNonZero(rom0, 0x13f, gameIDLen, "manufacturer code");
memcpy(&rom0[0x13f], gameID, gameIDLen); memcpy(&rom0[0x13f], gameID, gameIDLen);
}
if (model != DMG) if (model != DMG) {
warnNonZero(rom0, 0x143, 1, "CGB flag");
rom0[0x143] = model == BOTH ? 0x80 : 0xc0; rom0[0x143] = model == BOTH ? 0x80 : 0xc0;
}
if (newLicensee) if (newLicensee) {
warnNonZero(rom0, 0x144, newLicenseeLen, "new licensee code");
memcpy(&rom0[0x144], newLicensee, newLicenseeLen); memcpy(&rom0[0x144], newLicensee, newLicenseeLen);
}
if (sgb) if (sgb) {
warnNonZero(rom0, 0x146, 1, "SGB flag");
rom0[0x146] = 0x03; rom0[0x146] = 0x03;
}
// If a valid MBC was specified... // If a valid MBC was specified...
if (cartridgeType < MBC_NONE) if (cartridgeType < MBC_NONE) {
rom0[0x147] = cartridgeType; warnNonZero(rom0, 0x147, 1, "cartridge type");
uint8_t byte = cartridgeType;
if (ramSize != UNSPECIFIED) if ((cartridgeType & 0xff00) == TPP1) {
// Cartridge type isn't directly actionable, translate it
byte = 0xBC;
// The other TPP1 identification bytes will be written below
}
rom0[0x147] = byte;
}
// ROM size will be written last, after evaluating the file's size
if ((cartridgeType & 0xff00) == TPP1) {
warnNonZero(rom0, 0x149, 2, "TPP1 identification code");
rom0[0x149] = 0xC1;
rom0[0x14a] = 0x65;
warnNonZero(rom0, 0x150, 2, "TPP1 revision number");
rom0[0x150] = tpp1Rev[0];
rom0[0x151] = tpp1Rev[1];
if (ramSize != UNSPECIFIED) {
warnNonZero(rom0, 0x152, 1, "RAM size");
rom0[0x152] = ramSize;
}
warnNonZero(rom0, 0x153, 1, "TPP1 feature flags");
rom0[0x153] = cartridgeType & 0xFF;
} else {
// Regular mappers
if (ramSize != UNSPECIFIED) {
warnNonZero(rom0, 0x149, 1, "RAM size");
rom0[0x149] = ramSize; rom0[0x149] = ramSize;
}
if (!japanese) if (!japanese) {
warnNonZero(rom0, 0x14a, 1, "destination code");
rom0[0x14a] = 0x01; rom0[0x14a] = 0x01;
}
}
if (oldLicensee != UNSPECIFIED) if (oldLicensee != UNSPECIFIED) {
warnNonZero(rom0, 0x14b, 1, "old licensee code");
rom0[0x14b] = oldLicensee; rom0[0x14b] = oldLicensee;
}
if (romVersion != UNSPECIFIED) if (romVersion != UNSPECIFIED) {
warnNonZero(rom0, 0x14c, 1, "mask ROM version number");
rom0[0x14c] = romVersion; rom0[0x14c] = romVersion;
}
// Remain to be handled the ROM size, and header checksum. // Remain to be handled the ROM size, and header checksum.
// The latter depends on the former, and so will be handled after it. // The latter depends on the former, and so will be handled after it.
@@ -818,6 +1057,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
for (uint16_t i = 0x134; i < 0x14d; i++) for (uint16_t i = 0x134; i < 0x14d; i++)
sum -= rom0[i] + 1; sum -= rom0[i] + 1;
warnNonZero(rom0, 0x14d, 1, "header checksum");
rom0[0x14d] = fixSpec & TRASH_HEADER_SUM ? ~sum : sum; rom0[0x14d] = fixSpec & TRASH_HEADER_SUM ? ~sum : sum;
} }
@@ -841,6 +1081,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
if (fixSpec & TRASH_GLOBAL_SUM) if (fixSpec & TRASH_GLOBAL_SUM)
globalSum = ~globalSum; globalSum = ~globalSum;
warnNonZero(rom0, 0x14e, 2, "global checksum");
rom0[0x14e] = globalSum >> 8; rom0[0x14e] = globalSum >> 8;
rom0[0x14f] = globalSum & 0xff; rom0[0x14f] = globalSum & 0xff;
} }
@@ -855,7 +1096,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
// If modifying the file in-place, we only need to edit the header // If modifying the file in-place, we only need to edit the header
// However, padding may have modified ROM0 (added padding), so don't in that case // However, padding may have modified ROM0 (added padding), so don't in that case
if (padValue == UNSPECIFIED) if (padValue == UNSPECIFIED)
rom0Len = 0x150; rom0Len = headerSize;
} }
ssize_t writeLen = writeBytes(output, rom0, rom0Len); ssize_t writeLen = writeBytes(output, rom0, rom0Len);
@@ -863,8 +1104,8 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
report("FATAL: Failed to write \"%s\"'s ROM0: %s\n", name, strerror(errno)); report("FATAL: Failed to write \"%s\"'s ROM0: %s\n", name, strerror(errno));
goto free_romx; goto free_romx;
} else if (writeLen < rom0Len) { } else if (writeLen < rom0Len) {
report("FATAL: Could only write %ld of \"%s\"'s %ld ROM0 bytes\n", report("FATAL: Could only write %jd of \"%s\"'s %jd ROM0 bytes\n",
writeLen, name, rom0Len); (intmax_t)writeLen, name, (intmax_t)rom0Len);
goto free_romx; goto free_romx;
} }
@@ -877,8 +1118,8 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
report("FATAL: Failed to write \"%s\"'s ROMX: %s\n", name, strerror(errno)); report("FATAL: Failed to write \"%s\"'s ROMX: %s\n", name, strerror(errno));
goto free_romx; goto free_romx;
} else if ((size_t)writeLen < totalRomxLen) { } else if ((size_t)writeLen < totalRomxLen) {
report("FATAL: Could only write %ld of \"%s\"'s %ld ROMX bytes\n", report("FATAL: Could only write %jd of \"%s\"'s %zu ROMX bytes\n",
writeLen, name, totalRomxLen); (intmax_t)writeLen, name, totalRomxLen);
goto free_romx; goto free_romx;
} }
} }
@@ -915,8 +1156,6 @@ free_romx:
free(romx); free(romx);
} }
#undef trySeek
static bool processFilename(char const *name) static bool processFilename(char const *name)
{ {
nbErrors = 0; nbErrors = 0;
@@ -949,8 +1188,8 @@ static bool processFilename(char const *name)
} else if (stat.st_size < 0x150) { } else if (stat.st_size < 0x150) {
// This check is in theory redundant with the one in `processFile`, but it // This check is in theory redundant with the one in `processFile`, but it
// prevents passing a file size of 0, which usually indicates pipes // prevents passing a file size of 0, which usually indicates pipes
report("FATAL: \"%s\" too short, expected at least 336 ($150) bytes, got only %ld\n", report("FATAL: \"%s\" too short, expected at least 336 ($150) bytes, got only %jd\n",
name, stat.st_size); name, (intmax_t)stat.st_size);
} else { } else {
processFile(input, input, name, stat.st_size); processFile(input, input, name, stat.st_size);
} }
@@ -967,7 +1206,7 @@ fail:
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
nbErrors = 0; nbErrors = 0;
char ch; int ch;
while ((ch = musl_getopt_long_only(argc, argv, optstring, longopts, NULL)) != -1) { while ((ch = musl_getopt_long_only(argc, argv, optstring, longopts, NULL)) != -1) {
switch (ch) { switch (ch) {
@@ -1015,7 +1254,7 @@ do { \
#define SPEC_H TRASH_HEADER_SUM #define SPEC_H TRASH_HEADER_SUM
#define SPEC_g FIX_GLOBAL_SUM #define SPEC_g FIX_GLOBAL_SUM
#define SPEC_G TRASH_GLOBAL_SUM #define SPEC_G TRASH_GLOBAL_SUM
#define or(new, bad) \ #define overrideSpec(new, bad) \
do { \ do { \
if (fixSpec & SPEC_##bad) \ if (fixSpec & SPEC_##bad) \
fprintf(stderr, \ fprintf(stderr, \
@@ -1023,30 +1262,30 @@ do { \
fixSpec = (fixSpec & ~SPEC_##bad) | SPEC_##new; \ fixSpec = (fixSpec & ~SPEC_##bad) | SPEC_##new; \
} while (0) } while (0)
case 'l': case 'l':
or(l, L); overrideSpec(l, L);
break; break;
case 'L': case 'L':
or(L, l); overrideSpec(L, l);
break; break;
case 'h': case 'h':
or(h, H); overrideSpec(h, H);
break; break;
case 'H': case 'H':
or(H, h); overrideSpec(H, h);
break; break;
case 'g': case 'g':
or(g, G); overrideSpec(g, G);
break; break;
case 'G': case 'G':
or(G, g); overrideSpec(G, g);
break; break;
default: default:
fprintf(stderr, "warning: Ignoring '%c' in fix spec\n", fprintf(stderr, "warning: Ignoring '%c' in fix spec\n",
*musl_optarg); *musl_optarg);
#undef or #undef overrideSpec
} }
musl_optarg++; musl_optarg++;
} }
@@ -1091,10 +1330,13 @@ do { \
case 'm': case 'm':
cartridgeType = parseMBC(musl_optarg); cartridgeType = parseMBC(musl_optarg);
if (cartridgeType == MBC_BAD) { if (cartridgeType == MBC_BAD) {
report("error: Unknown MBC \"%s\"\n", musl_optarg); report("error: Unknown MBC \"%s\"\nAccepted MBC names:\n",
} else if (cartridgeType == MBC_WRONG_FEATURES) {
report("error: Features incompatible with MBC (\"%s\")\n",
musl_optarg); musl_optarg);
printAcceptedMBCNames();
} else if (cartridgeType == MBC_WRONG_FEATURES) {
report("error: Features incompatible with MBC (\"%s\")\nAccepted combinations:\n",
musl_optarg);
printAcceptedMBCNames();
} else if (cartridgeType == MBC_BAD_RANGE) { } else if (cartridgeType == MBC_BAD_RANGE) {
report("error: Specified MBC ID out of range 0-255: %s\n", report("error: Specified MBC ID out of range 0-255: %s\n",
musl_optarg); musl_optarg);
@@ -1148,7 +1390,11 @@ do { \
#undef parseByte #undef parseByte
} }
if (ramSize != UNSPECIFIED && cartridgeType < UNSPECIFIED) { if ((cartridgeType & 0xff00) == TPP1 && !japanese)
fprintf(stderr, "warning: TPP1 overwrites region flag for its identification code, ignoring `-j`\n");
// Check that RAM size is correct for "standard" mappers
if (ramSize != UNSPECIFIED && (cartridgeType & 0xff00) == 0) {
if (cartridgeType == ROM_RAM || cartridgeType == ROM_RAM_BATTERY) { if (cartridgeType == ROM_RAM || cartridgeType == ROM_RAM_BATTERY) {
if (ramSize != 1) if (ramSize != 1)
fprintf(stderr, "warning: MBC \"%s\" should have 2kiB of RAM (-r 1)\n", fprintf(stderr, "warning: MBC \"%s\" should have 2kiB of RAM (-r 1)\n",

View File

@@ -112,7 +112,13 @@ This value is deprecated and should be set to 0x33 in all new software.
Set the MBC type Set the MBC type
.Pq 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. .Pp
This value may also be an MBC name.
The list of accepted names can be obtained by passing "help" as the argument.
Any amount of whitespace (space and tabs) is allowed around plus signs, and the order of "components" is free, as long as the MBC name is first.
There are special considerations to take for the TPP1 mapper; see the
.Sx TPP1
section below.
.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
.Pq Ad 0x14C .Pq Ad 0x14C
@@ -179,6 +185,46 @@ 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
.Sh TPP1
TPP1 is a homebrew mapper designed as a functional superset of the common traditional MBCs, allowing larger ROM and RAM sizes combined with other hardware features.
Its specification, as well as more resources, can be found online at
.Lk https://github.com/TwitchPlaysPokemon/tpp1 .
.Ss MBC name
The MBC name for TPP1 is more complex than standard mappers.
It must be followed with the revision number, of the form
.Ql major.minor ,
where both
.Ql major
and
.Ql minor
are decimal, 8-bit integers.
There may be any amount of spaces or underscores between
.Ql TPP1
and the revision number.
.Nm
only supports 1.x revisions, and will reject everything else.
.Pp
Like other mappers, the name may be followed with a list of optional,
.Ql + Ns
-separated features; however,
.Ql RAM
should not be specified, as the TPP1 mapper implicitly requests RAM if a non-zero RAM size is specified.
Therefore,
.Nm
will ignore the
.Ql RAM
feature on a TPP1 mapper with a warning.
.Ss Special considerations
TPP1 overwrites the byte at
.Ad 0x14A ,
usually indicating the region destination
.Pq see Fl j ,
with one of its three identification bytes.
Therefore,
.Nm
will warn about and ignore
.Fl j
if used in combination with TPP1.
.Sh BUGS .Sh BUGS
Please report bugs on Please report bugs on
.Lk https://github.com/gbdev/rgbds/issues GitHub . .Lk https://github.com/gbdev/rgbds/issues GitHub .

View File

@@ -224,7 +224,11 @@ void create_mapfiles(const struct Options *opts, struct GBImage *gb,
if (!tile) if (!tile)
err(1, "%s: Failed to allocate memory for tile", err(1, "%s: Failed to allocate memory for tile",
__func__); __func__);
for (i = 0; i < tile_size; i++) { /*
* If the input image doesn't fill the last tile,
* `gb_i` will reach `gb_size`.
*/
for (i = 0; i < tile_size && gb_i < gb_size; i++) {
tile[i] = gb->data[gb_i]; tile[i] = gb->data[gb_i];
gb_i++; gb_i++;
} }

View File

@@ -337,7 +337,7 @@ static void placeSection(struct Section *section)
section->name, typeNames[section->type], where); section->name, typeNames[section->type], where);
/* If the section just can't fit the bank, report that */ /* If the section just can't fit the bank, report that */
else if (section->org + section->size > endaddr(section->type) + 1) else if (section->org + section->size > endaddr(section->type) + 1)
errx(1, "Unable to place \"%s\" (%s section) %s: section runs past end of region ($%04" PRIx16 " > $%04" PRIx16 ")", errx(1, "Unable to place \"%s\" (%s section) %s: section runs past end of region ($%04x > $%04x)",
section->name, typeNames[section->type], where, section->name, typeNames[section->type], where,
section->org + section->size, endaddr(section->type) + 1); section->org + section->size, endaddr(section->type) + 1);
/* Otherwise there is overlap with another section */ /* Otherwise there is overlap with another section */

View File

@@ -48,6 +48,7 @@ static struct Assertion *assertions;
do { \ do { \
FILE *tmpFile = file; \ FILE *tmpFile = file; \
type tmpVal = func(tmpFile); \ type tmpVal = func(tmpFile); \
/* TODO: maybe mark the condition as `unlikely`; how to do that portably? */ \
if (tmpVal == (errval)) { \ if (tmpVal == (errval)) { \
errx(1, __VA_ARGS__, feof(tmpFile) \ errx(1, __VA_ARGS__, feof(tmpFile) \
? "Unexpected end of file" \ ? "Unexpected end of file" \
@@ -86,7 +87,6 @@ static int64_t readlong(FILE *file)
/** /**
* Helper macro for reading longs from a file, and errors out if it fails to. * Helper macro for reading longs from a file, and errors out if it fails to.
* Not as a function to avoid overhead in the general case. * Not as a function to avoid overhead in the general case.
* TODO: maybe mark the condition as `unlikely`; how to do that portably?
* @param var The variable to stash the number into * @param var The variable to stash the number into
* @param file The file to read from. Its position will be advanced * @param file The file to read from. Its position will be advanced
* @param ... A format string and related arguments; note that an extra string * @param ... A format string and related arguments; note that an extra string
@@ -101,7 +101,6 @@ static int64_t readlong(FILE *file)
* Helper macro for reading bytes from a file, and errors out if it fails to. * Helper macro for reading bytes from a file, and errors out if it fails to.
* Differs from `tryGetc` in that the backing function is fgetc(1). * Differs from `tryGetc` in that the backing function is fgetc(1).
* Not as a function to avoid overhead in the general case. * Not as a function to avoid overhead in the general case.
* TODO: maybe mark the condition as `unlikely`; how to do that portably?
* @param var The variable to stash the number into * @param var The variable to stash the number into
* @param file The file to read from. Its position will be advanced * @param file The file to read from. Its position will be advanced
* @param ... A format string and related arguments; note that an extra string * @param ... A format string and related arguments; note that an extra string
@@ -114,7 +113,6 @@ static int64_t readlong(FILE *file)
* Helper macro for reading bytes from a file, and errors out if it fails to. * Helper macro for reading bytes from a file, and errors out if it fails to.
* Differs from `tryGetc` in that the backing function is fgetc(1). * Differs from `tryGetc` in that the backing function is fgetc(1).
* Not as a function to avoid overhead in the general case. * Not as a function to avoid overhead in the general case.
* TODO: maybe mark the condition as `unlikely`; how to do that portably?
* @param var The variable to stash the number into * @param var The variable to stash the number into
* @param file The file to read from. Its position will be advanced * @param file The file to read from. Its position will be advanced
* @param ... A format string and related arguments; note that an extra string * @param ... A format string and related arguments; note that an extra string
@@ -163,7 +161,6 @@ static char *readstr(FILE *file)
/** /**
* Helper macro for reading bytes from a file, and errors out if it fails to. * Helper macro for reading bytes from a file, and errors out if it fails to.
* Not as a function to avoid overhead in the general case. * Not as a function to avoid overhead in the general case.
* TODO: maybe mark the condition as `unlikely`; how to do that portably?
* @param var The variable to stash the string into * @param var The variable to stash the string into
* @param file The file to read from. Its position will be advanced * @param file The file to read from. Its position will be advanced
* @param ... A format string and related arguments; note that an extra string * @param ... A format string and related arguments; note that an extra string
@@ -643,10 +640,21 @@ void obj_Setup(unsigned int nbFiles)
nodes = malloc(sizeof(*nodes) * nbFiles); nodes = malloc(sizeof(*nodes) * nbFiles);
} }
static void freeNode(struct FileStackNode *node)
{
if (node->type == NODE_REPT)
free(node->iters);
else
free(node->name);
}
static void freeSection(struct Section *section, void *arg) static void freeSection(struct Section *section, void *arg)
{ {
(void)arg; (void)arg;
do {
struct Section *next = section->nextu;
free(section->name); free(section->name);
if (sect_HasData(section->type)) { if (sect_HasData(section->type)) {
free(section->data); free(section->data);
@@ -656,6 +664,9 @@ static void freeSection(struct Section *section, void *arg)
} }
free(section->symbols); free(section->symbols);
free(section); free(section);
section = next;
} while (section);
} }
static void freeSymbol(struct Symbol *symbol) static void freeSymbol(struct Symbol *symbol)
@@ -668,8 +679,7 @@ void obj_Cleanup(void)
{ {
for (unsigned int i = 0; i < nbObjFiles; i++) { for (unsigned int i = 0; i < nbObjFiles; i++) {
for (uint32_t j = 0; j < nodes[i].nbNodes; j++) { for (uint32_t j = 0; j < nodes[i].nbNodes; j++) {
if (nodes[i].nodes[j].type == NODE_REPT) freeNode(&nodes[i].nodes[j]);
free(nodes[i].nodes[j].iters);
} }
free(nodes[i].nodes); free(nodes[i].nodes);
} }
@@ -680,16 +690,12 @@ void obj_Cleanup(void)
sect_ForEach(freeSection, NULL); sect_ForEach(freeSection, NULL);
sect_CleanupSections(); sect_CleanupSections();
struct SymbolList *list = symbolLists; for (struct SymbolList *list = symbolLists, *next; list; list = next) {
next = list->next;
while (list) {
for (size_t i = 0; i < list->nbSymbols; i++) for (size_t i = 0; i < list->nbSymbols; i++)
freeSymbol(list->symbolList[i]); freeSymbol(list->symbolList[i]);
free(list->symbolList); free(list->symbolList);
struct SymbolList *next = list->next;
free(list); free(list);
list = next;
} }
} }

View File

@@ -6,6 +6,7 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
#include <assert.h>
#include <inttypes.h> #include <inttypes.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
@@ -33,6 +34,12 @@ struct SortedSection {
struct SortedSection *next; struct SortedSection *next;
}; };
struct SortedSymbol {
struct Symbol const *sym;
uint32_t idx;
uint16_t addr;
};
static struct { static struct {
uint32_t nbBanks; uint32_t nbBanks;
struct SortedSections { struct SortedSections {
@@ -268,69 +275,86 @@ static struct SortedSection const **nextSection(struct SortedSection const **s1,
return (*s1)->section->org < (*s2)->section->org ? s1 : s2; return (*s1)->section->org < (*s2)->section->org ? s1 : s2;
} }
/*
* Comparator function for `qsort` to sort symbols
* Symbols are ordered by address, or else by original index for a stable sort
*/
static int compareSymbols(void const *a, void const *b)
{
struct SortedSymbol const *sym1 = (struct SortedSymbol const *)a;
struct SortedSymbol const *sym2 = (struct SortedSymbol const *)b;
if (sym1->addr != sym2->addr)
return sym1->addr < sym2->addr ? -1 : 1;
return sym1->idx < sym2->idx ? -1 : sym1->idx > sym2->idx ? 1 : 0;
}
/** /**
* Write a bank's contents to the sym file * Write a bank's contents to the sym file
* @param bankSections The bank's sections * @param bankSections The bank's sections
*/ */
static void writeSymBank(struct SortedSections const *bankSections) static void writeSymBank(struct SortedSections const *bankSections,
enum SectionType type, uint32_t bank)
{ {
if (!symFile) if (!symFile)
return; return;
struct { uint32_t nbSymbols = 0;
struct SortedSection const *sections;
#define sect sections->section /* Fake member as a shortcut */
uint32_t i;
struct Symbol const *sym;
uint16_t addr;
} sectList = { .sections = bankSections->sections, .i = 0 },
zlSectList = { .sections = bankSections->zeroLenSections, .i = 0 },
*minSectList;
for (;;) { for (struct SortedSection const *ptr = bankSections->zeroLenSections; ptr; ptr = ptr->next) {
while (sectList.sections for (struct Section const *sect = ptr->section; sect; sect = sect->nextu)
&& sectList.i == sectList.sect->nbSymbols) { nbSymbols += sect->nbSymbols;
sectList.sections = sectList.sections->next;
sectList.i = 0;
} }
while (zlSectList.sections for (struct SortedSection const *ptr = bankSections->sections; ptr; ptr = ptr->next) {
&& zlSectList.i == zlSectList.sect->nbSymbols) { for (struct Section const *sect = ptr->section; sect; sect = sect->nextu)
zlSectList.sections = zlSectList.sections->next; nbSymbols += sect->nbSymbols;
zlSectList.i = 0;
} }
if (!sectList.sections && !zlSectList.sections) { if (!nbSymbols)
break; return;
} else if (sectList.sections && zlSectList.sections) {
sectList.sym = sectList.sect->symbols[sectList.i];
zlSectList.sym = zlSectList.sect->symbols[zlSectList.i];
sectList.addr =
sectList.sym->offset + sectList.sect->org;
zlSectList.addr =
zlSectList.sym->offset + zlSectList.sect->org;
minSectList = sectList.addr < zlSectList.addr struct SortedSymbol *symList = malloc(sizeof(*symList) * nbSymbols);
? &sectList
: &zlSectList;
} else if (sectList.sections) {
sectList.sym = sectList.sect->symbols[sectList.i];
sectList.addr =
sectList.sym->offset + sectList.sect->org;
minSectList = &sectList; if (!symList)
} else { err(1, "Failed to allocate symbol list");
zlSectList.sym = zlSectList.sect->symbols[zlSectList.i];
zlSectList.addr =
zlSectList.sym->offset + zlSectList.sect->org;
minSectList = &zlSectList; uint32_t idx = 0;
for (struct SortedSection const *ptr = bankSections->zeroLenSections; ptr; ptr = ptr->next) {
for (struct Section const *sect = ptr->section; sect; sect = sect->nextu) {
for (uint32_t i = 0; i < sect->nbSymbols; i++) {
symList[idx].idx = idx;
symList[idx].sym = sect->symbols[i];
symList[idx].addr = symList[idx].sym->offset + sect->org;
idx++;
} }
}
}
for (struct SortedSection const *ptr = bankSections->sections; ptr; ptr = ptr->next) {
for (struct Section const *sect = ptr->section; sect; sect = sect->nextu) {
for (uint32_t i = 0; i < sect->nbSymbols; i++) {
symList[idx].idx = idx;
symList[idx].sym = sect->symbols[i];
symList[idx].addr = symList[idx].sym->offset + sect->org;
idx++;
}
}
}
assert(idx == nbSymbols);
qsort(symList, nbSymbols, sizeof(*symList), compareSymbols);
uint32_t symBank = bank + bankranges[type][0];
for (uint32_t i = 0; i < nbSymbols; i++) {
struct SortedSymbol *sym = &symList[i];
fprintf(symFile, "%02" PRIx32 ":%04" PRIx16 " %s\n", fprintf(symFile, "%02" PRIx32 ":%04" PRIx16 " %s\n",
minSectList->sect->bank, minSectList->addr, symBank, sym->addr, sym->sym->name);
minSectList->sym->name);
minSectList->i++;
} }
#undef sect
free(symList);
} }
/** /**
@@ -360,7 +384,8 @@ static uint16_t writeMapBank(struct SortedSections const *sectList,
used += sect->size; used += sect->size;
if (sect->size != 0) if (sect->size != 0)
fprintf(mapFile, " SECTION: $%04" PRIx16 "-$%04" PRIx16 " ($%04" PRIx16 " byte%s) [\"%s\"]\n", fprintf(mapFile, " SECTION: $%04" PRIx16 "-$%04x ($%04" PRIx16
" byte%s) [\"%s\"]\n",
sect->org, sect->org + sect->size - 1, sect->org, sect->org + sect->size - 1,
sect->size, sect->size == 1 ? "" : "s", sect->size, sect->size == 1 ? "" : "s",
sect->name); sect->name);
@@ -443,7 +468,7 @@ static void writeSymAndMap(void)
for (uint32_t bank = 0; bank < sections[type].nbBanks; bank++) { for (uint32_t bank = 0; bank < sections[type].nbBanks; bank++) {
struct SortedSections const *sect = &sections[type].banks[bank]; struct SortedSections const *sect = &sections[type].banks[bank];
writeSymBank(sect); writeSymBank(sect, type, bank);
usedMap[type] += writeMapBank(sect, type, bank); usedMap[type] += writeMapBank(sect, type, bank);
} }
} }

View File

@@ -292,6 +292,11 @@ static int32_t computeRPNExpr(struct Patch const *patch,
break; break;
case RPN_BANK_SECT: case RPN_BANK_SECT:
/*
* `expression` is not guaranteed to be '\0'-terminated. If it is not,
* `getRPNByte` will have a fatal internal error.
* In either case, `getRPNByte` will not free `expression`.
*/
name = (char const *)expression; name = (char const *)expression;
while (getRPNByte(&expression, &size, patch->src, patch->lineNo)) while (getRPNByte(&expression, &size, patch->src, patch->lineNo))
; ;
@@ -320,6 +325,44 @@ static int32_t computeRPNExpr(struct Patch const *patch,
} }
break; break;
case RPN_SIZEOF_SECT:
/* This has assumptions commented in the `RPN_BANK_SECT` case above. */
name = (char const *)expression;
while (getRPNByte(&expression, &size, patch->src, patch->lineNo))
;
sect = sect_GetSection(name);
if (!sect) {
error(patch->src, patch->lineNo,
"Requested SIZEOF() of section \"%s\", which was not found",
name);
isError = true;
value = 1;
} else {
value = sect->size;
}
break;
case RPN_STARTOF_SECT:
/* This has assumptions commented in the `RPN_BANK_SECT` case above. */
name = (char const *)expression;
while (getRPNByte(&expression, &size, patch->src, patch->lineNo))
;
sect = sect_GetSection(name);
if (!sect) {
error(patch->src, patch->lineNo,
"Requested STARTOF() of section \"%s\", which was not found",
name);
isError = true;
value = 1;
} else {
value = sect->org;
}
break;
case RPN_HRAM: case RPN_HRAM:
value = popRPN(); value = popRPN();
if (!isError && (value < 0 if (!isError && (value < 0
@@ -435,6 +478,8 @@ void patch_CheckAssertions(struct Assertion *assert)
} }
struct Assertion *next = assert->next; struct Assertion *next = assert->next;
free(assert->patch.rpnExpression);
free(assert->message);
free(assert); free(assert);
assert = next; assert = next;
} }

View File

@@ -43,8 +43,7 @@ static void pushFile(char *newFileName)
if (!fileStackSize) /* Init file stack */ if (!fileStackSize) /* Init file stack */
fileStackSize = 4; fileStackSize = 4;
fileStackSize *= 2; fileStackSize *= 2;
fileStack = realloc(fileStack, fileStack = realloc(fileStack, sizeof(*fileStack) * fileStackSize);
sizeof(*fileStack) * fileStackSize);
if (!fileStack) if (!fileStack)
err(1, "%s(%" PRIu32 "): Internal INCLUDE error", err(1, "%s(%" PRIu32 "): Internal INCLUDE error",
linkerScriptName, lineNo); linkerScriptName, lineNo);
@@ -173,11 +172,11 @@ static char const * const commands[] = {
[COMMAND_ALIGN] = "ALIGN" [COMMAND_ALIGN] = "ALIGN"
}; };
static int readChar(FILE *file) static int nextChar(void)
{ {
int curchar = getc(file); int curchar = getc(linkerScript);
if (curchar == EOF && ferror(file)) if (curchar == EOF && ferror(linkerScript))
err(1, "%s(%" PRIu32 "): Unexpected error in %s", err(1, "%s(%" PRIu32 "): Unexpected error in %s",
linkerScriptName, lineNo, __func__); linkerScriptName, lineNo, __func__);
return curchar; return curchar;
@@ -194,14 +193,14 @@ static struct LinkerScriptToken *nextToken(void)
/* Skip initial whitespace... */ /* Skip initial whitespace... */
do do
curchar = readChar(linkerScript); curchar = nextChar();
while (isWhiteSpace(curchar)); while (isWhiteSpace(curchar));
/* If this is a comment, skip to the end of the line */ /* If this is a comment, skip to the end of the line */
if (curchar == ';') { if (curchar == ';') {
do do {
curchar = readChar(linkerScript); curchar = nextChar();
while (!isNewline(curchar) && curchar != EOF); } while (!isNewline(curchar) && curchar != EOF);
} }
if (curchar == EOF) { if (curchar == EOF) {
@@ -210,9 +209,14 @@ static struct LinkerScriptToken *nextToken(void)
/* If we have a newline char, this is a newline token */ /* If we have a newline char, this is a newline token */
token.type = TOKEN_NEWLINE; token.type = TOKEN_NEWLINE;
/* FIXME: This works with CRLF newlines, but not CR-only */ if (curchar == '\r') {
if (curchar == '\r') /* Handle CRLF */
readChar(linkerScript); /* Read and discard LF */ curchar = nextChar();
if (curchar != '\n') {
ungetc(curchar, linkerScript);
curchar = '\r';
}
}
} else if (curchar == '"') { } else if (curchar == '"') {
/* If we have a string start, this is a string */ /* If we have a string start, this is a string */
token.type = TOKEN_STRING; token.type = TOKEN_STRING;
@@ -222,18 +226,33 @@ static struct LinkerScriptToken *nextToken(void)
size_t capacity = 16; /* Half of the default capacity */ size_t capacity = 16; /* Half of the default capacity */
do { do {
curchar = readChar(linkerScript); curchar = nextChar();
if (curchar == EOF || isNewline(curchar)) {
errx(1, "%s(%" PRIu32 "): Unterminated string",
linkerScriptName, lineNo);
} else if (curchar == '"') {
/* Quotes force a string termination */
curchar = '\0';
} else if (curchar == '\\') {
/* Backslashes are escape sequences */
curchar = nextChar();
if (curchar == EOF || isNewline(curchar)) if (curchar == EOF || isNewline(curchar))
errx(1, "%s(%" PRIu32 "): Unterminated string", errx(1, "%s(%" PRIu32 "): Unterminated string",
linkerScriptName, lineNo); linkerScriptName, lineNo);
else if (curchar == '"') else if (curchar == 'n')
/* Quotes force a string termination */ curchar = '\n';
curchar = '\0'; else if (curchar == 'r')
curchar = '\r';
else if (curchar == 't')
curchar = '\t';
else if (curchar != '\\' && curchar != '"')
errx(1, "%s(%" PRIu32 "): Illegal character escape",
linkerScriptName, lineNo);
}
if (size >= capacity || token.attr.string == NULL) { if (size >= capacity || token.attr.string == NULL) {
capacity *= 2; capacity *= 2;
token.attr.string = realloc(token.attr.string, token.attr.string = realloc(token.attr.string, capacity);
capacity);
if (!token.attr.string) if (!token.attr.string)
err(1, "%s: Failed to allocate memory for string", err(1, "%s: Failed to allocate memory for string",
__func__); __func__);
@@ -260,10 +279,9 @@ static struct LinkerScriptToken *nextToken(void)
if (!curchar) if (!curchar)
break; break;
curchar = readChar(linkerScript); curchar = nextChar();
/* Whitespace, a newline or a comment end the token */ /* Whitespace, a newline or a comment end the token */
if (isWhiteSpace(curchar) || isNewline(curchar) if (isWhiteSpace(curchar) || isNewline(curchar) || curchar == ';') {
|| curchar == ';') {
ungetc(curchar, linkerScript); ungetc(curchar, linkerScript);
curchar = '\0'; curchar = '\0';
} }
@@ -282,8 +300,7 @@ static struct LinkerScriptToken *nextToken(void)
if (token.type == TOKEN_INVALID) { if (token.type == TOKEN_INVALID) {
/* Try to match a bank specifier */ /* Try to match a bank specifier */
for (enum SectionType type = 0; type < SECTTYPE_INVALID; for (enum SectionType type = 0; type < SECTTYPE_INVALID; type++) {
type++) {
if (!strcmp(typeNames[type], str)) { if (!strcmp(typeNames[type], str)) {
token.type = TOKEN_BANK; token.type = TOKEN_BANK;
token.attr.secttype = type; token.attr.secttype = type;
@@ -313,8 +330,7 @@ static struct LinkerScriptToken *nextToken(void)
return &token; return &token;
} }
static void processCommand(enum LinkerScriptCommand command, uint16_t arg, static void processCommand(enum LinkerScriptCommand command, uint16_t arg, uint16_t *pc)
uint16_t *pc)
{ {
switch (command) { switch (command) {
case COMMAND_INVALID: case COMMAND_INVALID:
@@ -459,8 +475,7 @@ struct SectionPlacement *script_NextSection(void)
errx(1, "%s(%" PRIu32 "): Command specified without an argument", errx(1, "%s(%" PRIu32 "): Command specified without an argument",
linkerScriptName, lineNo); linkerScriptName, lineNo);
processCommand(attr.command, arg, processCommand(attr.command, arg, &curaddr[type][bankID]);
&curaddr[type][bankID]);
} else { /* TOKEN_BANK */ } else { /* TOKEN_BANK */
type = attr.secttype; type = attr.secttype;
/* /*

View File

@@ -49,8 +49,8 @@ static void checkSectUnionCompat(struct Section *target, struct Section *other)
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 errx(1, "Section \"%s\" is defined with conflicting %d-byte alignment (offset %"
"-byte alignment (offset %" PRIu16 ") and address $%04" PRIx16, PRIu16 ") and address $%04" PRIx16,
other->name, target->alignMask + 1, other->name, target->alignMask + 1,
target->alignOfs, other->org); target->alignOfs, other->org);
} }
@@ -61,15 +61,14 @@ static void checkSectUnionCompat(struct Section *target, struct Section *other)
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 $%04" errx(1, "Section \"%s\" is defined with conflicting address $%04"
PRIx16 " and %" PRIu16 "-byte alignment (offset %" PRIu16 ")", PRIx16 " and %d-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 errx(1, "Section \"%s\" is defined with conflicting %d-byte alignment (offset %"
"-byte alignment (offset %" PRIu16 ") and %" PRIu16 PRIu16 ") and %d-byte alignment (offset %" PRIu16 ")",
"-byte alignment (offset %" PRIu16 ")",
other->name, target->alignMask + 1, target->alignOfs, other->name, target->alignMask + 1, target->alignOfs,
other->alignMask + 1, other->alignOfs); other->alignMask + 1, other->alignOfs);
} else if (!target->isAlignFixed || (other->alignMask > target->alignMask)) { } else if (!target->isAlignFixed || (other->alignMask > target->alignMask)) {
@@ -92,8 +91,8 @@ static void checkFragmentCompat(struct Section *target, struct Section *other)
} else if (target->isAlignFixed) { } else if (target->isAlignFixed) {
if ((org - target->alignOfs) & target->alignMask) if ((org - target->alignOfs) & target->alignMask)
errx(1, "Section \"%s\" is defined with conflicting %" PRIu16 errx(1, "Section \"%s\" is defined with conflicting %d-byte alignment (offset %"
"-byte alignment (offset %" PRIu16 ") and address $%04" PRIx16, PRIu16 ") and address $%04" PRIx16,
other->name, target->alignMask + 1, other->name, target->alignMask + 1,
target->alignOfs, other->org); target->alignOfs, other->org);
} }
@@ -109,15 +108,14 @@ static void checkFragmentCompat(struct Section *target, struct Section *other)
if (target->isAddressFixed) { if (target->isAddressFixed) {
if ((target->org - ofs) & other->alignMask) if ((target->org - ofs) & other->alignMask)
errx(1, "Section \"%s\" is defined with conflicting address $%04" errx(1, "Section \"%s\" is defined with conflicting address $%04"
PRIx16 " and %" PRIu16 "-byte alignment (offset %" PRIu16 ")", PRIx16 " and %d-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) != (target->alignMask & ofs)) { && (other->alignMask & target->alignOfs) != (target->alignMask & ofs)) {
errx(1, "Section \"%s\" is defined with conflicting %" PRIu16 errx(1, "Section \"%s\" is defined with conflicting %d-byte alignment (offset %"
"-byte alignment (offset %" PRIu16 ") and %" PRIu16 PRIu16 ") and %d-byte alignment (offset %" PRIu16 ")",
"-byte alignment (offset %" PRIu16 ")",
other->name, target->alignMask + 1, target->alignOfs, other->name, target->alignMask + 1, target->alignOfs,
other->alignMask + 1, other->alignOfs); other->alignMask + 1, other->alignOfs);
@@ -165,6 +163,9 @@ static void mergeSections(struct Section *target, struct Section *other, enum Se
if (!target->data) if (!target->data)
errx(1, "Failed to concatenate \"%s\"'s fragments", target->name); errx(1, "Failed to concatenate \"%s\"'s fragments", target->name);
memcpy(target->data + target->size - other->size, other->data, other->size); memcpy(target->data + target->size - other->size, other->data, other->size);
/* Adjust patches' PC offsets */
for (uint32_t patchID = 0; patchID < other->nbPatches; patchID++)
other->patches[patchID].pcOffset += other->offset;
} }
break; break;
@@ -249,7 +250,7 @@ static void doSanityChecks(struct Section *section, void *ptr)
/* Too large an alignment may not be satisfiable */ /* Too large an alignment may not be satisfiable */
if (section->isAlignFixed && (section->alignMask & startaddr[section->type])) if (section->isAlignFixed && (section->alignMask & startaddr[section->type]))
fail("%s: %s sections cannot be aligned to $%04" PRIx16 " bytes", fail("%s: %s sections cannot be aligned to $%04x bytes",
section->name, typeNames[section->type], section->alignMask + 1); section->name, typeNames[section->type], section->alignMask + 1);
uint32_t minbank = bankranges[section->type][0], maxbank = bankranges[section->type][1]; uint32_t minbank = bankranges[section->type][0], maxbank = bankranges[section->type][1];
@@ -289,9 +290,9 @@ static void doSanityChecks(struct Section *section, void *ptr)
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 fail("Section \"%s\"'s end address %#x is greater than last address %#x",
" is greater than last address %#" PRIx16, section->name, section->name, section->org + section->size,
section->org + section->size, endaddr(section->type) + 1); endaddr(section->type) + 1);
} }
#undef fail #undef fail

View File

@@ -226,7 +226,7 @@ with some bytes being special prefixes for integers and symbols.
.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 $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
.It Li $13 Ta Li unary ~ .It Li $13 Ta Li unary ~
@@ -248,6 +248,10 @@ Symbol ID follows, where -1 means PC
.It Li $51 Ta Li BANK(section_name) , .It Li $51 Ta Li BANK(section_name) ,
a null-terminated string follows. a null-terminated string follows.
.It Li $52 Ta Li Current BANK() .It Li $52 Ta Li Current BANK()
.It Li $53 Ta Li SIZEOF(section_name) ,
a null-terminated string follows.
.It Li $54 Ta Li STARTOF(section_name) ,
a null-terminated string follows.
.It Li $60 Ta Li HRAMCheck . .It Li $60 Ta Li HRAMCheck .
Checks if the value is in HRAM, ANDs it with 0xFF. Checks if the value is in HRAM, ANDs it with 0xFF.
.It Li $61 Ta Li RSTCheck . .It Li $61 Ta Li RSTCheck .

View File

@@ -18,6 +18,10 @@ $ rgbasm \-o bar.o foo.asm
$ rgblink \-o baz.gb bar.o $ rgblink \-o baz.gb bar.o
$ rgbfix \-v \-p 0 baz.gb $ rgbfix \-v \-p 0 baz.gb
.Ed .Ed
Or in a single command line:
.Bd -literal -offset indent
$ rgbasm \-o - foo.asm | rgblink \-o - - | rgbfix \-v \-p 0 - > baz.gb
.Ed
.Sh SEE ALSO .Sh SEE ALSO
.Xr rgbasm 1 , .Xr rgbasm 1 ,
.Xr rgbfix 1 , .Xr rgbfix 1 ,

View File

@@ -1,2 +1,3 @@
FATAL: align-pc-outside-section.asm(1): ERROR: align-pc-outside-section.asm(1):
Code generation before SECTION directive Cannot output data outside of a SECTION
error: Assembly aborted (1 error)!

View File

@@ -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, unexpected newline syntax error, unexpected end of buffer
error: Assembly aborted (2 errors)! error: Assembly aborted (2 errors)!

View File

@@ -0,0 +1,19 @@
MACRO printargs
PRINTLN "first = \<1>"
FOR I, 2, _NARG
PRINTLN "next = \<{d:I}>"
ENDR
PRINTLN "last = \<{d:_NARG}>"
ENDM
printargs A, B, C, D
MACRO mac
println \<2__> + \<1_2> + \<\1>
x = 2
println \<{d:x}> + \<1_{d:x}> + \<\<\<13>>>
y equs "NARG"
println \<x> + \<1_{d:x}_> + \<\<\<_{y}>>>
ENDM
mac 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 1

View File

@@ -0,0 +1,7 @@
first = A
next = B
next = C
last = D
$F0
$F0
$F0

View File

@@ -0,0 +1,27 @@
charmap "<NULL>", $00
charmap "A", $10
charmap "B", $20
charmap "C", $30
charmap "Bold", $88
SECTION "test", ROM0
S EQUS "XBold<NULL>ABC"
assert CHARLEN("{S}") == 6
println CHARSUB("{S}", 2)
assert !STRCMP(CHARSUB("{S}", 2), "Bold")
assert CHARSUB("{S}", -5) == CHARSUB("{S}", CHARLEN("{S}") + 1 - 5)
assert CHARSUB("{S}", 2) == "Bold" && "Bold" == $88
assert CHARSUB("{S}", 1) == $58 ; ASCII "X"
db "{S}"
newcharmap ascii
assert CHARLEN("{S}") == 14
println CHARSUB("{S}", 2)
assert !STRCMP(CHARSUB("{S}", 2), "B")
assert CHARSUB("{S}", -5) == CHARSUB("{S}", CHARLEN("{S}") + 1 - 5)
assert CHARSUB("{S}", 2) == "B" && "B" == $42 ; ASCII "B"
assert CHARSUB("{S}", 1) == $58 ; ASCII "X"
db "{S}"

View File

@@ -0,0 +1,2 @@
Bold
B

Binary file not shown.

View File

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

View File

@@ -26,4 +26,5 @@ def constant equ 6*7 ; fails
redef string equs "there" redef string equs "there"
println "{string}" println "{string}"
redef constant equ 6*9 ; syntax error redef constant equ 6*9
println constant

View File

@@ -1,5 +1,3 @@
ERROR: def.asm(23): ERROR: def.asm(23):
'constant' already defined at def.asm(10) 'constant' already defined at def.asm(10)
ERROR: def.asm(29): error: Assembly aborted (1 error)!
syntax error, unexpected EQU, expecting SET or = or EQUS
error: Assembly aborted (2 errors)!

View File

@@ -7,3 +7,4 @@ here
$0 $1 $5 $9 $0 $1 $5 $9
$2A $2A
there there
$36

9
test/asm/ff00+c-bad.asm Normal file
View File

@@ -0,0 +1,9 @@
SECTION "ff00+c or not to ff00+c", ROMX
ld a, [$ff00 + c]
ld [65280 + c], a
; Not ok
ld a, [$ff01 + c]
ld [xyz + c], a

5
test/asm/ff00+c-bad.err Normal file
View File

@@ -0,0 +1,5 @@
ERROR: ff00+c-bad.asm(8):
Expected constant expression equal to $FF00 for "$ff00+c"
ERROR: ff00+c-bad.asm(9):
Expected constant expression equal to $FF00 for "$ff00+c"
error: Assembly aborted (2 errors)!

11
test/asm/ff00+c-label.asm Normal file
View File

@@ -0,0 +1,11 @@
CONSTANT equ 42
PRINTLN $ff00 + CONSTANT
SECTION "Overreading much?", ROM0[0]
jp $ff00 + CONSTANT
jp $ff00 + carryOnPlease
jp $ff00+CONSTANT
jp $ff00+carryOnPlease
carryOnPlease:

View File

@@ -0,0 +1 @@
$FF2A

View File

@@ -0,0 +1 @@
<EFBFBD>*<2A><> <0C><>*<2A><> <0C>

0
test/asm/ff00+c.err Normal file
View File

0
test/asm/ff00+c.out Normal file
View File

View File

@@ -0,0 +1,3 @@
ERROR: incbin-end-0.asm(1):
Cannot output data outside of a SECTION
error: Assembly aborted (1 error)!

View File

@@ -5,5 +5,8 @@ ERROR: interpolation-overflow.asm(4):
warning: interpolation-overflow.asm(4): [-Wlarge-constant] warning: interpolation-overflow.asm(4): [-Wlarge-constant]
Precision of fixed-point constant is too large Precision of fixed-point constant is too large
while expanding symbol "0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" while expanding symbol "0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
FATAL: interpolation-overflow.asm(4): ERROR: interpolation-overflow.asm(4):
Macro argument '\1' not defined Macro argument '\1' not defined
ERROR: interpolation-overflow.asm(4):
syntax error, unexpected number
error: Assembly aborted (4 errors)!

View File

@@ -0,0 +1 @@
\<>

View File

@@ -0,0 +1,3 @@
ERROR: invalid-empty-macro-arg.asm(1):
Empty bracketed macro argument
error: Assembly aborted (1 error)!

View File

View File

@@ -0,0 +1 @@
\<10!>

View File

@@ -0,0 +1,5 @@
ERROR: invalid-macro-arg-character.asm(1):
Invalid character in bracketed macro argument '!'
ERROR: invalid-macro-arg-character.asm(1):
syntax error, unexpected >
error: Assembly aborted (2 errors)!

View File

View File

@@ -0,0 +1 @@
\<foo>

View File

@@ -0,0 +1,3 @@
ERROR: invalid-macro-arg-symbol.asm(1):
Bracketed symbol "foo" does not exist
error: Assembly aborted (1 error)!

View File

View File

@@ -0,0 +1,23 @@
; characters:
; 1: U+0061 a
; 2: U+00E4 a with diaresis (0xC3 0xA4)
; 3: U+0062 b
; 4: U+6F22 kanji (0xE6 0xBC 0xA2)
; 5: U+002C ,
; 6: U+0061 a
; 7: invalid byte 0xA3
; 8: invalid byte 0xA4
; 9: U+0062 b
; 10: invalid bytes 0xE6 0xF0
; 11: invalid byte 0xA2
; 12: U+0021 !
invalid EQUS "aäb漢,a<><61>b<EFBFBD><62><EFBFBD>!"
n = STRLEN("{invalid}")
copy EQUS STRSUB("{invalid}", 1)
println "\"{invalid}\" == \"{copy}\" ({d:n})"
mid1 EQUS STRSUB("{invalid}", 5, 2)
mid2 EQUS STRSUB("{invalid}", 9, 1)
println "\"{mid2}{mid1}\""

View File

@@ -0,0 +1,45 @@
ERROR: invalid-utf-8-strings.asm(16):
STRLEN: Invalid UTF-8 byte 0xA3
ERROR: invalid-utf-8-strings.asm(16):
STRLEN: Invalid UTF-8 byte 0xA4
ERROR: invalid-utf-8-strings.asm(16):
STRLEN: Invalid UTF-8 byte 0xF0
ERROR: invalid-utf-8-strings.asm(16):
STRLEN: Invalid UTF-8 byte 0xA2
ERROR: invalid-utf-8-strings.asm(17):
STRLEN: Invalid UTF-8 byte 0xA3
ERROR: invalid-utf-8-strings.asm(17):
STRLEN: Invalid UTF-8 byte 0xA4
ERROR: invalid-utf-8-strings.asm(17):
STRLEN: Invalid UTF-8 byte 0xF0
ERROR: invalid-utf-8-strings.asm(17):
STRLEN: Invalid UTF-8 byte 0xA2
ERROR: invalid-utf-8-strings.asm(17):
STRSUB: Invalid UTF-8 byte 0xA3
ERROR: invalid-utf-8-strings.asm(17):
STRSUB: Invalid UTF-8 byte 0xA4
ERROR: invalid-utf-8-strings.asm(17):
STRSUB: Invalid UTF-8 byte 0xF0
ERROR: invalid-utf-8-strings.asm(17):
STRSUB: Invalid UTF-8 byte 0xA2
ERROR: invalid-utf-8-strings.asm(21):
STRLEN: Invalid UTF-8 byte 0xA3
ERROR: invalid-utf-8-strings.asm(21):
STRLEN: Invalid UTF-8 byte 0xA4
ERROR: invalid-utf-8-strings.asm(21):
STRLEN: Invalid UTF-8 byte 0xF0
ERROR: invalid-utf-8-strings.asm(21):
STRLEN: Invalid UTF-8 byte 0xA2
ERROR: invalid-utf-8-strings.asm(22):
STRLEN: Invalid UTF-8 byte 0xA3
ERROR: invalid-utf-8-strings.asm(22):
STRLEN: Invalid UTF-8 byte 0xA4
ERROR: invalid-utf-8-strings.asm(22):
STRLEN: Invalid UTF-8 byte 0xF0
ERROR: invalid-utf-8-strings.asm(22):
STRLEN: Invalid UTF-8 byte 0xA2
ERROR: invalid-utf-8-strings.asm(22):
STRSUB: Invalid UTF-8 byte 0xA3
ERROR: invalid-utf-8-strings.asm(22):
STRSUB: Invalid UTF-8 byte 0xA4
error: Assembly aborted (22 errors)!

View File

@@ -0,0 +1,2 @@
"aäb漢,a<><61>b<EFBFBD><62><EFBFBD>!" == "aäb漢,a<><61>b<EFBFBD><62><EFBFBD>!" (12)
"b,a"

View File

@@ -1,5 +1,5 @@
; This test tries to pass invalid UTF-8 through a macro argument ; This test tries to pass invalid UTF-8 through a macro argument
; to exercise the lexer's reportGarbageChar ; to exercise the lexer's unknown character reporting
m:MACRO m:MACRO
\1 \1
ENDM ENDM

View File

@@ -1,29 +1,2 @@
SECTION "A", ROM0 SECTION "A", ROM0
AData::
LOAD FRAGMENT "RAM", WRAM0 LOAD FRAGMENT "RAM", WRAM0
AMem::
db 0, 1, 2
AMemEnd::
ENDL
ADataEnd::
dw AMem
SECTION "B", ROM0
BData::
LOAD FRAGMENT "RAM", WRAM0
BMem::
db 3, 4, 5, 6, 7
BMemEnd::
ENDL
BDataEnd::
dw BMem
SECTION "C", ROM0
CData::
LOAD FRAGMENT "RAM", WRAM0
CMem::
db 8, 9
CMemEnd::
ENDL
CDataEnd::
dw CMem

View File

@@ -0,0 +1,3 @@
ERROR: load-fragment.asm(2):
`LOAD FRAGMENT` is not allowed
error: Assembly aborted (1 error)!

Binary file not shown.

View File

@@ -1,5 +1,25 @@
SECTION "Overflow", ROM0 SECTION "Overflow", ROM0
ds $6000 ds $6000
LOAD "oops",WRAM0 LOAD "oops",WRAM0
; We might get an error for "oops", but it can also make sense to no-op the directive
ds $2001 ds $2001
; We shouldn't get any more errors for "Overflow" nor "oops"
db
ENDL ENDL
SECTION "Moar overflow", ROM0
ds $6001
LOAD "hmm", WRAM0
ds $2000
; Since the `ds` overflows "Moar overflow", it could be no-op'd, making this `db` not error
db
ENDL
SECTION "Not overflowing", ROM0
ds $5FFF
LOAD "lol", WRAM0
ds $2000
db
; Since the LOAD block is overflowed, this may be no-op'd, not affecting the "parent"
ENDL
dw ; This, however...

View File

@@ -1,2 +1,11 @@
FATAL: load-overflow.asm(4): ERROR: load-overflow.asm(5):
Section 'Overflow' grew too big (max size = 0x8000 bytes, reached 0x8001). Section 'Overflow' grew too big (max size = 0x8000 bytes, reached 0x8001).
ERROR: load-overflow.asm(5):
Section 'oops' grew too big (max size = 0x2000 bytes, reached 0x2001).
ERROR: load-overflow.asm(13):
Section 'Moar overflow' grew too big (max size = 0x8000 bytes, reached 0x8001).
ERROR: load-overflow.asm(22):
Section 'lol' grew too big (max size = 0x2000 bytes, reached 0x2001).
ERROR: load-overflow.asm(25):
Section 'Not overflowing' grew too big (max size = 0x8000 bytes, reached 0x8001).
error: Assembly aborted (5 errors)!

View File

@@ -1,3 +1,5 @@
ERROR: load-rom.asm(3): ERROR: load-rom.asm(3):
`LOAD` blocks cannot create a ROM section `LOAD` blocks cannot create a ROM section
error: Assembly aborted (1 error)! ERROR: load-rom.asm(5):
Found `ENDL` outside of a `LOAD` block
error: Assembly aborted (2 errors)!

View File

@@ -0,0 +1,19 @@
MACRO printargs
REPT _NARG
PRINTLN \1
SHIFT
ENDR
ENDM
printargs mul(3.0, 4.0)
MACRO printlit
REPT _NARG
PRINTLN "\1"
SHIFT
ENDR
ENDM
printlit a(b,c\,d), ((e,f),g), ))h, i\,j,
printlit \(k, l), (m:\)n,o(p)q), (r,s)t
printlit "))u,v(", ("w,x","y,z"),

View File

View File

@@ -0,0 +1,11 @@
$C0000
a(b,c,d)
((e,f),g)
))h
i,j
(k
l)
(m:)n,o(p)q)
(r,s)t
"))u,v("
("w,x","y,z")

View File

@@ -1,3 +1,2 @@
def s equs "s" ; 128 nested {}s, recursion limit is 64
; 65 nested {}s, recursion limit is 64 {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{s}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
println "{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{s}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}"

View File

@@ -1,2 +1,2 @@
FATAL: nested-interpolation-recursion.asm(3): FATAL: nested-interpolation-recursion.asm(2):
Recursion limit (64) exceeded Recursion limit (64) exceeded

View File

@@ -3,5 +3,5 @@ warning: nested-macrodef.asm(26) -> nested-macrodef.asm::outer(22): [-Wuser]
ERROR: nested-macrodef.asm(26) -> nested-macrodef.asm::outer(24): ERROR: nested-macrodef.asm(26) -> nested-macrodef.asm::outer(24):
Unterminated macro definition Unterminated macro definition
ERROR: nested-macrodef.asm(27): ERROR: nested-macrodef.asm(27):
syntax error, unexpected identifier, expecting newline Macro "inner" not defined
error: Assembly aborted (2 errors)! error: Assembly aborted (2 errors)!

16
test/asm/opt.asm Normal file
View File

@@ -0,0 +1,16 @@
SECTION "test", ROM0
opt !h, !L ; already the default, but tests parsing "!"
pusho
opt p42, h, L, Wno-div
ds 1
ld [$ff88], a
halt
println $8000_0000 / -1
popo
ds 1
ld [$ff88], a
halt
println $8000_0000 / -1

2
test/asm/opt.err Normal file
View File

@@ -0,0 +1,2 @@
warning: opt.asm(16): [-Wdiv]
Division of -2147483648 by -1 yields -2147483648

Some files were not shown because too many files have changed in this diff Show More