Compare commits

..

150 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
ISSOtm
432c769d60 Release v0.5.0 2021-04-17 22:53:00 +02:00
Rangi
9923fa3eee Fix expansions that start from the end of another expansion (#839)
Do not free an expansion until its offset is *past* its size.
This means potentially freeing a nested stack of expansions
all at once.

Fixes #696
2021-04-17 13:14:40 -04:00
Rangi
750e93be3d Further simplify formatting code
- Remove redundant length checks before `memcpy`
- Coerce `sign` and `prefix` to boolean for `numLen`
2021-04-17 01:11:11 -04:00
Rangi
ee5da4468d Fix interpolation/STRFMT overflow issues (#838)
Widths and fractional widths greater than 255 would overflow a
uint8_t and wrap around to smaller values.

Total formatted lengths greater than the avilable buffer size
would overflow it and potentially corrupt memory.

Fixes #830
Closes #831
2021-04-17 00:52:55 -04:00
Rangi
503c3b5364 Revert "Fix interpolation/STRFMT overflow issues"
This reverts commit 992be3fd9b.
2021-04-16 22:19:37 -04:00
Rangi
992be3fd9b Fix interpolation/STRFMT overflow issues
Widths and fractional widths greater than 255 would overflow a
uint8_t and wrap around to smaller values.

Total formatted lengths greater than the avilable buffer size
would overflow it and potentially corrupt memory.

Fixes #830
Closes #831
2021-04-16 22:00:17 -04:00
Rangi
c755fa3469 readIdentifier does not process characters that get truncated
Previously a '.' could be past the truncation limit but still
cause the identifier to be marked as local, violating an
assertion in `sym_AddLocalLabel`.

Fixes #832
2021-04-16 21:15:01 -04:00
Rangi
e78a1d5bfd readInterpolation is limited by nMaxRecursionDepth
Fixes #837
2021-04-16 16:10:46 -04:00
Rangi
d2f6def2eb Remove unused function hash_ReplaceElement 2021-04-16 12:36:45 -04:00
Jakub Kądziołka
1ffd7cb5bb Make tests work on NixOS
Some distributions, such as NixOS and Guix, only have the /bin/sh and
/usr/bin/env binaries in standard locations.
2021-04-16 16:38:04 +02:00
Jakub Kądziołka
215e26b478 charmap: Store hashmap nodes in charmap stack
This helps update all the pointers during reallocation.
2021-04-16 16:00:26 +02:00
Jakub Kądziołka
8885f7bcf6 hash_AddElement: return the new node 2021-04-16 16:00:26 +02:00
Jakub Kądziołka
5334fc334e Don't report hashmap collisions
This doesn't seem to be very useful, and keeping this "feature" is
difficult.
2021-04-16 16:00:26 +02:00
Jakub Kądziołka
f97663aa37 hashmap: add hash_GetNode 2021-04-16 16:00:26 +02:00
Jakub Kądziołka
08bdbd1949 Add an .editorconfig
This automatically sets up the formatting settings in a number of
editors, regardless of the user's preferences.
2021-04-16 15:56:57 +02:00
Rangi
5c852c7651 Store the nested expansions starting from the deepest one (#829)
This shortens the lexer by 100 lines and simplifies
access to expansion contents, since it usually needs the
deepest one, not the top-level one.

Fixes #813
2021-04-16 09:54:13 -04:00
Rangi
6be3584467 LexerState's 'size' and 'offset' for mmapped files are unsigned
These were using signed 'off_t' because that is the type of
'st_size' from 'stat()', but neither one can be negative.
2021-04-16 10:23:37 +02:00
Rangi
8c90d9d2d7 Get rid of skip in struct Expansion
This was only used to skip the two macro arg characters,
but shiftChar() can skip them before the expansion.
2021-04-16 10:23:37 +02:00
Rangi
f69e666b00 expansionOfs cannot be negative
lexerState->expansionOfs is always either set to 0, or updated by
adding a positive quantity:

    if (distance > lexerState->expansions->distance) {
        lexerState->expansionOfs += distance - lexerState->expansions->distance;
        ...
    }

so it will always be positive or zero.
2021-04-16 10:23:37 +02:00
Rangi
eba06404f0 peek(0) => peek()
This does not completely refactor `peek` as #708 suggested,
to make it shift and cache a character itself. However it
does simplify the lexer code.
2021-04-16 10:23:37 +02:00
Rangi
9558ccea1b shiftChars(1) => shiftChar()
Only two sites were for distances greater than 1:
a `shiftChars(2)`, trivial to just do two `shiftChar()`s;
and `shiftChars(size)` in `reportGarbageChar`, which
can be a `for` loop, and should be fixed anyway to
"avoid having to peek further than 0".
2021-04-16 10:23:37 +02:00
Rangi
260d372acd Lex $ff00+c without needing large peek lookahead
This also allows arbitrary amounts of whitespace in `$ff00 + c`,
instead of needing to fit in the 42-byte LEXER_BUF_SIZE
2021-04-16 10:23:37 +02:00
Rangi
4e1b0ce793 Rephrase CONTRIBUTING.rst
Fix #729
2021-04-14 21:19:54 -04:00
Rangi
363ee9578c Document the release process in RELEASE.rst
Fix #718
2021-04-14 21:13:37 -04:00
Rangi
8fa5a4255e Mention alternative mnemonics in gbz80(7)
Fixes #819
2021-04-14 16:59:31 -04:00
Rangi
b3312886fb Use a lookupExpansion, but not as an X macro
Instead of defining `LOOKUP_PRE_NEST` and `LOOKUP_POST_NEST`,
pass a variable name and a block to `lookupExpansion`; it
will assign successive looked-up expansions to the variable
and use them in the block.

The technique of using `__VA_ARGS__` to allow commas within a
block passed to a macro is not original, and should be stable.
2021-04-13 17:58:46 +02:00
Rangi
7fc8a65d0a Refactor the lexer to not use the lookupExpansion X macro
This macro was only used twice, in `beginExpansion` and
`lexer_DumpStringExpansions`, with `getExpansionAtDistance`
already containing an inlined and slightly modified version
of `lookupExpansion` (retaining the `LOOKUP_PRE_NEST` and
`LOOKUP_POST_NEST` macros, but with both of them doing nothing).

Not using an X macro here makes the actual control flow in both
places more obvious, and I think the repeated code is acceptable
for the same reasons as the similar-but-distinct implementations
of `readString`, `appendStringLiteral`, `yylex_NORMAL`, and
`yylex_RAW`.
2021-04-13 17:58:46 +02:00
Rangi
c278a361da Remove the unused calchash djb2 hash function
Note that hashmap.c uses its own FNV-1a hash function
2021-04-13 17:41:12 +02:00
Rangi
a2f52867ad Rename print to printChar
This clarifies its usage, for printing a single character
in error messages.
2021-04-13 17:41:12 +02:00
Rangi
ab79e6bede Change how print(c) formats reported characters
Printable ASCII becomes single-quoted, using backslash
escapes if necessary. Unprintable characters use 0xNN
formatting, without quotes.
2021-04-13 17:41:12 +02:00
Rangi
850c78aaf4 Report garbage chars as their bytes; don't try decoding them as UTF-8
This decoding required high lookahead, and was not even
consistently useful (the `garbage_char` test case was not
valid UTF-8 and so did not benefit from `reportGarbageChar`).

This limits UTF-8 handling to the `STRLEN` and `STRSUB`
built-in functions, and to charmap conversion.
2021-04-13 17:41:12 +02:00
Rangi
c08cf783c8 Remove 'inline' from functions not in headers 2021-04-13 10:27:08 -04:00
Rangi
25a8518fbf Remove unused function removeLeadingZeros 2021-04-13 10:12:40 -04:00
Rangi
49174f4486 Define the UTC time components as EQU, not EQUS
Fixes #827
2021-04-13 10:11:21 -04:00
Rangi
81327b0d99 Merge branch 'master' of https://github.com/gbdev/rgbds 2021-04-13 09:31:35 -04:00
Rangi
c0859e64f7 fstk_FindFile checks for sprintf failure 2021-04-13 09:31:05 -04:00
Rangi
3e0b7d428f Fix an unclosed file and unfreed memory in out_BinaryFileSlice
Use 'goto cleanup' in both out_BinaryFileSlice and out_BinaryFile
2021-04-13 09:19:59 -04:00
ISSOtm
ba3428314b Add contact info for potential contributors
I'm going on a break, but I'll stay available if anyone wants to contribute.
2021-04-13 00:27:49 +02:00
Rangi
bcb78f5d18 Define __RGBDS_VERSION__ as the output of rgbasm --version (sans "rgbasm")
Fixes #824
2021-04-09 19:42:48 +02:00
ISSOtm
de7d1facf3 Add assertion that an expansion's total len doesn't overflow
Typically not needed because the recursion depth limit should prevent it,
but it might help debug weird lexer issues.
2021-04-03 18:31:30 +02:00
Rangi
310d34c655 Comment on REG_SP and REG_AF both being 3
No instruction needs to distinguish them both
2021-03-31 18:41:42 -04:00
Rangi
39c38f9838 Add a test case for unattainable SECTION UNION alignment
This test cases used to fail an assertion in `make develop`
2021-03-31 16:09:52 -04:00
Rangi
576b063519 Fix unattainable alignments to address 0
Fixes #818
2021-03-31 16:06:15 -04:00
Rangi
596e17ee61 Factor out a common strlen into beginExpansion
This avoids the possibility of `size` not matching `str`
2021-03-31 14:41:38 -04:00
Rangi
363b3d0134 Flush stdout after PRINTLN
This allows debug PRINTLN statements to run even if
subsequent rgbasm directives cause a crash.
2021-03-31 11:22:41 -04:00
Rangi
c7ed9a275e Do not expand empty strings
Fixes #813
2021-03-31 10:21:04 -04:00
Rangi
49aac2961d Warn about backwards FOR loops with -Wbackwards-for (-Wall)
Fixes #816
2021-03-31 10:00:21 -04:00
Rangi
3741bd4617 Speed up the div-mod test case
Test various explicit cases, not nested `for` loops
over thousands of cases
2021-03-31 09:37:23 -04:00
Jakub Kądziołka
937c9888a4 Robustly disallow overwriting builtin symbols (#817) 2021-03-31 09:03:57 -04:00
Rangi
61a9bfd33c Some tests use the new macro <name> syntax
This happens to make quine.asm shorter
2021-03-31 00:00:18 -04:00
Jakub Kądziołka
d08bcc455d Handle errors when opening source file
Before this commit, opening a file for which the user didn't have
permission resulted in a "Bad file descriptor" error.
2021-03-30 23:35:50 +02:00
Rangi
aaa92659ea Require a plus sign in ld hl, sp + <e8>
Fixes #810
2021-03-30 13:05:21 -04:00
Rangi
be877134e5 Remove support for ld bc/de/hl/sp for ld hl, bc/de/hl/sp
Fixes #811
2021-03-30 13:01:49 -04:00
Rangi
d05703c692 Release 0.5.0-rc2 2021-03-28 17:15:44 -04:00
Rangi
5dbfafcc55 Update man page copyrights to 2021 2021-03-28 16:37:15 -04:00
Rangi
a265b85d9d Report "1 error", not "1 errors", when assembly is aborted
This matches other such pluralized error messages
2021-03-28 15:55:32 -04:00
Rangi
28abf03c0a Output USED space, not FREE space, in the .map file
Fixes #808
2021-03-28 15:49:44 -04:00
Rangi
7e7f92f18c Assign section locations to all UNIONs/FRAGMENTs
Fixes the test case from #800

The `out.gb` output was corrected, since the two "test"
fragments have a different order in ROM than in SRAM.
It is effectively:

; ROM0[$0000], fragments ordered by size
    jr Label
    dw Label
    db 0

; SRAM[$a000], fragments ordered by .o order
    ds 1  ; db 0
    ds 2  ; jr Label
Label:    ; $a003
    ds 2  ; dw Label
2021-03-28 15:21:17 -04:00
Eldred Habert
7461170956 Add LOAD FRAGMENT pc test (#800)
Reproduces a reported problem, fix pending
2021-03-28 15:11:20 -04:00
Rangi
57d966d6e0 Merge pull request #804 from Rangi42/normal-backslash
Backslash in normal lexer mode must be a line continuation
2021-03-26 13:33:11 -04:00
Rangi
17752d7094 Backslash in normal lexer mode must be a line continuation
Macro args were already handled by `peek`, and character escapes
do not exist outside of string literals.

This only affects the error message printed when a non-whitespace
character comes after the backslash. Instead of "Illegal character
escape '%s'", it will print "Begun line continuation, but
encountered character '%s'".
2021-03-26 13:23:05 -04:00
ISSOtm
4216f0a9b0 Init top-level fstack node line number
It still gets written to the object file, generating Valgrind warnings
about using uninitialized memory. To silence those errors, and make
output more reproducible, init the line no to a dummy (0) value.
2021-03-25 11:26:58 +01:00
ISSOtm
e3fde986ad Remove hashmap collision warning
It was only used to check hashmap collision rate in the wild,
but it no longer has a purpose and produces spurious messages
2021-03-23 21:34:08 +01:00
Rangi
aa99ed056c Do not evaluate an untaken ELIF's condition
Fixes #764
2021-03-23 16:20:24 +01:00
ISSOtm
46d6652df1 Fix missing .sym/.map symbols in SECTION UNION/FRAGMENTs
Only the first "slice"'s symbols were considered, forgetting some symbols
2021-03-22 23:23:06 +01:00
ISSOtm
5406674cdd Install groff to build PDFs correctly in CI 2021-03-20 02:09:47 +01:00
ISSOtm
5d2e2e2182 Make docs update script also produce PDFs
See rgbds-www#12
2021-03-20 01:53:26 +01:00
ISSOtm
a929f36bc5 Replace UTF-8 hyphens with ASCII ones in man pages 2021-03-20 01:25:17 +01:00
ISSOtm
bdb84a901f Use sub-sections for the different symbol types
This will make them appear in the ToC, and generate HTML anchors for them
2021-03-19 11:04:22 +01:00
Rangi
093631ca0b Revise rgbasm(5) string symbol documentation
Clarify the differences between EQUS expansion and {interpolation}
2021-03-19 01:48:36 +01:00
Rangi
7e127a4e52 Don't expand string symbols in MACRO and FOR symbol names
Explicit {interpolation} can still achieve this, but
to match DEF, REDEF, and PURGE, these new directives that
define symbols do not expand string EQUS.
2021-03-19 01:48:36 +01:00
Rangi
b8093847dc New definition syntax with leading DEF keyword
This will enable fixing #457 later once the old
definition syntax is removed.
2021-03-19 01:48:36 +01:00
Rangi
8d1b56bcf5 rgblink identifies patches' PC sections after reading all sections
Fixes #794
2021-03-18 23:53:54 +01:00
ISSOtm
5fb7fcf461 Fix description of rgbgfx -h
The old description was backwards and mostly confusing.
2021-03-18 15:35:01 +01:00
Rangi
ee900ae7a3 Add a missing newline to a verbosePrint 2021-03-17 20:53:25 -04:00
ISSOtm
3ca58e13dc Fix verbose messages claiming non-existent errors
They were confusing when trying to debug other things
2021-03-14 18:52:16 +01:00
ISSOtm
aaa4e17454 Add verbose messages for early exit from -MG
Should help debugging Make invocations
2021-03-14 18:28:05 +01:00
daid
a81d383f75 Alignment mask was incorrectly checked for 1 instead of 0
This caused an `ALIGN[1]` to be ignored.
2021-03-11 13:24:59 +01:00
ISSOtm
60019cf476 Fix a bunch of Clang warnings
As reported by #789
Should avoid relying on 32-bit int (for implicit conversions)
and account for more extreme uses of RGBDS.
2021-03-10 10:56:57 +01:00
ISSOtm
5a6a44cbc1 Fix master documentation updater
Its path was not synced with a recent change
2021-03-10 01:15:51 +01:00
312 changed files with 4496 additions and 2433 deletions

9
.editorconfig Normal file
View File

@@ -0,0 +1,9 @@
[*]
root = true
indent_style = tab
indent_size = tab
tab_width = 8
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf

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

@@ -53,8 +53,8 @@ PAGES=(
WWWPATH="/docs"
mkdir -p "$1/_documentation/$2"
# `mandoc` uses a different format for referring to man pages present in the **current** directory
# We want that format for RGBDS man pages, and the other one for the t=rest;
# `mandoc` uses a different format for referring to man pages present in the **current** directory.
# We want that format for RGBDS man pages, and the other one for the rest;
# we thus need to copy all pages to a temporary directory, and process them there.
# Copy all pages to current dir
@@ -77,6 +77,7 @@ EOF
options+=,toc
fi
mandoc -Thtml -I os=Linux -O$options "${PAGES[$page]##*/}" | .github/actions/doc_postproc.awk >> "$1/_documentation/$2/$page"
groff -Tpdf -mdoc -wall "${PAGES[$page]##*/}" >"$1/_documentation/$2/$stem.pdf"
if [ $is_release -ne 0 ]; then
cat - >"$1/_documentation/$page" <<EOF
---
@@ -88,6 +89,7 @@ description: RGBDS latest stable — $descr
EOF
fi
done
cat - >"$1/_documentation/$2/index.html" <<EOF
---
layout: doc_index
@@ -97,6 +99,7 @@ description: RGBDS $2 - Online manual
---
EOF
# If making a release, add a new entry right after `master`
if [ $is_release -ne 0 ]; then
awk '{ print }
@@ -105,5 +108,6 @@ if [ $is_release -ne 0 ]; then
mv "$1/_data/doc.json"{.tmp,}
fi
# Clean up
rm "${PAGES[@]##*/}"

View File

@@ -29,9 +29,9 @@ jobs:
Write-Host "libpng SHA256 mismatch! ($hash)"
exit 1
}
$wc.DownloadFile('https://github.com/lexxmark/winflexbison/releases/download/v2.5.23/win_flex_bison-2.5.23.zip', 'winflexbison.zip')
$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
if ($hash -ne '6AA5C8EA662DA1550020A5804C28BE63FFAA53486DA9F6842E24C379EC422DFC') {
if ($hash -ne '39c6086ce211d5415500acc5ed2d8939861ca1696aee48909c7f6daf5122b505') {
Write-Host "bison SHA256 mismatch! ($hash)"
}
Expand-Archive -DestinationPath . "zlib.zip"

View File

@@ -6,6 +6,7 @@ on:
jobs:
build:
if: github.repository_owner == 'gbdev'
runs-on: ubuntu-18.04
steps:
- name: Checkout rgbds@release
@@ -18,10 +19,10 @@ jobs:
repository: ${{ github.repository_owner }}/rgbds-www
path: rgbds-www
# `-O toc` was added in 1.14.5, but the repos only have 1.14.4
- name: Build and install mandoc
- name: Build and install mandoc + install groff
run: |
sudo apt-get -qq update
sudo apt-get install -yq zlib1g-dev
sudo apt-get install -yq groff zlib1g-dev
wget 'http://mandoc.bsd.lv/snapshots/mandoc-1.14.5.tar.gz'
tar xf mandoc-1.14.5.tar.gz
cd mandoc-1.14.5

View File

@@ -88,9 +88,9 @@ jobs:
Write-Host "libpng SHA256 mismatch! ($hash)"
exit 1
}
$wc.DownloadFile('https://github.com/lexxmark/winflexbison/releases/download/v2.5.23/win_flex_bison-2.5.23.zip', 'winflexbison.zip')
$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
if ($hash -ne '6AA5C8EA662DA1550020A5804C28BE63FFAA53486DA9F6842E24C379EC422DFC') {
if ($hash -ne '39c6086ce211d5415500acc5ed2d8939861ca1696aee48909c7f6daf5122b505') {
Write-Host "bison SHA256 mismatch! ($hash)"
}
Expand-Archive -DestinationPath . "zlib.zip"

View File

@@ -17,6 +17,7 @@ on:
jobs:
build:
if: github.repository_owner == 'gbdev'
runs-on: ubuntu-18.04
steps:
- name: Checkout rgbds@master
@@ -31,10 +32,10 @@ jobs:
repository: gbdev/rgbds-www
ref: master
path: rgbds-www
- name: Build and install mandoc
- name: Build and install mandoc + install groff
run: |
sudo apt-get -qq update
sudo apt-get install -yq zlib1g-dev
sudo apt-get install -yq groff zlib1g-dev
wget 'http://mandoc.bsd.lv/snapshots/mandoc-1.14.5.tar.gz'
tar xf mandoc-1.14.5.tar.gz
cd mandoc-1.14.5
@@ -44,7 +45,7 @@ jobs:
- name: Update pages
working-directory: rgbds
run: |
./.github/actions/get-pages.sh ../rgbds-www/_documentation master
./.github/actions/get-pages.sh ../rgbds-www master
- name: Push new pages
working-directory: rgbds-www
run: |

View File

@@ -35,12 +35,13 @@ if(MSVC)
add_definitions(/D_CRT_SECURE_NO_WARNINGS)
else()
add_compile_options(-Wall -pedantic)
add_definitions(-D_POSIX_C_SOURCE=200809L -D_ISOC11_SOURCE)
if(SANITIZERS)
set(SAN_FLAGS -fsanitize=shift -fsanitize=integer-divide-by-zero
-fsanitize=unreachable -fsanitize=vla-bound
-fsanitize=signed-integer-overflow -fsanitize=bounds
-fsanitize=object-size -fsanitize=bool -fsanitize=enum
-fsanitize=alignment -fsanitize=null)
-fsanitize=alignment -fsanitize=null -fsanitize=address)
add_compile_options(${SAN_FLAGS})
link_libraries(${SAN_FLAGS})
endif()

View File

@@ -1,10 +1,9 @@
Contributing
============
RGBDS was created in the late 90's and has received contributions from several
RGBDS was created in the late '90s and has received contributions from several
developers since then. It wouldn't have been possible to get to this point
without their work and, for that reason, it is always open to the contributions
of other people.
without their work, and it is always open to the contributions of other people.
Reporting Bugs
--------------

View File

@@ -35,8 +35,9 @@ WARNFLAGS := -Wall
# Overridable CFLAGS
CFLAGS ?= -O3 -flto -DNDEBUG
# Non-overridable CFLAGS
REALCFLAGS := ${CFLAGS} ${WARNFLAGS} -std=gnu11 -D_POSIX_C_SOURCE=200809L \
-Iinclude
# _ISOC11_SOURCE is required on certain platforms to get C11 on top of the C99-based POSIX 2008
REALCFLAGS := ${CFLAGS} ${WARNFLAGS} -std=gnu11 -I include \
-D_POSIX_C_SOURCE=200809L -D_ISOC11_SOURCE
# Overridable LDFLAGS
LDFLAGS ?=
# Non-overridable LDFLAGS
@@ -60,9 +61,9 @@ rgbasm_obj := \
src/asm/lexer.o \
src/asm/macro.o \
src/asm/main.o \
src/asm/parser.o \
src/asm/opt.o \
src/asm/output.o \
src/asm/parser.o \
src/asm/rpn.o \
src/asm/section.o \
src/asm/symbol.o \
@@ -203,7 +204,7 @@ checkpatch:
# compilation and make the continous integration infrastructure return failure.
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 \
-Wformat-truncation=1 -Wformat-y2k -Wswitch-enum -Wunused \
-Wuninitialized -Wunknown-pragmas -Wstrict-overflow=4 \
@@ -215,7 +216,8 @@ develop:
-fsanitize=unreachable -fsanitize=vla-bound \
-fsanitize=signed-integer-overflow -fsanitize=bounds \
-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.
# This is not for Windows users!

View File

@@ -18,6 +18,8 @@ The documentation of this toolchain can be viewed online
`here <https://rgbds.gbdev.io/docs/>`__, it is generated from the man pages
found in this repository.
If you want to contribute or maintain RGBDS, and have questions regarding the code, its organisation, etc. you can find me `on GBDev <https://gbdev.io/chat>`__ or via mail at ``rgbds at eldred dot fr``.
1. Installing RGBDS
-------------------

65
RELEASE.rst Normal file
View File

@@ -0,0 +1,65 @@
Releasing
=========
This describes for the maintainers of RGBDS how to publish a new release on
GitHub.
1. Update, commit, and push `include/version.h <include/version.h>`__ with
values for ``PACKAGE_VERSION_MAJOR``, ``PACKAGE_VERSION_MINOR``,
``PACKAGE_VERSION_PATCH``, and ``PACKAGE_VERSION_RC``. Only define
``PACKAGE_VERSION_RC`` if you are publishing a release candidate! You can
use ``git commit -m "Release <version>"`` and ``git push origin master``.
2. Create a Git tag formatted as ``v<MAJOR>.<MINOR>.<PATCH>``, or
``v<MAJOR>.<MINOR>.<PATCH>-rc<RC>`` for a release candidate. ``MAJOR``,
``MINOR``, ``PATCH``, and ``RC`` should match their values from
`include/version.h <include/version.h>`__. You can use ``git tag <tag>``.
3. Push the tag to GitHub. You can use ``git push origin <tag>``.
GitHub Actions will run the `create-release-artifacts.yaml
<.github/workflows/create-release-artifacts.yaml>`__ workflow to detect the
tag starting with "``v[0-9]``" and automatically do the following:
1. Build 32-bit and 64-bit RGBDS binaries for Windows with ``cmake``.
2. Package the binaries into zip files.
3. Package the source code into a tar.gz file with ``make dist``.
4. Create a draft GitHub release for the tag, attaching the three
packaged files. It will be a prerelease if the tag contains "``-rc``".
If an error occurred in the above steps, delete the tag and restart the
procedure. You can use ``git push --delete origin <tag>`` and
``git tag --delete <tag>``.
4. GitHub Actions will run the `create-release-docs.yml
<.github/workflows/create-release-docs.yml>`__ workflow to add the release
documentation to `rgbds-www <https://github.com/gbdev/rgbds-www>`__.
For a release candidate, which creates a prerelease, you will have to
take these steps yourself.
1. Clone `rgbds-www <https://github.com/gbdev/rgbds-www>`__. You can use
``git clone https://github.com/gbdev/rgbds-www.git``.
2. Make sure that you have installed ``groff`` and ``mandoc``. You will
need ``mandoc`` 1.14.5 or later to support ``-O toc``.
3. Run ``.github/actions/get-pages.sh -r <path/to/rgbds-www> <tag>``. This
will render the RGBDS documentation as HTML and PDF and copy it to
``rgbds-www``.
If you do not have ``groff`` installed, you can change
``groff -Tpdf -mdoc -wall`` to ``mandoc -Tpdf -I os=Linux`` in
`.github/actions/get-pages.sh <.github/actions/get-pages.sh>`__ and it
will suffice.
4. Commit and push the documentation. You can use ``git commit -m
"Create RGBDS <tag> documentation"`` and ``git push origin master``
(within the ``rgbds-www`` directory, not RGBDS).
5. Write a changelog in the GitHub draft release.
6. Click the "Publish release" button to publish it!

View File

@@ -18,5 +18,6 @@ void charmap_Push(void);
void charmap_Pop(void);
void charmap_Add(char *mapping, uint8_t value);
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 */

View File

@@ -9,8 +9,8 @@
#ifndef RGBDS_FORMAT_SPEC_H
#define RGBDS_FORMAT_SPEC_H
#include <stdint.h>
#include <stdbool.h>
#include <stdint.h>
enum FormatState {
FORMAT_SIGN, // expects '+' or ' ' (optional)
@@ -28,9 +28,9 @@ struct FormatSpec {
bool prefix;
bool alignLeft;
bool padZero;
uint8_t width;
size_t width;
bool hasFrac;
uint8_t fracWidth;
size_t fracWidth;
int type;
bool valid;
};

View File

@@ -49,7 +49,7 @@ struct FileStackNamedNode { /* NODE_FILE, NODE_MACRO */
char name[]; /* File name for files, file::macro name for macros */
};
extern size_t nMaxRecursionDepth;
extern size_t maxRecursionDepth;
struct MacroArgs;
@@ -72,12 +72,12 @@ bool fstk_FindFile(char const *path, char **fullPath, size_t *size);
bool yywrap(void);
void fstk_RunInclude(char const *path);
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,
int32_t reptLineNo, char *body, size_t size);
void fstk_StopRept(void);
bool fstk_Break(void);
void fstk_Init(char const *mainPath, size_t maxRecursionDepth);
void fstk_Init(char const *mainPath, size_t maxDepth);
#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..!
*/
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_DeleteState(struct LexerState *state);
void lexer_Init(void);
@@ -88,8 +88,8 @@ uint32_t lexer_GetLineNo(void);
uint32_t lexer_GetColNo(void);
void lexer_DumpStringExpansions(void);
int yylex(void);
void lexer_CaptureRept(struct CaptureBody *capture);
void lexer_CaptureMacroBody(struct CaptureBody *capture);
bool lexer_CaptureRept(struct CaptureBody *capture);
bool lexer_CaptureMacroBody(struct CaptureBody *capture);
#define INITIAL_DS_ARG_SIZE 2
struct DsArgList {

View File

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

View File

@@ -9,14 +9,16 @@
#ifndef RGBDS_OPT_H
#define RGBDS_OPT_H
#include <stdbool.h>
void opt_B(char chars[2]);
void opt_G(char chars[4]);
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_Push(void);
void opt_Pop(void);
#endif

View File

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

View File

@@ -17,20 +17,20 @@
#define MAXRPNLEN 1048576
struct Expression {
int32_t nVal; // If the expression's value is known, it's here
char *reason; // Why the expression is not known, if it isn't
bool isKnown; // Whether the expression's value is known
bool isSymbol; // Whether the expression represents a symbol
uint8_t *tRPN; // Array of bytes serializing the RPN expression
uint32_t nRPNCapacity; // Size of the `tRPN` buffer
uint32_t nRPNLength; // Used size of the `tRPN` buffer
uint32_t nRPNPatchSize; // Size the expression will take in the obj file
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
bool isKnown; // Whether the expression's value is known
bool isSymbol; // Whether the expression represents a symbol
uint8_t *rpn; // Array of bytes serializing the RPN expression
uint32_t rpnCapacity; // Size of the `rpn` buffer
uint32_t rpnLength; // Used size of the `rpn` buffer
uint32_t rpnPatchSize; // Size the expression will take in the obj file
};
/*
* 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;
}
@@ -43,11 +43,11 @@ static inline bool rpn_isSymbol(const struct Expression *expr)
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_LOGNOT(struct Expression *expr, const struct Expression *src);
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,
const struct Expression *src1,
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_UNNEG(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_BankSection(struct Expression *expr, char const *tzSectionName);
void rpn_BankSymbol(struct Expression *expr, char const *symName);
void rpn_BankSection(struct Expression *expr, char const *sectionName);
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_CheckHRAM(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 */

View File

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

View File

@@ -45,13 +45,13 @@ struct Symbol {
/* If sym_IsNumeric */
int32_t value;
int32_t (*numCallback)(void);
/* For SYM_MACRO */
/* For SYM_MACRO and SYM_EQUS; TODO: have separate fields */
struct {
size_t macroSize;
char *macro;
};
/* For SYM_EQUS, TODO: separate "base" fields from SYM_MACRO */
char const *(*strCallback)(void); /* For SYM_EQUS */
/* For SYM_EQUS */
char const *(*strCallback)(void);
};
uint32_t ID; /* ID of the symbol in the object file (-1 if none) */
@@ -75,15 +75,14 @@ static inline bool sym_IsConstant(struct Symbol const *sym)
if (sym->type == SYM_LABEL) {
struct Section const *sect = sym_GetSection(sym);
return sect && sect->org != -1;
return sect && sect->org != (uint32_t)-1;
}
return sym->type == SYM_EQU || sym->type == SYM_SET;
}
static inline bool sym_IsNumeric(struct Symbol const *sym)
{
return sym->type == SYM_LABEL || sym->type == SYM_EQU
|| sym->type == SYM_SET;
return sym->type == SYM_LABEL || sym->type == SYM_EQU || sym->type == SYM_SET;
}
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_Export(char const *symName);
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);
uint32_t sym_GetPCValue(void);
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
*/
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
*/
struct Symbol *sym_FindUnscopedSymbol(char const *name);
struct Symbol *sym_FindUnscopedSymbol(char const *symName);
/*
* 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 *sym_AddMacro(char const *symName, int32_t defLineNo, char *body, size_t size);
struct Symbol *sym_Ref(char const *symName);

View File

@@ -11,8 +11,8 @@
#include <stdint.h>
uint32_t calchash(const char *s);
char const *print(int c);
char const *printChar(int c);
/*
* @return The number of bytes read, or 0 if invalid data was found
*/

View File

@@ -13,8 +13,16 @@
extern unsigned int nbErrors;
enum WarningState {
WARNING_DEFAULT,
WARNING_DISABLED,
WARNING_ENABLED,
WARNING_ERROR
};
enum WarningID {
WARNING_ASSERT, /* Assertions */
WARNING_BACKWARDS_FOR, /* `for` loop with backwards range */
WARNING_BUILTIN_ARG, /* Invalid args to builtins */
WARNING_CHARMAP_REDEF, /* Charmap entry re-definition */
WARNING_DIV, /* Division undefined behavior */
@@ -42,6 +50,9 @@ enum WarningID {
#define NB_META_WARNINGS (NB_WARNINGS_ALL - NB_WARNINGS)
};
extern enum WarningState warningStates[NB_WARNINGS];
extern bool warningsAreErrors;
void processWarningFlag(char const *flag);
/*

View File

@@ -29,19 +29,9 @@ typedef struct HashMapEntry *HashMap[HASHMAP_NB_BUCKETS];
* @param map The HashMap to add the element to
* @param key The key with which the element will be stored and retrieved
* @param element The element to add
* @return True if a collision occurred (for statistics)
* @return A pointer to the pointer to the element.
*/
bool hash_AddElement(HashMap map, char const *key, void *element);
/**
* Replaces an element with an already-present key in a hashmap.
* @warning Inserting a NULL will make `hash_GetElement`'s return ambiguous!
* @param map The HashMap to replace the element in
* @param key The key with which the element will be stored and retrieved
* @param element The element to replace
* @return True if the element was found and replaced
*/
bool hash_ReplaceElement(HashMap const map, char const *key, void *element);
void **hash_AddElement(HashMap map, char const *key, void *element);
/**
* Removes an element from a hashmap.
@@ -51,6 +41,14 @@ bool hash_ReplaceElement(HashMap const map, char const *key, void *element);
*/
bool hash_RemoveElement(HashMap map, char const *key);
/**
* Finds an element in a hashmap, and returns a pointer to its value field.
* @param map The map to consider the elements of
* @param key The key to search an element for
* @return A pointer to the pointer to the element, or NULL if not found.
*/
void **hash_GetNode(HashMap const map, char const *key);
/**
* Finds an element in a hashmap.
* @param map The map to consider the elements of

View File

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

View File

@@ -21,10 +21,7 @@ struct Assertion {
struct Patch patch;
// enum AssertionType type; The `patch`'s field is instead re-used
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 Assertion *next;

View File

@@ -30,11 +30,11 @@ struct AttachedSymbol {
struct Patch {
struct FileStackNode const *src;
uint32_t lineNo;
int32_t offset;
uint32_t offset;
uint32_t pcSectionID;
uint32_t pcOffset;
enum PatchType type;
int32_t rpnSize;
uint32_t rpnSize;
uint8_t *rpnExpression;
struct Section const *pcSection;

View File

@@ -14,7 +14,7 @@
#define RGBDS_OBJECT_VERSION_STRING "RGB%1u"
#define RGBDS_OBJECT_VERSION_NUMBER 9U
#define RGBDS_OBJECT_REV 7U
#define RGBDS_OBJECT_REV 8U
enum AssertionType {
ASSERT_WARN,
@@ -23,42 +23,44 @@ enum AssertionType {
};
enum RPNCommand {
RPN_ADD = 0x00,
RPN_SUB = 0x01,
RPN_MUL = 0x02,
RPN_DIV = 0x03,
RPN_MOD = 0x04,
RPN_UNSUB = 0x05,
RPN_EXP = 0x06,
RPN_ADD = 0x00,
RPN_SUB = 0x01,
RPN_MUL = 0x02,
RPN_DIV = 0x03,
RPN_MOD = 0x04,
RPN_UNSUB = 0x05, // FIXME: should be renamed to "NEG" for consistency
RPN_EXP = 0x06,
RPN_OR = 0x10,
RPN_AND = 0x11,
RPN_XOR = 0x12,
RPN_UNNOT = 0x13,
RPN_OR = 0x10,
RPN_AND = 0x11,
RPN_XOR = 0x12,
RPN_UNNOT = 0x13, // FIXME: should be renamed to "NOT" for consistency
RPN_LOGAND = 0x21,
RPN_LOGOR = 0x22,
RPN_LOGUNNOT = 0x23,
RPN_LOGAND = 0x21,
RPN_LOGOR = 0x22,
RPN_LOGUNNOT = 0x23, // FIXME: should be renamed to "LOGNOT" for consistency
RPN_LOGEQ = 0x30,
RPN_LOGNE = 0x31,
RPN_LOGGT = 0x32,
RPN_LOGLT = 0x33,
RPN_LOGGE = 0x34,
RPN_LOGLE = 0x35,
RPN_LOGEQ = 0x30,
RPN_LOGNE = 0x31,
RPN_LOGGT = 0x32,
RPN_LOGLT = 0x33,
RPN_LOGGE = 0x34,
RPN_LOGLE = 0x35,
RPN_SHL = 0x40,
RPN_SHR = 0x41,
RPN_SHL = 0x40,
RPN_SHR = 0x41,
RPN_BANK_SYM = 0x50,
RPN_BANK_SECT = 0x51,
RPN_BANK_SELF = 0x52,
RPN_BANK_SYM = 0x50,
RPN_BANK_SECT = 0x51,
RPN_BANK_SELF = 0x52,
RPN_SIZEOF_SECT = 0x53,
RPN_STARTOF_SECT = 0x54,
RPN_HRAM = 0x60,
RPN_RST = 0x61,
RPN_HRAM = 0x60,
RPN_RST = 0x61,
RPN_CONST = 0x80,
RPN_SYM = 0x81
RPN_CONST = 0x80,
RPN_SYM = 0x81
};
enum SectionType {

View File

@@ -11,9 +11,10 @@
#ifndef 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
# include <string.h>
# define strcasecmp _stricmp
# define strncasecmp _strnicmp
#else
# include <strings.h>

View File

@@ -1,7 +1,7 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2017-2018, Antonio Nino Diaz and RGBDS contributors.
* Copyright (c) 2017-2021, Antonio Nino Diaz and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
@@ -11,8 +11,7 @@
#define PACKAGE_VERSION_MAJOR 0
#define PACKAGE_VERSION_MINOR 5
#define PACKAGE_VERSION_PATCH 0
#define PACKAGE_VERSION_RC 1
#define PACKAGE_VERSION_PATCH 1
const char *get_package_version_string(void);

View File

@@ -44,32 +44,35 @@ struct Charmap {
static HashMap charmaps;
static struct Charmap *currentCharmap;
/*
* Store pointers to hashmap nodes, so that there is only one pointer to the memory block
* that gets reallocated.
*/
static struct Charmap **currentCharmap;
struct CharmapStackEntry {
struct Charmap *charmap;
struct Charmap **charmap;
struct CharmapStackEntry *next;
};
struct CharmapStackEntry *charmapStack;
static inline struct Charmap *charmap_Get(const char *name)
static struct Charmap *charmap_Get(char const *name)
{
return hash_GetElement(charmaps, name);
}
static inline struct Charmap *resizeCharmap(struct Charmap *map, size_t capacity)
static void resizeCharmap(struct Charmap **map, size_t capacity)
{
struct Charmap *new = realloc(map, sizeof(*map) + sizeof(*map->nodes) * capacity);
*map = realloc(*map, sizeof(**map) + sizeof(*(*map)->nodes) * capacity);
if (!new)
if (!*map)
fatalerror("Failed to %s charmap: %s\n",
map ? "create" : "resize", strerror(errno));
new->capacity = capacity;
return new;
*map ? "create" : "resize", strerror(errno));
(**map).capacity = capacity;
}
static inline void initNode(struct Charnode *node)
static void initNode(struct Charnode *node)
{
node->isTerminal = false;
memset(node->next, 0, sizeof(node->next));
@@ -95,19 +98,18 @@ struct Charmap *charmap_New(const char *name, const char *baseName)
/* Init the new charmap's fields */
if (base) {
charmap = resizeCharmap(NULL, base->capacity);
resizeCharmap(&charmap, base->capacity);
charmap->usedNodes = base->usedNodes;
memcpy(charmap->nodes, base->nodes, sizeof(base->nodes[0]) * charmap->usedNodes);
} else {
charmap = resizeCharmap(NULL, INITIAL_CAPACITY);
resizeCharmap(&charmap, INITIAL_CAPACITY);
charmap->usedNodes = 1;
initNode(&charmap->nodes[0]); /* Init the root node */
}
charmap->name = strdup(name);
hash_AddElement(charmaps, charmap->name, charmap);
currentCharmap = charmap;
currentCharmap = (struct Charmap **)hash_AddElement(charmaps, charmap->name, charmap);
return charmap;
}
@@ -120,7 +122,7 @@ void charmap_Delete(struct Charmap *charmap)
void charmap_Set(const char *name)
{
struct Charmap *charmap = charmap_Get(name);
struct Charmap **charmap = (struct Charmap **)hash_GetNode(charmaps, name);
if (charmap == NULL)
error("Charmap '%s' doesn't exist\n", name);
@@ -158,25 +160,26 @@ void charmap_Pop(void)
void charmap_Add(char *mapping, uint8_t value)
{
struct Charnode *node = &currentCharmap->nodes[0];
struct Charmap *charmap = *currentCharmap;
struct Charnode *node = &charmap->nodes[0];
for (uint8_t c; *mapping; mapping++) {
c = *mapping - 1;
if (node->next[c]) {
node = &currentCharmap->nodes[node->next[c]];
node = &charmap->nodes[node->next[c]];
} else {
/* Register next available node */
node->next[c] = currentCharmap->usedNodes;
node->next[c] = charmap->usedNodes;
/* If no more nodes are available, get new ones */
if (currentCharmap->usedNodes == currentCharmap->capacity) {
currentCharmap->capacity *= 2;
currentCharmap = resizeCharmap(currentCharmap, currentCharmap->capacity);
hash_ReplaceElement(charmaps, currentCharmap->name, currentCharmap);
if (charmap->usedNodes == charmap->capacity) {
charmap->capacity *= 2;
resizeCharmap(currentCharmap, charmap->capacity);
charmap = *currentCharmap;
}
/* Switch to and init new node */
node = &currentCharmap->nodes[currentCharmap->usedNodes++];
node = &charmap->nodes[charmap->usedNodes++];
initNode(node);
}
}
@@ -189,6 +192,16 @@ void charmap_Add(char *mapping, uint8_t value)
}
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.
@@ -196,51 +209,55 @@ 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 no match, read a UTF-8 codepoint and output that.
*/
size_t outputLen = 0;
struct Charnode const *node = &currentCharmap->nodes[0];
struct Charmap const *charmap = *currentCharmap;
struct Charnode const *node = &charmap->nodes[0];
struct Charnode const *match = NULL;
size_t rewindDistance = 0;
for (;;) {
/* 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]) {
input++; /* Consume that char */
if (**input && node->next[c]) {
(*input)++; /* Consume that char */
rewindDistance++;
node = &currentCharmap->nodes[node->next[c]];
node = &charmap->nodes[node->next[c]];
if (node->isTerminal) {
match = node;
rewindDistance = 0; /* Rewind from after the match */
}
} else {
input -= rewindDistance; /* Rewind */
*input -= rewindDistance; /* Rewind */
rewindDistance = 0;
node = &currentCharmap->nodes[0];
node = &charmap->nodes[0];
if (match) { /* Arrived at a dead end with a match found */
*output++ = match->value;
outputLen++;
match = NULL; /* Reset match for next round */
if (output)
*(*output)++ = match->value;
} else if (*input) { /* No match found */
size_t codepointLen = readUTF8Char(output, input);
return 1;
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");
break;
}
input += codepointLen; /* OK because UTF-8 has no NUL in multi-byte chars */
output += codepointLen;
outputLen += codepointLen;
}
if (!*input)
break;
/* OK because UTF-8 has no NUL in multi-byte chars */
*input += codepointLen;
if (output)
*output += codepointLen;
return codepointLen;
} else { /* End of input */
return 0;
}
}
}
return outputLen;
unreachable_();
}

View File

@@ -6,6 +6,7 @@
* SPDX-License-Identifier: MIT
*/
#include <assert.h>
#include <inttypes.h>
#include <math.h>
#include <stdbool.h>
@@ -148,20 +149,24 @@ void fmt_PrintString(char *buf, size_t bufLen, struct FormatSpec const *fmt, cha
size_t len = strlen(value);
size_t totalLen = fmt->width > len ? fmt->width : len;
if (totalLen + 1 > bufLen) /* bufLen includes terminator */
if (totalLen > bufLen - 1) { /* bufLen includes terminator */
error("Formatted string value too long\n");
totalLen = bufLen - 1;
if (len > totalLen)
len = totalLen;
}
assert(len < bufLen && totalLen < bufLen && len <= totalLen);
size_t padLen = fmt->width > len ? fmt->width - len : 0;
size_t padLen = totalLen - len;
if (fmt->alignLeft) {
strncpy(buf, value, len < bufLen ? len : bufLen);
for (size_t i = 0; i < totalLen && len + i < bufLen; i++)
buf[len + i] = ' ';
} else {
for (size_t i = 0; i < padLen && i < bufLen; i++)
memcpy(buf, value, len);
for (size_t i = len; i < totalLen; i++)
buf[i] = ' ';
if (bufLen > padLen)
strncpy(buf + padLen, value, bufLen - padLen - 1);
} else {
for (size_t i = 0; i < padLen; i++)
buf[i] = ' ';
memcpy(buf + padLen, value, len);
}
buf[totalLen] = '\0';
@@ -221,12 +226,18 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
/* Special case for fixed-point */
/* Default fractional width (C's is 6 for "%f"; here 5 is enough) */
uint8_t fracWidth = fmt->hasFrac ? fmt->fracWidth : 5;
size_t fracWidth = fmt->hasFrac ? fmt->fracWidth : 5;
if (fracWidth) {
if (fracWidth > 255) {
error("Fractional width %zu too long, limiting to 255\n",
fracWidth);
fracWidth = 255;
}
char spec[16]; /* Max "%" + 5-char PRIu32 + ".%0255.f" + terminator */
snprintf(spec, sizeof(spec), "%%" PRIu32 ".%%0%d.f", fracWidth);
snprintf(spec, sizeof(spec), "%%" PRIu32 ".%%0%zu.f", fracWidth);
snprintf(valueBuf, sizeof(valueBuf), spec, value >> 16,
(value % 65536) / 65536.0 * pow(10, fracWidth) + 0.5);
} else {
@@ -244,55 +255,49 @@ void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uin
}
size_t len = strlen(valueBuf);
size_t numLen = len;
if (sign)
numLen++;
if (prefix)
numLen++;
size_t numLen = !!sign + !!prefix + len;
size_t totalLen = fmt->width > numLen ? fmt->width : numLen;
if (totalLen + 1 > bufLen) /* bufLen includes terminator */
if (totalLen > bufLen - 1) { /* bufLen includes terminator */
error("Formatted numeric value too long\n");
totalLen = bufLen - 1;
if (numLen > totalLen) {
len -= numLen - totalLen;
numLen = totalLen;
}
}
assert(numLen < bufLen && totalLen < bufLen && numLen <= totalLen && len <= numLen);
size_t padLen = fmt->width > numLen ? fmt->width - numLen : 0;
size_t padLen = totalLen - numLen;
size_t pos = 0;
if (fmt->alignLeft) {
size_t pos = 0;
if (sign && pos < bufLen)
if (sign)
buf[pos++] = sign;
if (prefix && pos < bufLen)
if (prefix)
buf[pos++] = prefix;
strcpy(buf + pos, valueBuf);
pos += len;
for (size_t i = 0; i < totalLen && pos + i < bufLen; i++)
buf[pos + i] = ' ';
memcpy(buf + pos, valueBuf, len);
for (size_t i = pos + len; i < totalLen; i++)
buf[i] = ' ';
} else {
size_t pos = 0;
if (fmt->padZero) {
/* sign, then prefix, then zero padding */
if (sign && pos < bufLen)
if (sign)
buf[pos++] = sign;
if (prefix && pos < bufLen)
if (prefix)
buf[pos++] = prefix;
for (size_t i = 0; i < padLen && pos < bufLen; i++)
for (size_t i = 0; i < padLen; i++)
buf[pos++] = '0';
} else {
/* space padding, then sign, then prefix */
for (size_t i = 0; i < padLen && pos < bufLen; i++)
for (size_t i = 0; i < padLen; i++)
buf[pos++] = ' ';
if (sign && pos < bufLen)
if (sign)
buf[pos++] = sign;
if (prefix && pos < bufLen)
if (prefix)
buf[pos++] = prefix;
}
if (bufLen > pos)
strcpy(buf + pos, valueBuf);
memcpy(buf + pos, valueBuf, len);
}
buf[totalLen] = '\0';

View File

@@ -44,7 +44,7 @@ struct Context {
static struct Context *contextStack;
static size_t contextDepth = 0;
#define DEFAULT_MAX_DEPTH 64
size_t nMaxRecursionDepth;
size_t maxRecursionDepth;
static unsigned int nbIncPaths = 0;
static char const *includePaths[MAXINCPATHS];
@@ -143,8 +143,8 @@ void fstk_AddIncludePath(char const *path)
static void printDep(char const *path)
{
if (dependfile) {
fprintf(dependfile, "%s: %s\n", tzTargetFileName, path);
if (oGeneratePhonyDeps)
fprintf(dependfile, "%s: %s\n", targetFileName, path);
if (generatePhonyDeps)
fprintf(dependfile, "%s:\n", path);
}
}
@@ -175,8 +175,14 @@ bool fstk_FindFile(char const *path, char **fullPath, size_t *size)
char const *incPath = i ? includePaths[i - 1] : "";
int len = snprintf(*fullPath, *size, "%s%s", incPath, path);
if (len < 0) {
error("snprintf error during include path search: %s\n",
strerror(errno));
break;
}
/* Oh how I wish `asnprintf` was standard... */
if (len >= *size) { /* `len` doesn't include the terminator, `size` does */
if ((size_t)len >= *size) { /* `len` doesn't include the terminator, `size` does */
*size = len + 1;
*fullPath = realloc(*fullPath, *size);
if (!*fullPath) {
@@ -185,12 +191,14 @@ bool fstk_FindFile(char const *path, char **fullPath, size_t *size)
break;
}
len = sprintf(*fullPath, "%s%s", incPath, path);
if (len < 0) {
error("sprintf error during include path search: %s\n",
strerror(errno));
break;
}
}
if (len < 0) {
error("snprintf error during include path search: %s\n",
strerror(errno));
} else if (isPathValid(*fullPath)) {
if (isPathValid(*fullPath)) {
printDep(*fullPath);
return true;
}
@@ -198,18 +206,18 @@ bool fstk_FindFile(char const *path, char **fullPath, size_t *size)
}
errno = ENOENT;
if (oGeneratedMissingIncludes)
if (generatedMissingIncludes)
printDep(path);
return false;
}
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",
nIFDepth, nIFDepth == 1 ? "" : "s");
ifDepth, ifDepth == 1 ? "" : "s");
if (contextStack->fileInfo->type == NODE_REPT) { /* The context is a REPT block, which may loop */
struct FileStackReptNode *fileInfo = (struct FileStackReptNode *)contextStack->fileInfo;
@@ -285,8 +293,8 @@ bool yywrap(void)
*/
static void newContext(struct FileStackNode *fileInfo)
{
if (++contextDepth >= nMaxRecursionDepth)
fatalerror("Recursion limit (%zu) exceeded\n", nMaxRecursionDepth);
if (++contextDepth >= maxRecursionDepth)
fatalerror("Recursion limit (%zu) exceeded\n", maxRecursionDepth);
struct Context *context = malloc(sizeof(*context));
if (!context)
@@ -314,10 +322,14 @@ void fstk_RunInclude(char const *path)
if (!fstk_FindFile(path, &fullPath, &size)) {
free(fullPath);
if (oGeneratedMissingIncludes)
oFailedOnMissingInclude = true;
else
if (generatedMissingIncludes) {
if (verbose)
printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n",
path, strerror(errno));
failedOnMissingInclude = true;
} else {
error("Unable to open included file '%s': %s\n", path, strerror(errno));
}
return;
}
dbgPrint("Full path: \"%s\"\n", fullPath);
@@ -405,7 +417,7 @@ void fstk_RunMacro(char const *macroName, struct MacroArgs *args)
memcpy(dest, macro->name, macroNameLen + 1);
newContext((struct FileStackNode *)fileInfo);
contextStack->lexerState = lexer_OpenFileView(macro->macro, macro->macroSize,
contextStack->lexerState = lexer_OpenFileView("MACRO", macro->macro, macro->macroSize,
macro->fileLine);
if (!contextStack->lexerState)
fatalerror("Failed to set up lexer for macro invocation\n");
@@ -439,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 */
contextStack->fileInfo->lineNo = reptLineNo;
contextStack->lexerState = lexer_OpenFileView(body, size, reptLineNo);
contextStack->lexerState = lexer_OpenFileView("REPT", body, size, reptLineNo);
if (!contextStack->lexerState)
fatalerror("Failed to set up lexer for REPT block\n");
lexer_SetStateAtEOL(contextStack->lexerState);
@@ -480,6 +492,10 @@ void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step,
else if (step == 0)
error("FOR cannot have a step value of 0\n");
if ((step > 0 && start > stop) || (step < 0 && start < stop))
warning(WARNING_BACKWARDS_FOR, "FOR goes backwards from %d to %d by %d\n",
start, stop, step);
if (count == 0)
return;
if (!newReptContext(reptLineNo, body, size))
@@ -512,7 +528,7 @@ bool fstk_Break(void)
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);
@@ -532,6 +548,7 @@ void fstk_Init(char const *mainPath, size_t maxRecursionDepth)
context->fileInfo = (struct FileStackNode *)fileInfo;
/* lineNo and reptIter are unused on the top-level context */
context->fileInfo->parent = NULL;
context->fileInfo->lineNo = 0; // This still gets written to the object file, so init it
context->fileInfo->referenced = false;
context->fileInfo->type = NODE_FILE;
memcpy(fileInfo->name, fileName, len + 1);
@@ -553,12 +570,12 @@ void fstk_Init(char const *mainPath, size_t maxRecursionDepth)
* This assumes that the rept node is larger
*/
#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 "
EXPAND_AND_STR(DEFAULT_MAX_DEPTH) "\n", DEPTH_LIMIT);
nMaxRecursionDepth = DEFAULT_MAX_DEPTH;
maxRecursionDepth = DEFAULT_MAX_DEPTH;
} else {
nMaxRecursionDepth = maxRecursionDepth;
maxRecursionDepth = maxDepth;
}
/* Make sure that the default of 64 is OK, though */
assert(DEPTH_LIMIT >= DEFAULT_MAX_DEPTH);

File diff suppressed because it is too large Load Diff

View File

@@ -170,12 +170,12 @@ void macro_ShiftCurrentArgs(int32_t count)
{
if (!macroArgs) {
error("Cannot shift macro arguments outside of a macro\n");
} else if (count > 0 && (count > macroArgs->nbArgs
} else if (count > 0 && ((uint32_t)count > macroArgs->nbArgs
|| macroArgs->shift > macroArgs->nbArgs - count)) {
warning(WARNING_MACRO_SHIFT,
"Cannot shift macro arguments past their end\n");
macroArgs->shift = macroArgs->nbArgs;
} else if (count < 0 && macroArgs->shift < -count) {
} else if (count < 0 && macroArgs->shift < (uint32_t)-count) {
warning(WARNING_MACRO_SHIFT,
"Cannot shift macro arguments past their beginning\n");
macroArgs->shift = 0;

View File

@@ -36,6 +36,18 @@
#include "helpers.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
// Unfortunately, macOS still ships 2.3, which is from 2008...
int yyparse(void);
@@ -45,13 +57,13 @@ extern int yydebug;
#endif
FILE * dependfile;
bool oGeneratedMissingIncludes;
bool oFailedOnMissingInclude;
bool oGeneratePhonyDeps;
char *tzTargetFileName;
bool generatedMissingIncludes;
bool failedOnMissingInclude;
bool generatePhonyDeps;
char *targetFileName;
bool haltnop;
bool optimizeloads;
bool optimizeLoads;
bool verbose;
bool warnings; /* True to enable warnings, false to disable them. */
@@ -158,21 +170,21 @@ int main(int argc, char *argv[])
// Set defaults
oGeneratePhonyDeps = false;
oGeneratedMissingIncludes = false;
oFailedOnMissingInclude = false;
tzTargetFileName = NULL;
generatePhonyDeps = false;
generatedMissingIncludes = false;
failedOnMissingInclude = false;
targetFileName = NULL;
opt_B("01");
opt_G("0123");
opt_P(0);
optimizeloads = true;
optimizeLoads = true;
haltnop = true;
verbose = false;
warnings = true;
sym_SetExportAll(false);
uint32_t maxRecursionDepth = 64;
size_t nTargetFileNameLen = 0;
uint32_t maxDepth = 64;
size_t targetFileNameLen = 0;
while ((ch = musl_getopt_long_only(argc, argv, optstring, longopts, NULL)) != -1) {
switch (ch) {
@@ -214,7 +226,7 @@ int main(int argc, char *argv[])
break;
case 'L':
optimizeloads = false;
optimizeLoads = false;
break;
case 'M':
@@ -244,7 +256,7 @@ int main(int argc, char *argv[])
break;
case 'r':
maxRecursionDepth = strtoul(musl_optarg, &ep, 0);
maxDepth = strtoul(musl_optarg, &ep, 0);
if (musl_optarg[0] == '\0' || *ep != '\0')
errx(1, "Invalid argument for option 'r'");
@@ -269,11 +281,11 @@ int main(int argc, char *argv[])
case 0:
switch (depType) {
case 'G':
oGeneratedMissingIncludes = true;
generatedMissingIncludes = true;
break;
case 'P':
oGeneratePhonyDeps = true;
generatePhonyDeps = true;
break;
case 'Q':
@@ -284,22 +296,22 @@ int main(int argc, char *argv[])
if (depType == 'Q')
ep = make_escape(ep);
nTargetFileNameLen += strlen(ep) + 1;
if (!tzTargetFileName) {
targetFileNameLen += strlen(ep) + 1;
if (!targetFileName) {
/* On first alloc, make an empty str */
tzTargetFileName = malloc(nTargetFileNameLen + 1);
if (tzTargetFileName)
*tzTargetFileName = '\0';
targetFileName = malloc(targetFileNameLen + 1);
if (targetFileName)
*targetFileName = '\0';
} else {
tzTargetFileName = realloc(tzTargetFileName,
nTargetFileNameLen + 1);
targetFileName = realloc(targetFileName,
targetFileNameLen + 1);
}
if (tzTargetFileName == NULL)
if (targetFileName == NULL)
err(1, "Cannot append new file to target file list");
strcat(tzTargetFileName, ep);
strcat(targetFileName, ep);
if (depType == 'Q')
free(ep);
char *ptr = tzTargetFileName + strlen(tzTargetFileName);
char *ptr = targetFileName + strlen(targetFileName);
*ptr++ = ' ';
*ptr = '\0';
@@ -314,8 +326,8 @@ int main(int argc, char *argv[])
}
}
if (tzTargetFileName == NULL)
tzTargetFileName = tzObjectname;
if (targetFileName == NULL)
targetFileName = objectName;
if (argc == musl_optind) {
fputs("FATAL: No input files\n", stderr);
@@ -331,17 +343,17 @@ int main(int argc, char *argv[])
printf("Assembling %s\n", mainFileName);
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");
fprintf(dependfile, "%s: %s\n", tzTargetFileName, mainFileName);
fprintf(dependfile, "%s: %s\n", targetFileName, mainFileName);
}
charmap_New("main", NULL);
// Init lexer and file stack, prodiving file info
lexer_Init();
fstk_Init(mainFileName, maxRecursionDepth);
fstk_Init(mainFileName, maxDepth);
// Perform parse (yyparse is auto-generated from `parser.y`)
if (yyparse() != 0 && nbErrors == 0)
@@ -353,14 +365,15 @@ int main(int argc, char *argv[])
sect_CheckUnionClosed();
if (nbErrors != 0)
errx(1, "Assembly aborted (%u errors)!", nbErrors);
errx(1, "Assembly aborted (%u error%s)!", nbErrors,
nbErrors == 1 ? "" : "s");
// If parse aborted due to missing an include, and `-MG` was given, exit normally
if (oFailedOnMissingInclude)
if (failedOnMissingInclude)
return 0;
/* If no path specified, don't write file */
if (tzObjectname != NULL)
if (objectName != NULL)
out_WriteObject();
return 0;
}

View File

@@ -1,4 +1,3 @@
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
@@ -7,6 +6,7 @@
#include <string.h>
#include "asm/lexer.h"
#include "asm/main.h"
#include "asm/section.h"
#include "asm/warning.h"
@@ -14,6 +14,10 @@ struct OptStackEntry {
char binary[2];
char gbgfx[4];
int32_t fillByte;
bool haltnop;
bool optimizeLoads;
bool warningsAreErrors;
enum WarningState warningStates[NB_WARNINGS];
struct OptStackEntry *next;
};
@@ -34,6 +38,21 @@ void opt_P(uint8_t 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)
{
switch (s[0]) {
@@ -66,6 +85,49 @@ void opt_Parse(char *s)
}
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:
error("Unknown option '%c'\n", s[0]);
break;
@@ -90,6 +152,14 @@ void opt_Push(void)
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;
stack = entry;
}
@@ -106,6 +176,13 @@ void opt_Pop(void)
opt_B(entry->binary);
opt_G(entry->gbgfx);
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;
free(entry);
}

View File

@@ -35,12 +35,12 @@
struct Patch {
struct FileStackNode const *src;
uint32_t lineNo;
uint32_t nOffset;
uint32_t offset;
struct Section *pcSection;
uint32_t pcOffset;
uint8_t type;
uint32_t nRPNSize;
uint8_t *pRPN;
uint32_t rpnSize;
uint8_t *rpn;
struct Patch *next;
};
@@ -51,10 +51,9 @@ struct Assertion {
struct Assertion *next;
};
char *tzObjectname;
char *objectName;
/* TODO: shouldn't `pCurrentSection` be somewhere else? */
struct Section *pSectionList, *pCurrentSection;
struct Section *sectionList;
/* Linked list of symbols to put in the object file */
static struct Symbol *objectSymbols = NULL;
@@ -72,7 +71,7 @@ static uint32_t countSections(void)
{
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++;
return count;
@@ -136,9 +135,9 @@ static uint32_t getNbFileStackNodes(void)
void out_RegisterNode(struct FileStackNode *node)
{
/* If node is not already registered, register it (and parents), and give it a unique ID */
while (node->ID == -1) {
while (node->ID == (uint32_t)-1) {
node->ID = getNbFileStackNodes();
if (node->ID == -1)
if (node->ID == (uint32_t)-1)
fatalerror("Reached too many file stack nodes; try splitting the file up\n");
node->next = fileStackNodes;
fileStackNodes = node;
@@ -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)
{
struct Section const *sec = pSectionList;
struct Section const *sec = sectionList;
uint32_t ID = 0;
while (sec) {
@@ -205,12 +204,12 @@ static void writepatch(struct Patch const *patch, FILE *f)
assert(patch->src->ID != -1);
putlong(patch->src->ID, f);
putlong(patch->lineNo, f);
putlong(patch->nOffset, f);
putlong(patch->offset, f);
putlong(getSectIDIfAny(patch->pcSection), f);
putlong(patch->pcOffset, f);
putc(patch->type, f);
putlong(patch->nRPNSize, f);
fwrite(patch->pRPN, 1, patch->nRPNSize, f);
putlong(patch->rpnSize, 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
*/
@@ -266,7 +280,7 @@ static void registerSymbol(struct Symbol *sym)
*objectSymbolsTail = sym;
objectSymbolsTail = &sym->next;
out_RegisterNode(sym->src);
if (nbSymbols == -1)
if (nbSymbols == (uint32_t)-1)
fatalerror("Registered too many symbols (%" PRIu32
"); try splitting up your files\n", (uint32_t)-1);
sym->ID = nbSymbols++;
@@ -278,7 +292,7 @@ static void registerSymbol(struct Symbol *sym)
*/
static uint32_t getSymbolID(struct Symbol *sym)
{
if (sym->ID == -1 && !sym_IsPC(sym))
if (sym->ID == (uint32_t)-1 && !sym_IsPC(sym))
registerSymbol(sym);
return sym->ID;
}
@@ -286,7 +300,7 @@ static uint32_t getSymbolID(struct Symbol *sym)
static void writerpn(uint8_t *rpnexpr, uint32_t *rpnptr, uint8_t *rpn,
uint32_t rpnlen)
{
char tzSym[512];
char symName[512];
for (size_t offset = 0; offset < rpnlen; ) {
#define popbyte() rpn[offset++]
@@ -310,14 +324,14 @@ static void writerpn(uint8_t *rpnexpr, uint32_t *rpnptr, uint8_t *rpn,
case RPN_SYM:
i = 0;
do {
tzSym[i] = popbyte();
} while (tzSym[i++]);
symName[i] = popbyte();
} while (symName[i++]);
// The symbol name is always written expanded
sym = sym_FindExactSymbol(tzSym);
sym = sym_FindExactSymbol(symName);
if (sym_IsConstant(sym)) {
writebyte(RPN_CONST);
value = sym_GetConstantValue(tzSym);
value = sym_GetConstantValue(symName);
} else {
writebyte(RPN_SYM);
value = getSymbolID(sym);
@@ -332,11 +346,11 @@ static void writerpn(uint8_t *rpnexpr, uint32_t *rpnptr, uint8_t *rpn,
case RPN_BANK_SYM:
i = 0;
do {
tzSym[i] = popbyte();
} while (tzSym[i++]);
symName[i] = popbyte();
} while (symName[i++]);
// The symbol name is always written expanded
sym = sym_FindExactSymbol(tzSym);
sym = sym_FindExactSymbol(symName);
value = getSymbolID(sym);
writebyte(RPN_BANK_SYM);
@@ -354,6 +368,22 @@ static void writerpn(uint8_t *rpnexpr, uint32_t *rpnptr, uint8_t *rpn,
} while (b != 0);
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:
writebyte(rpndata);
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)
{
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();
if (!patch)
fatalerror("No memory for patch: %s\n", strerror(errno));
patch->pRPN = malloc(sizeof(*patch->pRPN) * rpnSize);
if (!patch->pRPN)
fatalerror("No memory for patch's RPN expression: %s\n", strerror(errno));
patch->rpn = malloc(sizeof(*patch->rpn) * rpnSize);
if (!patch->rpn)
fatalerror("No memory for patch's RPN rpnSize: %s\n", strerror(errno));
patch->type = type;
patch->src = node;
out_RegisterNode(node);
patch->lineNo = lexer_GetLineNo();
patch->nOffset = ofs;
patch->offset = ofs;
patch->pcSection = sect_GetSymbolSection();
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) {
patch->nRPNSize = rpnSize;
patch->rpnSize = rpnSize;
/* Make sure to update `rpnSize` above if modifying this! */
patch->pRPN[0] = RPN_CONST;
patch->pRPN[1] = (uint32_t)(expr->nVal) & 0xFF;
patch->pRPN[2] = (uint32_t)(expr->nVal) >> 8;
patch->pRPN[3] = (uint32_t)(expr->nVal) >> 16;
patch->pRPN[4] = (uint32_t)(expr->nVal) >> 24;
patch->rpn[0] = RPN_CONST;
patch->rpn[1] = (uint32_t)(expr->val) & 0xFF;
patch->rpn[2] = (uint32_t)(expr->val) >> 8;
patch->rpn[3] = (uint32_t)(expr->val) >> 16;
patch->rpn[4] = (uint32_t)(expr->val) >> 24;
} else {
patch->nRPNSize = 0;
writerpn(patch->pRPN, &patch->nRPNSize, expr->tRPN, expr->nRPNLength);
patch->rpnSize = 0;
writerpn(patch->rpn, &patch->rpnSize, expr->rpn, expr->rpnLength);
}
assert(patch->nRPNSize == rpnSize);
assert(patch->rpnSize == rpnSize);
return patch;
}
@@ -418,8 +448,8 @@ void out_CreatePatch(uint32_t type, struct Expression const *expr, uint32_t ofs,
// before those bytes.
patch->pcOffset -= pcShift;
patch->next = pCurrentSection->patches;
pCurrentSection->patches = patch;
patch->next = currentSection->patches;
currentSection->patches = patch;
}
/**
@@ -452,6 +482,13 @@ static void writeassert(struct Assertion *assert, FILE *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)
{
putlong(node->parent ? node->parent->ID : -1, f);
@@ -474,7 +511,7 @@ static void registerUnregisteredSymbol(struct Symbol *symbol, void *arg)
(void)arg; // sym_ForEach requires a void* parameter, but we are not using it.
// Check for symbol->src, to skip any built-in symbol from rgbasm
if (symbol->src && symbol->ID == -1) {
if (symbol->src && symbol->ID == (uint32_t)-1) {
registerSymbol(symbol);
}
}
@@ -485,13 +522,13 @@ static void registerUnregisteredSymbol(struct Symbol *symbol, void *arg)
void out_WriteObject(void)
{
FILE *f;
if (strcmp(tzObjectname, "-") != 0)
f = fopen(tzObjectname, "wb");
if (strcmp(objectName, "-") != 0)
f = fopen(objectName, "wb");
else
f = fdopen(1, "wb");
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 */
sym_ForEach(registerUnregisteredSymbol, NULL);
@@ -514,13 +551,21 @@ void out_WriteObject(void)
for (struct Symbol const *sym = objectSymbols; sym; sym = sym->next)
writesymbol(sym, f);
for (struct Section *sect = pSectionList; sect; sect = sect->next)
for (struct Section *sect = sectionList; sect; sect = sect->next) {
writesection(sect, f);
freesection(sect);
}
putlong(countAsserts(), f);
for (struct Assertion *assert = assertions; assert;
assert = assert->next)
struct Assertion *assert = assertions;
while (assert != NULL) {
struct Assertion *next = assert->next;
writeassert(assert, f);
freeassert(assert);
assert = next;
}
fclose(f);
}
@@ -530,7 +575,7 @@ void out_WriteObject(void)
*/
void out_SetFileName(char *s)
{
tzObjectname = s;
objectName = s;
if (verbose)
printf("Output filename %s\n", s);
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
.\"
.\" This file is part of RGBDS.
.\"
.\" Copyright (c) 2010-2019, Anthony J. Bentley and RGBDS contributors.
.\" Copyright (c) 2010-2021, Anthony J. Bentley and RGBDS contributors.
.\"
.\" SPDX-License-Identifier: MIT
.\"
.Dd July 8, 2019
.Dd March 28, 2021
.Dt RGBASM 1
.Os
.Sh NAME
@@ -189,7 +189,7 @@ Ignoring the
prefix, entries are listed alphabetically.
.Bl -tag -width Ds
.It Fl Wno-assert
Warns when
Warn when
.Ic WARN Ns No -type
assertions fail. (See
.Dq Aborting the assembly process
@@ -197,6 +197,12 @@ in
.Xr rgbasm 5
for
.Ic ASSERT ) .
.It Fl Wbackwards-for
Warn when
.Ic FOR
loops have their start and stop values switched according to the step value.
This warning is enabled by
.Fl Wall .
.It Fl Wbuiltin-args
Warn about incorrect arguments to built-in functions, such as
.Fn STRSUB
@@ -239,7 +245,7 @@ constant or
directive are encountered.
.It Fl Wshift
Warn when shifting right a negative value.
Use a division by 2^N instead.
Use a division by 2**N instead.
.It Fl Wshift-amount
Warn when a shift's operand is negative or greater than 32.
.It Fl Wno-truncation

View File

@@ -1,11 +1,11 @@
.\"
.\" This file is part of RGBDS.
.\"
.\" Copyright (c) 2017-2018, Antonio Nino Diaz and RGBDS contributors.
.\" Copyright (c) 2017-2021, Antonio Nino Diaz and RGBDS contributors.
.\"
.\" SPDX-License-Identifier: MIT
.\"
.Dd December 5, 2019
.Dd March 28, 2021
.Dt RGBASM 5
.Os
.Sh NAME
@@ -25,11 +25,11 @@ Generally,
.Dq the linker
will refer to
.Xr rgblink 1 ,
but any program that processes RGB object files (described in
but any program that processes RGBDS object files (described in
.Xr rgbds 5 )
can be used in its place.
.Sh SYNTAX
The syntax is linebased, just as in any other assembler, meaning that you do one instruction or directive per line:
The syntax is line-based, just as in any other assembler, meaning that you do one instruction or directive per line:
.Pp
.Dl Oo Ar label Oc Oo Ar instruction Oc Oo Ar ;\ comment Oc
.Pp
@@ -38,7 +38,7 @@ Example:
John: ld a,87 ;Weee
.Ed
.Pp
All reserved keywords (directives, mnemonics, registers, etc.) are caseinsensitive;
All reserved keywords (directives, mnemonics, registers, etc.) are case-insensitive;
all identifiers (symbol names) are case-sensitive.
.Pp
Comments are used to give humans information about the code, such as explanations.
@@ -91,13 +91,13 @@ section.
The instructions in the macro-language generally require constant expressions.
.Ss 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 Hexadecimal Ta $ Ta 0123456789ABCDEF
.It Decimal Ta none Ta 0123456789
.It Octal Ta & Ta 01234567
.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 Gameboy graphics Ta \` Ta 0123
.El
@@ -177,8 +177,8 @@ and
.Pp
.Ic \&!
returns 1 if the operand was 0, and 0 otherwise.
.Ss Fixedpoint Expressions
Fixed-point numbers are basically normal (32-bit) integers, which count 65536th's instead of entire units, offering better precision than integers but limiting the range of values.
.Ss Fixed-point Expressions
Fixed-point numbers are basically normal (32-bit) integers, which count 65536ths instead of entire units, offering better precision than integers but limiting the range of values.
The upper 16 bits are used for the integer part and the lower 16 bits are used for the fraction (65536ths).
Since they are still akin to integers, you can use them in normal integer expressions, and some integer operators like
.Sq +
@@ -266,21 +266,37 @@ A funky feature is
.Ql {symbol}
within a string, called
.Dq symbol interpolation .
This will paste
.Ar symbol Ap s
contents as a string.
If it's a string symbol, the string is simply inserted.
If it's a numeric symbol, its value is converted to hexadecimal notation with a dollar sign
This will paste the contents of
.Ql symbol
as if they were part of the source file.
If it's a string symbol, its characters are simply inserted.
If it's a numerical symbol, its value is converted to hexadecimal notation with a dollar sign
.Sq $
prepended.
.Bd -literal -offset indent
TOPIC equs "life, the universe, and \[rs]"everything\[rs]""
ANSWER = 42
;\ Prints "The answer to life, the universe, and "everything" is $2A"
PRINTLN "The answer to {TOPIC} is {ANSWER}"
.Ed
.Pp
Symbols can be
.Em interpolated
even in the contexts that disable
.Em expansion
of string equates:
.Ql DEF({name}) ,
.Ql DEF {name} EQU/SET/EQUS/etc ... ,
.Ql PURGE {name} ,
and
.Ql MACRO {name}
will all interpolate the contents of
.Ql {name} .
.Pp
Symbol interpolations can be nested, too!
.Bd -literal -offset indent
DEF topic EQUS "life, the universe, and \[rs]"everything\[rs]""
DEF meaning EQUS "answer"
;\ Defines answer = 42
DEF {meaning} = 42
;\ Prints "The answer to life, the universe, and "everything" is 42"
PRINTLN "The {meaning} to {topic} is {d:{meaning}}"
PURGE topic, meaning, {meaning}
.Ed
.Pp
It's possible to change the way symbols are converted by specifying a print format like so:
.Ql {fmt:symbol} .
@@ -321,7 +337,7 @@ followed by one or more
\[en]
.Ql 9 .
If specified, prints this many digits of a fixed-point fraction.
Defaults to 5 digits.
Defaults to 5 digits, maximum 255 digits.
.It Ql <type> Ta Specifies the type of value.
.El
.Pp
@@ -355,14 +371,14 @@ HINT: The
construct can also be used outside strings.
The symbol's value is again inserted directly.
.Bd -literal -offset indent
NAME equs "ITEM"
FMT equs "d"
ZERO_NUM equ 0
ZERO_STR equs "0"
def NAME equs "ITEM"
def FMT equs "d"
def ZERO_NUM equ 0
def ZERO_STR equs "0"
;\ Defines INDEX as 100
INDEX = 1{ZERO_STR}{{FMT}:ZERO_NUM}
;\ Defines ITEM_100 as "\[rs]"hundredth\[rs]""
{NAME}_{d:INDEX} equs "\[rs]"hundredth\[rs]""
def {NAME}_{d:INDEX} equs "\[rs]"hundredth\[rs]""
;\ Prints "ITEM_100 is hundredth"
PRINTLN STRCAT("{NAME}_{d:INDEX} is ", {NAME}_{d:INDEX})
;\ Purges ITEM_100
@@ -378,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 STRIN str1 str2 Ta Returns the first position of Ar str2 No in Ar str1 No or zero if it's not present Pq first character is position 1 .
.It Fn STRRIN str1 str2 Ta Returns the last position of Ar str2 No in Ar str1 No or zero if it's not present Pq first character is position 1 .
.It Fn STRSUB str pos len Ta Returns a substring from Ar str No starting at Ar pos Po first character is position 1 Pc and Ar len No characters long.
.It Fn STRSUB str pos len Ta Returns a substring from Ar str No starting at Ar pos 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 STRLWR str Ta Returns Ar str No with all letters in lowercase.
.It Fn STRRPL str old new Ta Returns Ar str No with each non-overlapping occurrence of the substring Ar old No replaced with Ar new .
.It Fn STRFMT fmt args... Ta Returns the string Ar fmt No with each
.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
pattern replaced by interpolating the format
.Ar spec
@@ -443,10 +461,16 @@ is a label, it returns the bank number the label is in.
The result may be constant if
.Nm
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
.Ar symbol
has been defined, FALSE (0) otherwise.
String symbols are not expanded within the parentheses.
String equates are not expanded within the parentheses.
.It Fn HIGH arg Ta Returns the top 8 bits of the operand if Ar arg No is a label or constant, or the top 8-bit register if it is a 16-bit register.
.It Fn LOW arg Ta Returns the bottom 8 bits of the operand if Ar arg No is a label or constant, or the bottom 8-bit register if it is a 16-bit register Pq Cm AF No isn't a valid register for this function .
.It Fn ISCONST arg Ta Returns 1 if Ar arg Ap s value is known by RGBASM (e.g. if it can be an argument to
@@ -590,7 +614,7 @@ depending on
.It Ic ALIGN Ns Bq Ar align , offset
Place the section at an address whose
.Ar align
leastsignificant bits are equal to
least-significant bits are equal to
.Ar offset .
(Note that
.Ic ALIGN Ns Bq Ar align
@@ -853,16 +877,7 @@ Periods
.Sq \&.
are allowed exclusively in labels, as described below.
A symbol cannot have the same name as a reserved keyword.
.Pp
Constants and string equates
.Em must not
have any whitespace before their name when they are defined;
otherwise
.Nm
will treat them as a macro invocation.
Label and macro definitions may have whitespace before them, since a leading period or a following colon distinguishes them from invoking a macro.
.Bl -tag -width indent
.It Sy Label declaration
.Ss Label declaration
One of the assembler's main tasks is to keep track of addresses for you, so you can work with meaningful names instead of "magic" numbers.
.Pp
This can be done in a number of ways:
@@ -937,56 +952,77 @@ However, if the section in which the label is declared has a fixed base address,
.Pp
.Nm
is able to compute the subtraction of two labels either if both are constant as described above, or if both belong to the same section.
.It Ic EQU
.Ss Immutable constants
.Ic EQU
allows defining constant symbols.
is used to define numerical constant symbols.
Unlike
.Ic SET
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
SCREEN_WIDTH equ 160 ;\ In pixels
SCREEN_HEIGHT equ 144
def SCREEN_WIDTH equ 160 ;\ In pixels
def SCREEN_HEIGHT equ 144
.Ed
.Pp
Note that colons
.Ql \&:
following the name are not allowed.
.It Ic SET
.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
.Ic SET ,
or its synonym
.Ic = ,
defines constant symbols like
is used to define numerical symbols like
.Ic EQU ,
but those constants can be redefined.
but these symbols can be redefined.
This is useful for variables in macros, for counters, etc.
.Bd -literal -offset indent
ARRAY_SIZE EQU 4
COUNT SET 2
COUNT SET ARRAY_SIZE+COUNT
;\ COUNT now has the value 6
COUNT = COUNT + 1
DEF ARRAY_SIZE EQU 4
DEF COUNT SET 2
DEF COUNT SET 3
REDEF COUNT SET ARRAY_SIZE+COUNT
COUNT = COUNT*2
;\ COUNT now has the value 14
.Ed
.Pp
Note that colons
.Ql \&:
following the name are not allowed.
.It Ic RSSET , RSRESET , RB , RW
The RS group of commands is a handy way of defining structures:
.Ss Offset constants
The RS group of commands is a handy way of defining structure offsets:
.Bd -literal -offset indent
RSRESET
str_pStuff RW 1
str_tData RB 256
str_bCount RB 1
str_SIZEOF RB 0
RSRESET
DEF str_pStuff RW 1
DEF str_tData RB 256
DEF str_bCount RB 1
DEF str_SIZEOF RB 0
.Ed
.Pp
The example defines four constants as if by:
.Bd -literal -offset indent
str_pStuff EQU 0
str_tData EQU 2
str_bCount EQU 258
str_SIZEOF EQU 259
DEF str_pStuff EQU 0
DEF str_tData EQU 2
DEF str_bCount EQU 258
DEF str_SIZEOF EQU 259
.Ed
.Pp
There are five commands in the RS group of commands:
@@ -1008,17 +1044,26 @@ is omitted, it's assumed to be 1.
Note that colons
.Ql \&:
following the name are not allowed.
.It Ic EQUS
.Ss String equates
.Ic EQUS
is used to define string symbols.
Wherever the assembler meets a string symbol its name is replaced with its value.
If you are familiar with C you can think of it as similar to
is used to define string equate symbols.
Wherever the assembler reads a string equate, it gets
.Em expanded :
the symbol's name is replaced with its contents.
If you are familiar with C, you can think of it as similar to
.Fd #define .
This expansion is disabled in a few contexts:
.Ql DEF(name) ,
.Ql DEF name EQU/SET/EQUS/etc ... ,
.Ql PURGE name ,
and
.Ql MACRO name
will not expand string equates in their names.
.Bd -literal -offset indent
COUNTREG EQUS "[hl+]"
DEF COUNTREG EQUS "[hl+]"
ld a,COUNTREG
PLAYER_NAME EQUS "\[rs]"John\[rs]""
DEF PLAYER_NAME EQUS "\[rs]"John\[rs]""
db PLAYER_NAME
.Ed
.Pp
@@ -1028,9 +1073,9 @@ This will be interpreted as:
db "John"
.Ed
.Pp
String symbols can also be used to define small one-line macros:
String equates can also be used to define small one-line macros:
.Bd -literal -offset indent
pusha EQUS "push af\[rs]npush bc\[rs]npush de\[rs]npush hl\[rs]n"
DEF pusha EQUS "push af\[rs]npush bc\[rs]npush de\[rs]npush hl\[rs]n"
.Ed
.Pp
Note that colons
@@ -1047,10 +1092,10 @@ However, the
keyword will define or redefine a string symbol.
For example:
.Bd -literal -offset indent
s EQUS "Hello, "
DEF s EQUS "Hello, "
REDEF s EQUS "{s}world!"
; prints "Hello, world!"
PRINTT "{s}\n"
PRINTLN "{s}\n"
.Ed
.Pp
.Sy Important note :
@@ -1070,7 +1115,47 @@ command-line option in
Also, a macro can contain an
.Ic EQUS
which calls the same macro, which causes the same problem.
.It Ic MACRO
.Pp
The examples above for
.Ql EQU ,
.Ql SET
or
.Ql = ,
.Ql RB ,
.Ql RW ,
.Ql RL ,
and
.Ql EQUS
all start with
.Ql DEF .
(A
.Ql SET
or
.Ql =
definition may start with
.Ql REDEF
instead, since they are redefinable.)
You may use the older syntax without
.Ql DEF ,
but then the name being defined
.Em must not
have any whitespace before it;
otherwise
.Nm
will treat it as a macro invocation.
Furthermore, without the
.Ql DEF
keyword,
string equates may be expanded for the name.
This can lead to surprising results:
.Bd -literal -offset indent
X EQUS "Y"
; this defines Y, not X!
X EQU 42
; prints "Y $2A"
PRINTLN "{X} {Y}"
.Ed
.Ss Macros
One of the best features of an assembler is the ability to write macros for it.
Macros can be called with arguments, and can react depending on input using
.Ic IF
@@ -1085,6 +1170,7 @@ ENDM
The example above defines
.Ql MyMacro
as a new macro.
String equates are not expanded within the name of the macro.
You may use the older syntax
.Ql MyMacro: MACRO
instead of
@@ -1092,6 +1178,8 @@ instead of
with a single colon
.Ql \&:
following the macro's name.
With the older syntax, string equates may be expanded for the name.
.Pp
Macros can't be exported or imported.
.Pp
Plainly nesting macro definitions is not allowed, but this can be worked around using
@@ -1108,16 +1196,19 @@ ENDM
But this will:
.Bd -literal -offset indent
MACRO outer
definition EQUS "MACRO inner\[rs]nPRINTLN \[rs]"Hello!\[rs]"\[rs]nENDM"
DEF definition EQUS "MACRO inner\[rs]nPRINTLN \[rs]"Hello!\[rs]"\[rs]nENDM"
definition
PURGE definition
ENDM
.Ed
.El
.Pp
Macro arguments support all the escape sequences of strings, as well as
.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
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
@@ -1184,15 +1275,12 @@ I can't stress this enough,
DON'T purge a symbol that you use in expressions the linker needs to calculate.
When not sure, it's probably not safe to purge anything other than string symbols, macros, and constants.
.Bd -literal -offset indent
Kamikaze EQUS "I don't want to live anymore"
AOLer EQUS "Me too"
DEF Kamikaze EQUS "I don't want to live anymore"
DEF AOLer EQUS "Me too"
PURGE Kamikaze, AOLer
.Ed
.Pp
Note that, as an exception, string symbols in the argument list of a
.Ic PURGE
command
.Em will not be expanded .
String equates are not expanded within the symbol names.
.Ss Predeclared Symbols
The following symbols are defined by the assembler:
.Bl -column -offset indent "EQUS" "__ISO_8601_LOCAL__"
@@ -1216,6 +1304,7 @@ The following symbols are defined by the assembler:
.It Dv __RGBDS_MINOR__ Ta Ic EQU Ta Minor version number of RGBDS
.It Dv __RGBDS_PATCH__ Ta Ic EQU Ta Patch version number of RGBDS
.It Dv __RGBDS_RC__ Ta Ic EQU Ta Release candidate ID of RGBDS, not defined for final releases
.It Dv __RGBDS_VERSION__ Ta Ic EQUS Ta Version of RGBDS, as printed by Ql rgbasm --version
.El
.Pp
The current time values will be taken from the
@@ -1458,7 +1547,7 @@ MACRO LoopyMacro
ENDM
.Ed
.Pp
Now I can call the macro specifying two arguments, the first being the address and the second being a byte count.
Now you can call the macro specifying two arguments, the first being the address and the second being a byte count.
The generated code will then reset all bytes in this range.
.Bd -literal -offset indent
LoopyMacro MyVars,54
@@ -1488,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.
However, some characters need to be escaped, as in the following example:
.Bd -literal -offset indent
MACRO PrintMacro
MACRO PrintMacro1
PRINTLN STRCAT(\[rs]1)
ENDM
PrintMacro1 "Hello "\[rs], \[rs]
"world"
MACRO PrintMacro2
PRINT \[rs]1
ENDM
PrintMacro STRCAT("Hello "\[rs], \[rs]
"world\[rs]n")
PrintMacro2 STRCAT("Hello ", \[rs]
"world\[rs]n")
.Ed
.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
.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
In reality, up to 256 arguments can be passed to a macro, but you can only use the first 9 like this.
If you want to use the rest, you need to use the
.Ic SHIFT
command.
Since there are only nine digits, you can only access the first nine macro arguments like this.
To use the rest, you need to put the multi-digit argument number in angle brackets, like
.Ql \[rs]<10> .
This bracketed syntax supports decimal numbers and numeric symbol names.
For example,
.Ql \[rs]<_NARG>
will get the last argument.
.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
is a special command only available in macros.
Very useful in
.Ic REPT
blocks.
command, a special command only available in macros.
It will shift the arguments by one to the left, and decrease
.Dv _NARG
by 1.
@@ -1521,11 +1638,14 @@ will get the value of
.Ic \[rs]3 ,
and so forth.
.Pp
This is the only way of accessing the value of arguments from 10 to 256.
.Pp
.Ic SHIFT
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.
.Pp
.Ic SHIFT
is useful in
.Ic REPT
blocks to repeat the same commands with multiple arguments.
.Ss Printing things during assembly
The
.Ic PRINT
@@ -1598,6 +1718,7 @@ Everything between
and the matching
.Ic ENDR
will be repeated for each value of a given symbol.
String equates are not expanded within the symbol name.
For example, this code will produce a table of squared values from 0 to 255:
.Bd -literal -offset indent
FOR N, 256
@@ -1624,9 +1745,9 @@ You can customize the range of
values:
.Bl -column "FOR V, start, stop, step"
.It Sy Code Ta Sy Range
.It Ic FOR Ar V , stop Ta Ar V No increments from 0 to Ar stop No
.It Ic FOR Ar V , start , stop Ta Ar V No increments from Ar start No to Ar stop No
.It Ic FOR Ar V , start , stop , step Ta Ar V No goes from Ar start No to Ar stop No by Ar step No
.It Ic FOR Ar V , stop Ta Ar V No increments from 0 to Ar stop
.It Ic FOR Ar V , start , stop Ta Ar V No increments from Ar start No to Ar stop
.It Ic FOR Ar V , start , stop , step Ta Ar V No goes from Ar start No to Ar stop No by Ar step
.El
.Pp
The
@@ -1825,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:
.Bd -literal -offset indent
PUSHO
OPT g.oOX ;Set the GB graphics constants to use these characters
DW `..ooOOXX
OPT g.oOX, Wdiv, L ; acts like command-line -g.oOX -Wdiv -L
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
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
.Pp
The options that OPT can modify are currently:
.Cm b , g
.Cm b , g , p , h , L ,
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
.Ic POPO
and

View File

@@ -13,6 +13,7 @@
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@@ -45,47 +46,47 @@
static uint8_t *reserveSpace(struct Expression *expr, uint32_t size)
{
/* 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 (!expr->tRPN)
expr->nRPNCapacity = 256; /* Initial size */
while (expr->nRPNCapacity - expr->nRPNLength < size) {
if (expr->nRPNCapacity >= MAXRPNLEN)
if (!expr->rpn)
expr->rpnCapacity = 256; /* Initial size */
while (expr->rpnCapacity - expr->rpnLength < size) {
if (expr->rpnCapacity >= MAXRPNLEN)
/*
* To avoid generating humongous object files, cap the
* size of RPN expressions
*/
fatalerror("RPN expression cannot grow larger than "
EXPAND_AND_STR(MAXRPNLEN) " bytes\n");
else if (expr->nRPNCapacity > MAXRPNLEN / 2)
expr->nRPNCapacity = MAXRPNLEN;
else if (expr->rpnCapacity > MAXRPNLEN / 2)
expr->rpnCapacity = MAXRPNLEN;
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));
}
uint8_t *ptr = expr->tRPN + expr->nRPNLength;
uint8_t *ptr = expr->rpn + expr->rpnLength;
expr->nRPNLength += size;
expr->rpnLength += size;
return ptr;
}
/*
* Init the RPN expression
* Init a RPN expression
*/
static void rpn_Init(struct Expression *expr)
{
expr->reason = NULL;
expr->isKnown = true;
expr->isSymbol = false;
expr->tRPN = NULL;
expr->nRPNCapacity = 0;
expr->nRPNLength = 0;
expr->nRPNPatchSize = 0;
expr->rpn = NULL;
expr->rpnCapacity = 0;
expr->rpnLength = 0;
expr->rpnPatchSize = 0;
}
/*
@@ -93,7 +94,7 @@ static void rpn_Init(struct Expression *expr)
*/
void rpn_Free(struct Expression *expr)
{
free(expr->tRPN);
free(expr->rpn);
free(expr->reason);
rpn_Init(expr);
}
@@ -104,12 +105,12 @@ void rpn_Free(struct Expression *expr)
void rpn_Number(struct Expression *expr, uint32_t i)
{
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()) {
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;
makeUnknown(expr, sym_IsPC(sym) ? "PC is not constant at assembly time"
: "'%s' is not constant at assembly time", tzSym);
sym = sym_Ref(tzSym);
expr->nRPNPatchSize += 5; /* 1-byte opcode + 4-byte symbol ID */
: "'%s' is not constant at assembly time", symName);
sym = sym_Ref(symName);
expr->rpnPatchSize += 5; /* 1-byte opcode + 4-byte symbol ID */
size_t nameLen = strlen(sym->name) + 1; /* Don't forget NUL! */
uint8_t *ptr = reserveSpace(expr, nameLen + 1);
*ptr++ = RPN_SYM;
memcpy(ptr, sym->name, nameLen);
} 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);
if (!pCurrentSection) {
if (!currentSection) {
error("PC has no bank outside a section\n");
expr->nVal = 1;
} else if (pCurrentSection->bank == -1) {
expr->val = 1;
} else if (currentSection->bank == (uint32_t)-1) {
makeUnknown(expr, "Current section's bank is not known");
expr->nRPNPatchSize++;
expr->rpnPatchSize++;
*reserveSpace(expr, 1) = RPN_BANK_SELF;
} 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. */
if (sym_IsPC(sym)) {
@@ -162,15 +163,15 @@ void rpn_BankSymbol(struct Expression *expr, char const *tzSym)
if (sym && !sym_IsLabel(sym)) {
error("BANK argument must be a label\n");
} else {
sym = sym_Ref(tzSym);
sym = sym_Ref(symName);
assert(sym); // If the symbol didn't exist, it should have been created
if (sym_GetSection(sym) && sym_GetSection(sym)->bank != -1) {
if (sym_GetSection(sym) && sym_GetSection(sym)->bank != (uint32_t)-1) {
/* Symbol's section is known and bank is fixed */
expr->nVal = sym_GetSection(sym)->bank;
expr->val = sym_GetSection(sym)->bank;
} else {
makeUnknown(expr, "\"%s\"'s bank is not known", tzSym);
expr->nRPNPatchSize += 5; /* opcode + 4-byte sect ID */
makeUnknown(expr, "\"%s\"'s bank is not known", symName);
expr->rpnPatchSize += 5; /* opcode + 4-byte sect ID */
size_t nameLen = strlen(sym->name) + 1; /* Room for NUL! */
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);
struct Section *pSection = out_FindSectionByName(tzSectionName);
struct Section *section = sect_FindSectionByName(sectionName);
if (pSection && pSection->bank != -1) {
expr->nVal = pSection->bank;
if (section && section->bank != (uint32_t)-1) {
expr->val = section->bank;
} else {
makeUnknown(expr, "Section \"%s\"'s bank is not known",
tzSectionName);
makeUnknown(expr, "Section \"%s\"'s bank is not known", sectionName);
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);
expr->nRPNPatchSize += nameLen + 1;
expr->rpnPatchSize += nameLen + 1;
*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)
{
*expr = *src;
expr->isSymbol = false;
if (!rpn_isKnown(expr)) {
expr->nRPNPatchSize++;
expr->rpnPatchSize++;
*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 */
expr->nVal &= 0xFF;
} else if (expr->nVal < 0 || expr->nVal > 0xFF) {
error("Source address $%" PRIx32 " not between $FF00 to $FFFF\n", expr->nVal);
expr->val &= 0xFF;
} else if (expr->val < 0 || expr->val > 0xFF) {
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)) {
/* A valid RST address must be masked with 0x38 */
if (expr->nVal & ~0x38)
error("Invalid address $%" PRIx32 " for RST\n", expr->nVal);
if (expr->val & ~0x38)
error("Invalid address $%" PRIx32 " for RST\n", expr->val);
/* The target is in the "0x38" bits, all other bits are set */
expr->nVal |= 0xC7;
expr->val |= 0xC7;
} else {
expr->nRPNPatchSize++;
expr->rpnPatchSize++;
*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)
{
*expr = *src;
expr->isSymbol = false;
if (rpn_isKnown(expr)) {
expr->nVal = !expr->nVal;
expr->val = !expr->val;
} else {
expr->nRPNPatchSize++;
expr->rpnPatchSize++;
*reserveSpace(expr, 1) = RPN_LOGUNNOT;
}
}
@@ -250,7 +303,7 @@ struct Symbol const *rpn_SymbolOf(struct Expression const *expr)
{
if (!rpn_isSymbol(expr))
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)
@@ -283,111 +336,110 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
rpn_Init(expr); /* Init the expression to something sane */
/* 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) {
case RPN_LOGOR:
expr->nVal = src1->nVal || src2->nVal;
expr->val = src1->val || src2->val;
break;
case RPN_LOGAND:
expr->nVal = src1->nVal && src2->nVal;
expr->val = src1->val && src2->val;
break;
case RPN_LOGEQ:
expr->nVal = src1->nVal == src2->nVal;
expr->val = src1->val == src2->val;
break;
case RPN_LOGGT:
expr->nVal = src1->nVal > src2->nVal;
expr->val = src1->val > src2->val;
break;
case RPN_LOGLT:
expr->nVal = src1->nVal < src2->nVal;
expr->val = src1->val < src2->val;
break;
case RPN_LOGGE:
expr->nVal = src1->nVal >= src2->nVal;
expr->val = src1->val >= src2->val;
break;
case RPN_LOGLE:
expr->nVal = src1->nVal <= src2->nVal;
expr->val = src1->val <= src2->val;
break;
case RPN_LOGNE:
expr->nVal = src1->nVal != src2->nVal;
expr->val = src1->val != src2->val;
break;
case RPN_ADD:
expr->nVal = uleft + uright;
expr->val = uleft + uright;
break;
case RPN_SUB:
expr->nVal = uleft - uright;
expr->val = uleft - uright;
break;
case RPN_XOR:
expr->nVal = src1->nVal ^ src2->nVal;
expr->val = src1->val ^ src2->val;
break;
case RPN_OR:
expr->nVal = src1->nVal | src2->nVal;
expr->val = src1->val | src2->val;
break;
case RPN_AND:
expr->nVal = src1->nVal & src2->nVal;
expr->val = src1->val & src2->val;
break;
case RPN_SHL:
if (src2->nVal < 0)
if (src2->val < 0)
warning(WARNING_SHIFT_AMOUNT,
"Shifting left by negative amount %" PRId32 "\n",
src2->nVal);
src2->val);
if (src2->nVal >= 32)
if (src2->val >= 32)
warning(WARNING_SHIFT_AMOUNT,
"Shifting left by large amount %" PRId32 "\n",
src2->nVal);
"Shifting left by large amount %" PRId32 "\n", src2->val);
expr->nVal = op_shift_left(src1->nVal, src2->nVal);
expr->val = op_shift_left(src1->val, src2->val);
break;
case RPN_SHR:
if (src1->nVal < 0)
warning(WARNING_SHIFT, "Shifting right negative value %"
PRId32 "\n",
src1->nVal);
if (src1->val < 0)
warning(WARNING_SHIFT,
"Shifting right negative value %" PRId32 "\n", src1->val);
if (src2->nVal < 0)
if (src2->val < 0)
warning(WARNING_SHIFT_AMOUNT,
"Shifting right by negative amount %" PRId32 "\n",
src2->nVal);
src2->val);
if (src2->nVal >= 32)
if (src2->val >= 32)
warning(WARNING_SHIFT_AMOUNT,
"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;
case RPN_MUL:
expr->nVal = uleft * uright;
expr->val = uleft * uright;
break;
case RPN_DIV:
if (src2->nVal == 0)
if (src2->val == 0)
fatalerror("Division by zero\n");
if (src1->nVal == INT32_MIN && src2->nVal == -1) {
warning(WARNING_DIV, "Division of %" PRId32 " by -1 yields %"
PRId32 "\n", INT32_MIN, INT32_MIN);
expr->nVal = INT32_MIN;
if (src1->val == INT32_MIN && src2->val == -1) {
warning(WARNING_DIV,
"Division of %" PRId32 " by -1 yields %" PRId32 "\n",
INT32_MIN, INT32_MIN);
expr->val = INT32_MIN;
} else {
expr->nVal = op_divide(src1->nVal, src2->nVal);
expr->val = op_divide(src1->val, src2->val);
}
break;
case RPN_MOD:
if (src2->nVal == 0)
if (src2->val == 0)
fatalerror("Modulo by zero\n");
if (src1->nVal == INT32_MIN && src2->nVal == -1)
expr->nVal = 0;
if (src1->val == INT32_MIN && src2->val == -1)
expr->val = 0;
else
expr->nVal = op_modulo(src1->nVal, src2->nVal);
expr->val = op_modulo(src1->val, src2->val);
break;
case RPN_EXP:
if (src2->nVal < 0)
if (src2->val < 0)
fatalerror("Exponentiation by negative power\n");
if (src1->nVal == INT32_MIN && src2->nVal == -1)
expr->nVal = 0;
if (src1->val == INT32_MIN && src2->val == -1)
expr->val = 0;
else
expr->nVal = op_exponent(src1->nVal, src2->nVal);
expr->val = op_exponent(src1->val, src2->val);
break;
case RPN_UNSUB:
@@ -396,6 +448,8 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
case RPN_BANK_SYM:
case RPN_BANK_SECT:
case RPN_BANK_SELF:
case RPN_SIZEOF_SECT:
case RPN_STARTOF_SECT:
case RPN_HRAM:
case RPN_RST:
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 *symbol2 = rpn_SymbolOf(src2);
expr->nVal = sym_GetValue(symbol1) - sym_GetValue(symbol2);
expr->val = sym_GetValue(symbol1) - sym_GetValue(symbol2);
expr->isKnown = true;
} else {
/* If it's not known, start computing the RPN expression */
/* Convert the left-hand expression if it's constant */
if (src1->isKnown) {
uint32_t lval = src1->nVal;
uint32_t lval = src1->val;
uint8_t bytes[] = {RPN_CONST, lval, lval >> 8,
lval >> 16, lval >> 24};
expr->nRPNPatchSize = sizeof(bytes);
expr->tRPN = NULL;
expr->nRPNCapacity = 0;
expr->nRPNLength = 0;
expr->rpnPatchSize = sizeof(bytes);
expr->rpn = NULL;
expr->rpnCapacity = 0;
expr->rpnLength = 0;
memcpy(reserveSpace(expr, sizeof(bytes)), bytes,
sizeof(bytes));
@@ -429,21 +483,21 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
free(src1->reason);
} else {
/* Otherwise just reuse its RPN buffer */
expr->nRPNPatchSize = src1->nRPNPatchSize;
expr->tRPN = src1->tRPN;
expr->nRPNCapacity = src1->nRPNCapacity;
expr->nRPNLength = src1->nRPNLength;
expr->rpnPatchSize = src1->rpnPatchSize;
expr->rpn = src1->rpn;
expr->rpnCapacity = src1->rpnCapacity;
expr->rpnLength = src1->rpnLength;
expr->reason = src1->reason;
free(src2->reason);
}
/* Now, merge the right expression into the left one */
uint8_t *ptr = src2->tRPN; /* Pointer to the right RPN */
uint32_t len = src2->nRPNLength; /* Size of the right RPN */
uint32_t patchSize = src2->nRPNPatchSize;
uint8_t *ptr = src2->rpn; /* Pointer to the right RPN */
uint32_t len = src2->rpnLength; /* Size of the right RPN */
uint32_t patchSize = src2->rpnPatchSize;
/* 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,
rval >> 24};
if (src2->isKnown) {
@@ -457,8 +511,8 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
memcpy(buf, ptr, len);
buf[len] = op;
free(src2->tRPN); /* If there was none, this is `free(NULL)` */
expr->nRPNPatchSize += patchSize + 1;
free(src2->rpn); /* If there was none, this is `free(NULL)` */
expr->rpnPatchSize += patchSize + 1;
}
}
@@ -468,11 +522,11 @@ void rpn_HIGH(struct Expression *expr, const struct Expression *src)
expr->isSymbol = false;
if (rpn_isKnown(expr)) {
expr->nVal = (uint32_t)expr->nVal >> 8 & 0xFF;
expr->val = (uint32_t)expr->val >> 8 & 0xFF;
} else {
uint8_t bytes[] = {RPN_CONST, 8, 0, 0, 0, RPN_SHR,
RPN_CONST, 0xFF, 0, 0, 0, RPN_AND};
expr->nRPNPatchSize += sizeof(bytes);
expr->rpnPatchSize += 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;
if (rpn_isKnown(expr)) {
expr->nVal = expr->nVal & 0xFF;
expr->val = expr->val & 0xFF;
} else {
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));
}
}
@@ -495,7 +549,7 @@ void rpn_LOW(struct Expression *expr, const struct Expression *src)
void rpn_ISCONST(struct Expression *expr, const struct Expression *src)
{
rpn_Init(expr);
expr->nVal = rpn_isKnown(src);
expr->val = rpn_isKnown(src);
expr->isKnown = true;
expr->isSymbol = false;
}
@@ -506,9 +560,9 @@ void rpn_UNNEG(struct Expression *expr, const struct Expression *src)
expr->isSymbol = false;
if (rpn_isKnown(expr)) {
expr->nVal = -(uint32_t)expr->nVal;
expr->val = -(uint32_t)expr->val;
} else {
expr->nRPNPatchSize++;
expr->rpnPatchSize++;
*reserveSpace(expr, 1) = RPN_UNSUB;
}
}
@@ -519,9 +573,9 @@ void rpn_UNNOT(struct Expression *expr, const struct Expression *src)
expr->isSymbol = false;
if (rpn_isKnown(expr)) {
expr->nVal = ~expr->nVal;
expr->val = ~expr->val;
} else {
expr->nRPNPatchSize++;
expr->rpnPatchSize++;
*reserveSpace(expr, 1) = RPN_UNNOT;
}
}

View File

@@ -22,13 +22,16 @@ uint8_t fillByte;
struct SectionStackEntry {
struct Section *section;
struct Section *loadSection;
char const *scope; /* Section's symbol scope */
uint32_t offset;
int32_t loadOffset;
struct SectionStackEntry *next;
};
struct SectionStackEntry *sectionStack;
uint32_t curOffset; /* Offset into the current section (see sect_GetSymbolOffset) */
struct Section *currentSection = NULL;
static struct Section *currentLoadSection = NULL;
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
*/
static inline void checksection(void)
attr_(warn_unused_result) static bool checksection(void)
{
if (pCurrentSection == NULL)
fatalerror("Code generation before SECTION directive\n");
if (currentSection)
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
* this much initialized data
*/
static inline void checkcodesection(void)
attr_(warn_unused_result) static bool checkcodesection(void)
{
checksection();
if (!checksection())
return false;
if (!sect_HasData(pCurrentSection->type))
fatalerror("Section '%s' cannot contain code or data (not ROM0 or ROMX)\n",
pCurrentSection->name);
if (sect_HasData(currentSection->type))
return true;
error("Section '%s' cannot contain code or data (not ROM0 or ROMX)\n",
currentSection->name);
return false;
}
static inline 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];
if (size > maxSize)
fatalerror("Section '%s' grew too big (max size = 0x%" PRIX32
" bytes, reached 0x%" PRIX32 ").\n", sect->name, maxSize, size);
// If the new size is reasonable, keep going
if (size <= maxSize)
return true;
error("Section '%s' grew too big (max size = 0x%" PRIX32
" bytes, reached 0x%" PRIX32 ").\n", sect->name, maxSize, size);
return false;
}
/*
* Check if the section has grown too much.
*/
static inline 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
* are too big and to prevent the assembler from generating huge object
* files or trying to allocate too much memory.
* This check is here to trap broken code that generates sections that are too big and to
* prevent the assembler from generating huge object files or trying to allocate too much
* memory.
* A check at the linking stage is still necessary.
*/
checkSectionSize(pCurrentSection, curOffset + loadOffset + delta_size);
if (currentLoadSection)
checkSectionSize(currentLoadSection, curOffset + delta_size);
// If the section has already overflowed, skip the check to avoid erroring out ad nauseam
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)
return sect;
}
@@ -114,9 +138,9 @@ static unsigned int mergeSectUnion(struct Section *sect, enum SectionType type,
if (sect_HasData(type))
fail("Cannot declare ROM sections as UNION\n");
if (org != -1) {
if (org != (uint32_t)-1) {
/* If both are fixed, they must be the same */
if (sect->org != -1 && sect->org != org)
if (sect->org != (uint32_t)-1 && sect->org != org)
fail("Section already declared as fixed at different address $%04"
PRIx32 "\n", sect->org);
else if (sect->align != 0 && (mask(sect->align) & (org - sect->alignOfs)))
@@ -128,7 +152,7 @@ static unsigned int mergeSectUnion(struct Section *sect, enum SectionType type,
} else if (alignment != 0) {
/* Make sure any fixed address given is compatible */
if (sect->org != -1) {
if (sect->org != (uint32_t)-1) {
if ((sect->org - alignOffset) & mask(alignment))
fail("Section already declared as fixed at incompatible address $%04"
PRIx32 "\n", sect->org);
@@ -160,11 +184,11 @@ static unsigned int mergeFragments(struct Section *sect, enum SectionType type,
* combination of both.
* The merging is however performed at the *end* of the original section!
*/
if (org != -1) {
if (org != (uint32_t)-1) {
uint16_t curOrg = org - sect->size;
/* If both are fixed, they must be the same */
if (sect->org != -1 && sect->org != curOrg)
if (sect->org != (uint32_t)-1 && sect->org != curOrg)
fail("Section already declared as fixed at incompatible address $%04"
PRIx32 " (cur addr = %04" PRIx32 ")\n",
sect->org, sect->org + sect->size);
@@ -182,7 +206,7 @@ static unsigned int mergeFragments(struct Section *sect, enum SectionType type,
curOfs += 1U << alignment;
/* Make sure any fixed address given is compatible */
if (sect->org != -1) {
if (sect->org != (uint32_t)-1) {
if ((sect->org - curOfs) & mask(alignment))
fail("Section already declared as fixed at incompatible address $%04"
PRIx32 "\n", sect->org);
@@ -221,10 +245,10 @@ static void mergeSections(struct Section *sect, enum SectionType type, uint32_t
// Common checks
/* If the section's bank is unspecified, override it */
if (sect->bank == -1)
if (sect->bank == (uint32_t)-1)
sect->bank = bank;
/* If both specify a bank, it must be the same one */
else if (bank != -1 && sect->bank != bank)
else if (bank != (uint32_t)-1 && sect->bank != bank)
fail("Section already declared with different bank %" PRIu32 "\n",
sect->bank);
break;
@@ -296,7 +320,7 @@ static struct Section *getSection(char const *name, enum SectionType type, uint3
// First, validate parameters, and normalize them if applicable
if (bank != -1) {
if (bank != (uint32_t)-1) {
if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM
&& type != SECTTYPE_SRAM && type != SECTTYPE_WRAMX)
error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n");
@@ -316,7 +340,7 @@ static struct Section *getSection(char const *name, enum SectionType type, uint3
alignOffset = 0;
}
if (org != -1) {
if (org != (uint32_t)-1) {
if (org < startaddr[type] || org > endaddr(type))
error("Section \"%s\"'s fixed address %#" PRIx32
" is outside of range [%#" PRIx16 "; %#" PRIx16 "]\n",
@@ -331,7 +355,7 @@ static struct Section *getSection(char const *name, enum SectionType type, uint3
/* It doesn't make sense to have both alignment and org set */
uint32_t mask = mask(alignment);
if (org != -1) {
if (org != (uint32_t)-1) {
if ((org - alignOffset) & mask)
error("Section \"%s\"'s fixed address doesn't match its alignment\n",
name);
@@ -339,6 +363,8 @@ static struct Section *getSection(char const *name, enum SectionType type, uint3
} else if (startaddr[type] & mask) {
error("Section \"%s\"'s alignment cannot be attained in %s\n",
name, typeNames[type]);
alignment = 0; /* Ignore it if it's unattainable */
org = 0;
} else if (alignment == 16) {
// Treat an alignment of 16 as being fixed at address 0
alignment = 0;
@@ -349,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
struct Section *sect = out_FindSectionByName(name);
struct Section *sect = sect_FindSectionByName(name);
if (sect) {
mergeSections(sect, type, org, bank, alignment, alignOffset, mod);
} else {
sect = createSection(name, type, org, bank, alignment, alignOffset, mod);
// Add the new section to the list (order doesn't matter)
sect->next = pSectionList;
pSectionList = sect;
sect->next = sectionList;
sectionList = sect;
}
return sect;
@@ -377,8 +403,8 @@ static void changeSection(void)
/*
* Set the current section by name and type
*/
void out_NewSection(char const *name, uint32_t type, uint32_t org,
struct SectionSpec const *attribs, enum SectionModifier mod)
void sect_NewSection(char const *name, uint32_t type, uint32_t org,
struct SectionSpec const *attribs, enum SectionModifier mod)
{
if (currentLoadSection)
fatalerror("Cannot change the section within a `LOAD` block\n");
@@ -392,23 +418,32 @@ void out_NewSection(char const *name, uint32_t type, uint32_t org,
changeSection();
curOffset = mod == SECTION_UNION ? 0 : sect->size;
pCurrentSection = sect;
currentSection = sect;
}
/*
* Set the current section by name and type
*/
void out_SetLoadSection(char const *name, uint32_t type, uint32_t org,
struct SectionSpec const *attribs,
enum SectionModifier mod)
void sect_SetLoadSection(char const *name, uint32_t type, uint32_t org,
struct SectionSpec const *attribs, enum SectionModifier mod)
{
checkcodesection();
if (!checkcodesection())
return;
if (currentLoadSection)
fatalerror("`LOAD` blocks cannot be nested\n");
if (currentLoadSection) {
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");
return;
}
if (mod == SECTION_FRAGMENT) {
error("`LOAD FRAGMENT` is not allowed\n");
return;
}
struct Section *sect = getSection(name, type, org, attribs, mod);
@@ -418,10 +453,12 @@ void out_SetLoadSection(char const *name, uint32_t type, uint32_t org,
currentLoadSection = sect;
}
void out_EndLoadSection(void)
void sect_EndLoadSection(void)
{
if (!currentLoadSection)
if (!currentLoadSection) {
error("Found `ENDL` outside of a `LOAD` block\n");
return;
}
changeSection();
curOffset += loadOffset;
@@ -431,7 +468,7 @@ void out_EndLoadSection(void)
struct Section *sect_GetSymbolSection(void)
{
return currentLoadSection ? currentLoadSection : pCurrentSection;
return currentLoadSection ? currentLoadSection : currentSection;
}
/*
@@ -449,11 +486,13 @@ uint32_t sect_GetOutputOffset(void)
void sect_AlignPC(uint8_t alignment, uint16_t offset)
{
checksection();
if (!checksection())
return;
struct Section *sect = sect_GetSymbolSection();
uint16_t alignSize = 1 << alignment; // Size of an aligned "block"
if (sect->org != -1) {
if (sect->org != (uint32_t)-1) {
if ((sym_GetPCValue() - offset) % alignSize)
error("Section's fixed address fails required alignment (PC = $%04" PRIx32
")\n", sym_GetPCValue());
@@ -472,28 +511,28 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset)
}
}
static inline void growSection(uint32_t growth)
static void growSection(uint32_t growth)
{
curOffset += growth;
if (curOffset + loadOffset > pCurrentSection->size)
pCurrentSection->size = curOffset + loadOffset;
if (curOffset + loadOffset > currentSection->size)
currentSection->size = curOffset + loadOffset;
if (currentLoadSection && curOffset > currentLoadSection->size)
currentLoadSection->size = curOffset;
}
static inline void writebyte(uint8_t byte)
static void writebyte(uint8_t byte)
{
pCurrentSection->data[sect_GetOutputOffset()] = byte;
currentSection->data[sect_GetOutputOffset()] = byte;
growSection(1);
}
static inline void writeword(uint16_t b)
static void writeword(uint16_t b)
{
writebyte(b & 0xFF);
writebyte(b >> 8);
}
static inline void writelong(uint32_t b)
static void writelong(uint32_t b)
{
writebyte(b & 0xFF);
writebyte(b >> 8);
@@ -501,18 +540,21 @@ static inline void writelong(uint32_t b)
writebyte(b >> 24);
}
static inline void createPatch(enum PatchType type, struct Expression const *expr,
uint32_t pcShift)
static void createPatch(enum PatchType type, struct Expression const *expr, uint32_t pcShift)
{
out_CreatePatch(type, expr, sect_GetOutputOffset(), pcShift);
}
void sect_StartUnion(void)
{
if (!pCurrentSection)
fatalerror("UNIONs must be inside a SECTION\n");
if (sect_HasData(pCurrentSection->type))
fatalerror("Cannot use UNION inside of ROM0 or ROMX sections\n");
if (!currentSection) {
error("UNIONs must be inside a SECTION\n");
return;
}
if (sect_HasData(currentSection->type)) {
error("Cannot use UNION inside of ROM0 or ROMX sections\n");
return;
}
struct UnionStackEntry *entry = malloc(sizeof(*entry));
if (!entry)
@@ -534,15 +576,19 @@ static void endUnionMember(void)
void sect_NextUnionMember(void)
{
if (!unionStack)
fatalerror("Found NEXTU outside of a UNION construct\n");
if (!unionStack) {
error("Found NEXTU outside of a UNION construct\n");
return;
}
endUnionMember();
}
void sect_EndUnion(void)
{
if (!unionStack)
fatalerror("Found ENDU outside of a UNION construct\n");
if (!unionStack) {
error("Found ENDU outside of a UNION construct\n");
return;
}
endUnionMember();
curOffset += unionStack->size;
struct UnionStackEntry *next = unionStack->next;
@@ -560,36 +606,44 @@ void sect_CheckUnionClosed(void)
/*
* Output an absolute byte
*/
void out_AbsByte(uint8_t b)
void sect_AbsByte(uint8_t b)
{
checkcodesection();
reserveSpace(1);
if (!checkcodesection())
return;
if (!reserveSpace(1))
return;
writebyte(b);
}
void out_AbsByteGroup(uint8_t const *s, int32_t length)
void sect_AbsByteGroup(uint8_t const *s, int32_t length)
{
checkcodesection();
reserveSpace(length);
if (!checkcodesection())
return;
if (!reserveSpace(length))
return;
while (length--)
writebyte(*s++);
}
void out_AbsWordGroup(uint8_t const *s, int32_t length)
void sect_AbsWordGroup(uint8_t const *s, int32_t length)
{
checkcodesection();
reserveSpace(length * 2);
if (!checkcodesection())
return;
if (!reserveSpace(length * 2))
return;
while (length--)
writeword(*s++);
}
void out_AbsLongGroup(uint8_t const *s, int32_t length)
void sect_AbsLongGroup(uint8_t const *s, int32_t length)
{
checkcodesection();
reserveSpace(length * 4);
if (!checkcodesection())
return;
if (!reserveSpace(length * 4))
return;
while (length--)
writelong(*s++);
@@ -598,19 +652,20 @@ void out_AbsLongGroup(uint8_t const *s, int32_t length)
/*
* Skip this many bytes
*/
void out_Skip(int32_t skip, bool ds)
void sect_Skip(int32_t skip, bool ds)
{
checksection();
reserveSpace(skip);
if (!checksection())
return;
if (!reserveSpace(skip))
return;
if (!ds && sect_HasData(pCurrentSection->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)) {
if (!sect_HasData(currentSection->type)) {
growSection(skip);
} 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--)
writebyte(fillByte);
}
@@ -619,10 +674,12 @@ void out_Skip(int32_t skip, bool ds)
/*
* Output a NULL terminated string (excluding the NULL-character)
*/
void out_String(char const *s)
void sect_String(char const *s)
{
checkcodesection();
reserveSpace(strlen(s));
if (!checkcodesection())
return;
if (!reserveSpace(strlen(s)))
return;
while (*s)
writebyte(*s++);
@@ -632,16 +689,18 @@ void out_String(char const *s)
* Output a relocatable byte. Checking will be done to see if it
* 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();
reserveSpace(1);
if (!checkcodesection())
return;
if (!reserveSpace(1))
return;
if (!rpn_isKnown(expr)) {
createPatch(PATCHTYPE_BYTE, expr, pcShift);
writebyte(0);
} else {
writebyte(expr->nVal);
writebyte(expr->val);
}
rpn_Free(expr);
}
@@ -650,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
* 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();
reserveSpace(n);
if (!checkcodesection())
return;
if (!reserveSpace(n))
return;
for (uint32_t i = 0; i < n; i++) {
struct Expression *expr = &exprs[i % size];
@@ -662,7 +723,7 @@ void out_RelBytes(uint32_t n, struct Expression *exprs, size_t size)
createPatch(PATCHTYPE_BYTE, expr, i);
writebyte(0);
} else {
writebyte(expr->nVal);
writebyte(expr->val);
}
}
@@ -674,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
* 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();
reserveSpace(2);
if (!checkcodesection())
return;
if (!reserveSpace(2))
return;
if (!rpn_isKnown(expr)) {
createPatch(PATCHTYPE_WORD, expr, pcShift);
writeword(0);
} else {
writeword(expr->nVal);
writeword(expr->val);
}
rpn_Free(expr);
}
@@ -692,16 +755,18 @@ void out_RelWord(struct Expression *expr, uint32_t pcShift)
* Output a relocatable longword. Checking will be done to see if
* 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();
reserveSpace(2);
if (!checkcodesection())
return;
if (!reserveSpace(2))
return;
if (!rpn_isKnown(expr)) {
createPatch(PATCHTYPE_LONG, expr, pcShift);
writelong(0);
} else {
writelong(expr->nVal);
writelong(expr->val);
}
rpn_Free(expr);
}
@@ -710,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
* 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();
reserveSpace(1);
if (!checkcodesection())
return;
if (!reserveSpace(1))
return;
struct Symbol const *pc = sym_GetPC();
if (!rpn_IsDiffConstant(expr, pc)) {
@@ -744,12 +811,14 @@ void out_PCRelByte(struct Expression *expr, uint32_t pcShift)
/*
* 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) {
error("Start position cannot be negative (%" PRId32 ")\n", startPos);
startPos = 0;
}
if (!checkcodesection())
return;
char *fullPath = NULL;
size_t size = 0;
@@ -760,8 +829,10 @@ void out_BinaryFile(char const *s, int32_t startPos)
free(fullPath);
if (!f) {
if (oGeneratedMissingIncludes) {
oFailedOnMissingInclude = true;
if (generatedMissingIncludes) {
if (verbose)
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", s, strerror(errno));
failedOnMissingInclude = true;
return;
}
error("Error opening INCBIN file '%s': %s\n", s, strerror(errno));
@@ -771,22 +842,21 @@ void out_BinaryFile(char const *s, int32_t startPos)
int32_t fsize = -1;
int byte;
checkcodesection();
if (fseek(f, 0, SEEK_END) != -1) {
fsize = ftell(f);
if (startPos > fsize) {
error("Specified start position is greater than length of file\n");
fclose(f);
return;
goto cleanup;
}
fseek(f, startPos, SEEK_SET);
reserveSpace(fsize - startPos);
if (!reserveSpace(fsize - startPos))
goto cleanup;
} else {
if (errno != ESPIPE)
error("Error determining size of INCBIN file '%s': %s\n",
s, strerror(errno));
s, strerror(errno));
/* The file isn't seekable, so we'll just skip bytes */
while (startPos--)
(void)fgetc(f);
@@ -801,10 +871,11 @@ void out_BinaryFile(char const *s, int32_t startPos)
if (ferror(f))
error("Error reading INCBIN file '%s': %s\n", s, strerror(errno));
cleanup:
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) {
error("Start position cannot be negative (%" PRId32 ")\n", start_pos);
@@ -815,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);
length = 0;
}
if (!checkcodesection())
return;
if (length == 0) /* Don't even bother with 0-byte slices */
return;
if (!reserveSpace(length))
return;
char *fullPath = NULL;
size_t size = 0;
@@ -824,20 +900,19 @@ void out_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
if (fstk_FindFile(s, &fullPath, &size))
f = fopen(fullPath, "rb");
free(fullPath);
if (!f) {
free(fullPath);
if (oGeneratedMissingIncludes) {
oFailedOnMissingInclude = true;
return;
if (generatedMissingIncludes) {
if (verbose)
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", s, strerror(errno));
failedOnMissingInclude = true;
} else {
error("Error opening INCBIN file '%s': %s\n", s, strerror(errno));
}
error("Error opening INCBIN file '%s': %s\n", s, strerror(errno));
return;
}
checkcodesection();
reserveSpace(length);
int32_t fsize;
if (fseek(f, 0, SEEK_END) != -1) {
@@ -845,13 +920,13 @@ void out_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
if (start_pos > fsize) {
error("Specified start position is greater than length of file\n");
return;
goto cleanup;
}
if ((start_pos + length) > fsize) {
error("Specified range in INCBIN is out of bounds (%" PRIu32 " + %" PRIu32
" > %" PRIu32 ")\n", start_pos, length, fsize);
return;
goto cleanup;
}
fseek(f, start_pos, SEEK_SET);
@@ -879,28 +954,34 @@ void out_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
}
}
cleanup:
fclose(f);
free(fullPath);
}
/*
* Section stack routines
*/
void out_PushSection(void)
void sect_PushSection(void)
{
struct SectionStackEntry *sect = malloc(sizeof(*sect));
if (sect == NULL)
fatalerror("No memory for section stack: %s\n", strerror(errno));
sect->section = pCurrentSection;
sect->section = currentSection;
sect->loadSection = currentLoadSection;
sect->scope = sym_GetCurrentSymbolScope();
sect->offset = curOffset;
sect->loadOffset = loadOffset;
sect->next = sectionStack;
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)
fatalerror("No entries in the section stack\n");
@@ -912,9 +993,11 @@ void out_PopSection(void)
sect = sectionStack;
changeSection();
pCurrentSection = sect->section;
currentSection = sect->section;
currentLoadSection = sect->loadSection;
sym_SetCurrentSymbolScope(sect->scope);
curOffset = sect->offset;
loadOffset = sect->loadOffset;
sectionStack = sect->next;
free(sect);

View File

@@ -43,12 +43,6 @@ static char savedTIME[256];
static char savedDATE[256];
static char savedTIMESTAMP_ISO8601_LOCAL[256];
static char savedTIMESTAMP_ISO8601_UTC[256];
static char savedDAY[3];
static char savedMONTH[3];
static char savedYEAR[20];
static char savedHOUR[3];
static char savedMINUTE[3];
static char savedSECOND[3];
static bool exportall;
bool sym_IsPC(struct Symbol const *sym)
@@ -57,16 +51,16 @@ bool sym_IsPC(struct Symbol const *sym)
}
struct ForEachArgs {
void (*func)(struct Symbol *symbol, void *arg);
void (*func)(struct Symbol *sym, void *arg);
void *arg;
};
static void forEachWrapper(void *_symbol, void *_argWrapper)
static void forEachWrapper(void *_sym, void *_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)
@@ -102,8 +96,6 @@ static char const *Callback__FILE__(void)
char const *fileName = fstk_GetFileName();
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 */
for (size_t i = 0; fileName[i]; i++, j++) {
/* Account for the extra backslash inserted below */
@@ -179,34 +171,34 @@ static void updateSymbolFilename(struct Symbol *sym)
setSymbolFilename(sym);
/* If the old node was referenced, ensure the new one is */
if (oldSrc && oldSrc->referenced && oldSrc->ID != -1)
if (oldSrc && oldSrc->referenced && oldSrc->ID != (uint32_t)-1)
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
*/
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)
fatalerror("Failed to create symbol '%s': %s\n", s, strerror(errno));
if (!sym)
fatalerror("Failed to create symbol '%s': %s\n", symName, strerror(errno));
if (snprintf(symbol->name, MAXSYMLEN + 1, "%s", s) > MAXSYMLEN)
warning(WARNING_LONG_STR, "Symbol name is too long: '%s'\n", s);
if (snprintf(sym->name, MAXSYMLEN + 1, "%s", symName) > MAXSYMLEN)
warning(WARNING_LONG_STR, "Symbol name is too long: '%s'\n", symName);
symbol->isExported = false;
symbol->isBuiltin = false;
symbol->hasCallback = false;
symbol->section = NULL;
setSymbolFilename(symbol);
symbol->ID = -1;
symbol->next = NULL;
sym->isExported = false;
sym->isBuiltin = false;
sym->hasCallback = false;
sym->section = NULL;
setSymbolFilename(sym);
sym->ID = -1;
sym->next = NULL;
hash_AddElement(symbols, symbol->name, symbol);
return symbol;
hash_AddElement(symbols, sym->name, sym);
return sym;
}
/*
@@ -228,46 +220,45 @@ static void assignStringSymbol(struct Symbol *sym, char const *value)
{
char *string = strdup(value);
if (string == NULL)
if (!string)
fatalerror("No memory for string equate: %s\n", strerror(errno));
sym->type = SYM_EQUS;
/* TODO: use other fields */
sym->macro = string;
sym->macroSize = strlen(string);
}
struct Symbol *sym_FindExactSymbol(char const *name)
struct Symbol *sym_FindExactSymbol(char const *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, '.')) {
error("Expected non-scoped symbol name, not \"%s\"\n", name);
if (strchr(symName, '.')) {
error("Expected non-scoped symbol name, not \"%s\"\n", symName);
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 (strchr(dotPtr + 1, '.'))
fatalerror("'%s' is a nonsensical reference to a nested local symbol\n",
name);
symName);
/* 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];
fullSymbolName(fullname, sizeof(fullname), name, labelScope);
fullSymbolName(fullname, sizeof(fullname), symName, labelScope);
return sym_FindExactSymbol(fullname);
}
}
return sym_FindExactSymbol(name);
return sym_FindExactSymbol(symName);
}
struct Symbol const *sym_GetPC(void)
@@ -275,9 +266,9 @@ struct Symbol const *sym_GetPC(void)
return PCSymbol;
}
static inline bool isReferenced(struct Symbol const *sym)
static bool isReferenced(struct Symbol const *sym)
{
return sym->ID != -1;
return sym->ID != (uint32_t)-1;
}
/*
@@ -285,27 +276,26 @@ static inline bool isReferenced(struct Symbol const *sym)
*/
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);
} else if (symbol->isBuiltin) {
} else if (sym->isBuiltin) {
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);
} else {
/* Do not keep a reference to the label's name after purging it */
if (symbol->name == labelScope)
labelScope = NULL;
if (sym->name == labelScope)
sym_SetCurrentSymbolScope(NULL);
/*
* FIXME: this leaks symbol->macro for SYM_EQUS and SYM_MACRO, but this can't
* free(symbol->macro) because the expansion may be purging itself.
* FIXME: this leaks sym->macro for SYM_EQUS and SYM_MACRO, but this can't
* free(sym->macro) because the expansion may be purging itself.
*/
hash_RemoveElement(symbols, symbol->name);
hash_RemoveElement(symbols, sym->name);
/* TODO: ideally, also unref the file stack nodes */
free(symbol);
free(sym);
}
}
@@ -315,7 +305,7 @@ uint32_t sym_GetPCValue(void)
if (!sect)
error("PC has no value outside a section\n");
else if (sect->org == -1)
else if (sect->org == (uint32_t)-1)
error("Expected constant PC but section is not fixed\n");
else
return CallbackPC();
@@ -340,12 +330,12 @@ uint32_t sym_GetConstantSymValue(struct Symbol const *sym)
/*
* 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)
error("'%s' not defined\n", s);
if (!sym)
error("'%s' not defined\n", symName);
else
return sym_GetConstantSymValue(sym);
@@ -366,28 +356,29 @@ void sym_SetCurrentSymbolScope(char const *newScope)
* Create a symbol that will be non-relocatable and ensure that it
* hasn't already been defined or referenced in a context that would
* 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
*/
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) {
symbol = createsymbol(symbolName);
} else if (sym_IsDefined(symbol)) {
error("'%s' already defined at ", symbolName);
dumpFilename(symbol);
if (!sym) {
sym = createsymbol(symName);
} else if (sym_IsDefined(sym)) {
error("'%s' already defined at ", symName);
dumpFilename(sym);
putc('\n', stderr);
return NULL; // Don't allow overriding the symbol, that'd be bad!
} else if (!numeric) {
// The symbol has already been referenced, but it's not allowed
error("'%s' already referenced at ", symbolName);
dumpFilename(symbol);
error("'%s' already referenced at ", symName);
dumpFilename(sym);
putc('\n', stderr);
return NULL; // Don't allow overriding the symbol, that'd be bad!
}
return symbol;
return sym;
}
/*
@@ -406,6 +397,30 @@ struct Symbol *sym_AddEqu(char const *symName, int32_t value)
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.
*
@@ -433,32 +448,40 @@ struct Symbol *sym_RedefString(char const *symName, char const *value)
{
struct Symbol *sym = sym_FindExactSymbol(symName);
if (!sym) {
sym = createsymbol(symName);
} else if (sym->type != SYM_EQUS) {
error("'%s' already defined as non-EQUS at ", symName);
if (!sym)
return sym_AddString(symName, value);
if (sym->type != SYM_EQUS) {
if (sym_IsDefined(sym))
error("'%s' already defined as non-EQUS at ", symName);
else
error("'%s' already referenced 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);
/*
* FIXME: this leaks the previous sym->macro value, but this can't
* free(sym->macro) because the expansion may be redefining itself.
*/
assignStringSymbol(sym, value);
return sym;
}
/*
* Alter a SET symbols value
* Alter a SET symbol's value
*/
struct Symbol *sym_AddSet(char const *symName, int32_t value)
{
struct Symbol *sym = sym_FindExactSymbol(symName);
if (sym == NULL) {
if (!sym) {
sym = createsymbol(symName);
} else if (sym_IsDefined(sym) && sym->type != SYM_SET) {
error("'%s' already defined as %s at ",
@@ -478,18 +501,18 @@ struct Symbol *sym_AddSet(char const *symName, int32_t value)
/*
* 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
*/
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 */
struct Symbol *sym = sym_FindExactSymbol(name);
assert(symName[0] != '.'); /* The symbol name must have been expanded prior */
struct Symbol *sym = sym_FindExactSymbol(symName);
if (!sym) {
sym = createsymbol(name);
sym = createsymbol(symName);
} else if (sym_IsDefined(sym)) {
error("'%s' already defined at ", name);
error("'%s' already defined at ", symName);
dumpFilename(sym);
putc('\n', stderr);
return NULL;
@@ -504,62 +527,62 @@ static struct Symbol *addLabel(char const *name)
sym->section = sect_GetSymbolSection();
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;
}
/*
* 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) {
error("Local label '%s' in main scope\n", name);
error("Local label '%s' in main scope\n", symName);
return NULL;
}
char fullname[MAXSYMLEN + 1];
if (name[0] == '.') {
if (symName[0] == '.') {
/* If symbol is of the form `.name`, expand to the full `Parent.name` name */
fullSymbolName(fullname, sizeof(fullname), name, labelScope);
name = fullname; /* Use the expanded name instead */
fullSymbolName(fullname, sizeof(fullname), symName, labelScope);
symName = fullname; /* Use the expanded name instead */
} else {
size_t i = 0;
/* 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++;
/* Assuming no dots in `labelScope` */
assert(strchr(&name[i], '.')); /* There should be at least one dot, though */
size_t parentLen = i + (strchr(&name[i], '.') - name);
assert(strchr(&symName[i], '.')); /* There should be at least one dot, though */
size_t parentLen = i + (strchr(&symName[i], '.') - symName);
/*
* Check that `labelScope[i]` ended the check, guaranteeing that `name` is at least
* as long, and then that this was the entirety of the `Parent` part of `name`.
* Check that `labelScope[i]` ended the check, guaranteeing that `symName` is at
* 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);
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",
name);
symName);
}
return addLabel(name);
return addLabel(symName);
}
/*
* 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 */
if (sym)
labelScope = sym->name;
sym_SetCurrentSymbolScope(sym->name);
return sym;
}
@@ -654,9 +677,9 @@ struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body,
*/
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];
if (symName[0] == '.') {
@@ -666,11 +689,11 @@ struct Symbol *sym_Ref(char const *symName)
symName = fullname;
}
nsym = createsymbol(symName);
nsym->type = SYM_REF;
sym = createsymbol(symName);
sym->type = SYM_REF;
}
return nsym;
return sym;
}
/*
@@ -681,20 +704,9 @@ void sym_SetExportAll(bool set)
exportall = set;
}
/**
* Returns a pointer to the first non-zero character in a string
* Non-'0', not non-'\0'.
*/
static inline char const *removeLeadingZeros(char const *ptr)
static struct Symbol *createBuiltinSymbol(char const *symName)
{
while (*ptr == '0')
ptr++;
return ptr;
}
static inline struct Symbol *createBuiltinSymbol(char const *name)
{
struct Symbol *sym = createsymbol(name);
struct Symbol *sym = createsymbol(symName);
sym->isBuiltin = true;
sym->hasCallback = true;
@@ -722,13 +734,18 @@ void sym_Init(time_t now)
__LINE__Symbol->numCallback = Callback__LINE__;
__FILE__Symbol->type = SYM_EQUS;
__FILE__Symbol->strCallback = Callback__FILE__;
sym_AddSet("_RS", 0)->isBuiltin = true;
sym_AddEqu("__RGBDS_MAJOR__", PACKAGE_VERSION_MAJOR)->isBuiltin = true;
sym_AddEqu("__RGBDS_MINOR__", PACKAGE_VERSION_MINOR)->isBuiltin = true;
sym_AddEqu("__RGBDS_PATCH__", PACKAGE_VERSION_PATCH)->isBuiltin = true;
#define addNumber(name, val) sym_AddEqu(name, val)->isBuiltin = true
#define addString(name, val) sym_AddString(name, val)->isBuiltin = true
addString("__RGBDS_VERSION__", get_package_version_string());
addNumber("__RGBDS_MAJOR__", PACKAGE_VERSION_MAJOR);
addNumber("__RGBDS_MINOR__", PACKAGE_VERSION_MINOR);
addNumber("__RGBDS_PATCH__", PACKAGE_VERSION_PATCH);
#ifdef PACKAGE_VERSION_RC
sym_AddEqu("__RGBDS_RC__", PACKAGE_VERSION_RC)->isBuiltin = true;
addNumber("__RGBDS_RC__", PACKAGE_VERSION_RC);
#endif
if (now == (time_t)-1) {
@@ -750,28 +767,22 @@ void sym_Init(time_t now)
sizeof(savedTIMESTAMP_ISO8601_UTC), "\"%Y-%m-%dT%H:%M:%SZ\"",
time_utc);
strftime(savedYEAR, sizeof(savedYEAR), "%Y", time_utc);
strftime(savedMONTH, sizeof(savedMONTH), "%m", time_utc);
strftime(savedDAY, sizeof(savedDAY), "%d", time_utc);
strftime(savedHOUR, sizeof(savedHOUR), "%H", time_utc);
strftime(savedMINUTE, sizeof(savedMINUTE), "%M", time_utc);
strftime(savedSECOND, sizeof(savedSECOND), "%S", time_utc);
#define addString(name, val) sym_AddString(name, val)->isBuiltin = true
addString("__TIME__", savedTIME);
addString("__DATE__", savedDATE);
addString("__ISO_8601_LOCAL__", savedTIMESTAMP_ISO8601_LOCAL);
addString("__ISO_8601_UTC__", savedTIMESTAMP_ISO8601_UTC);
/* This cannot start with zeros */
addString("__UTC_YEAR__", savedYEAR);
addString("__UTC_MONTH__", removeLeadingZeros(savedMONTH));
addString("__UTC_DAY__", removeLeadingZeros(savedDAY));
addString("__UTC_HOUR__", removeLeadingZeros(savedHOUR));
addString("__UTC_MINUTE__", removeLeadingZeros(savedMINUTE));
addString("__UTC_SECOND__", removeLeadingZeros(savedSECOND));
addNumber("__UTC_YEAR__", time_utc->tm_year + 1900);
addNumber("__UTC_MONTH__", time_utc->tm_mon + 1);
addNumber("__UTC_DAY__", time_utc->tm_mday);
addNumber("__UTC_HOUR__", time_utc->tm_hour);
addNumber("__UTC_MINUTE__", time_utc->tm_min);
addNumber("__UTC_SECOND__", time_utc->tm_sec);
#undef addNumber
#undef addString
labelScope = NULL;
sym_SetCurrentSymbolScope(NULL);
anonLabelID = 0;
/* _PI is deprecated */

View File

@@ -15,52 +15,45 @@
#include "extern/utf8decoder.h"
/*
* Calculate the hash value for a string.
* Uses the djb2 algorithm (xor version).
* http://www.cse.yorku.ca/~oz/hash.html
*/
uint32_t calchash(const char *s)
char const *printChar(int c)
{
uint32_t hash = 5381;
while (*s != 0)
hash = (hash * 33) ^ (*s++);
return hash;
}
char const *print(int c)
{
static char buf[5]; /* '\xNN' + '\0' */
// "'A'" + '\0': 4 bytes
// "'\\n'" + '\0': 5 bytes
// "0xFF" + '\0': 5 bytes
static char buf[5];
if (c == EOF)
return "EOF";
if (isprint(c)) {
buf[0] = c;
buf[1] = '\0';
buf[0] = '\'';
buf[1] = c;
buf[2] = '\'';
buf[3] = '\0';
return buf;
}
buf[0] = '\\';
switch (c) {
case '\n':
buf[1] = 'n';
buf[2] = 'n';
break;
case '\r':
buf[1] = 'r';
buf[2] = 'r';
break;
case '\t':
buf[1] = 't';
buf[2] = 't';
break;
default: /* Print as hex */
buf[0] = '0';
buf[1] = 'x';
sprintf(&buf[2], "%02hhx", c);
snprintf(&buf[2], 3, "%02hhX", (uint8_t)c); // includes the '\0'
return buf;
}
buf[2] = '\0';
buf[0] = '\'';
buf[1] = '\\';
buf[3] = '\'';
buf[4] = '\0';
return buf;
}
@@ -74,7 +67,8 @@ size_t readUTF8Char(uint8_t *dest, char const *src)
if (decode(&state, &codep, src[i]) == 1)
return 0;
dest[i] = src[i];
if (dest)
dest[i] = src[i];
i++;
if (state == 0)

View File

@@ -21,15 +21,9 @@
unsigned int nbErrors = 0;
enum WarningState {
WARNING_DEFAULT,
WARNING_DISABLED,
WARNING_ENABLED,
WARNING_ERROR
};
static enum WarningState const defaultWarnings[NB_WARNINGS] = {
[WARNING_ASSERT] = WARNING_ENABLED,
[WARNING_BACKWARDS_FOR] = WARNING_DISABLED,
[WARNING_BUILTIN_ARG] = WARNING_DISABLED,
[WARNING_CHARMAP_REDEF] = WARNING_DISABLED,
[WARNING_DIV] = WARNING_DISABLED,
@@ -47,9 +41,9 @@ static enum WarningState const defaultWarnings[NB_WARNINGS] = {
[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)
{
@@ -72,6 +66,7 @@ static enum WarningState warningState(enum WarningID id)
static char const *warningFlags[NB_WARNINGS_ALL] = {
"assert",
"backwards-for",
"builtin-args",
"charmap-redef",
"div",
@@ -100,6 +95,7 @@ enum MetaWarningCommand {
/* Warnings that probably indicate an error */
static uint8_t const _wallCommands[] = {
WARNING_BACKWARDS_FOR,
WARNING_BUILTIN_ARG,
WARNING_CHARMAP_REDEF,
WARNING_EMPTY_DATA_DIRECTIVE,
@@ -119,6 +115,7 @@ static uint8_t const _wextraCommands[] = {
/* Literally everything. Notably useful for testing */
static uint8_t const _weverythingCommands[] = {
WARNING_BACKWARDS_FOR,
WARNING_BUILTIN_ARG,
WARNING_DIV,
WARNING_EMPTY_DATA_DIRECTIVE,
@@ -155,8 +152,7 @@ void processWarningFlag(char const *flag)
errx(1, "Cannot make meta warning \"%s\" into an error",
flag);
uint8_t const *ptr =
metaWarningCommands[id - NB_WARNINGS];
uint8_t const *ptr = metaWarningCommands[id - NB_WARNINGS];
for (;;) {
if (*ptr == META_WARNING_DONE)
@@ -197,7 +193,7 @@ void processWarningFlag(char const *flag)
bool isNegation = !strncmp(flag, "no-", strlen("no-")) && !setError;
char const *rootFlag = isNegation ? flag + strlen("no-") : flag;
enum WarningState state = setError ? WARNING_ERROR :
isNegation ? WARNING_DISABLED : WARNING_ENABLED;
isNegation ? WARNING_DISABLED : WARNING_ENABLED;
/* Try to match the flag against a "normal" flag */
for (enum WarningID id = 0; id < NB_WARNINGS; id++) {

View File

@@ -25,7 +25,7 @@
#include "platform.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
@@ -79,6 +79,20 @@ static void printUsage(void)
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 {
ROM = 0x00,
ROM_RAM = 0x08,
@@ -120,6 +134,28 @@ enum MbcType {
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
MBC_NONE = UNSPECIFIED, // No MBC specified, do not act on it
MBC_BAD, // Specified MBC does not exist / syntax error
@@ -127,6 +163,33 @@ enum MbcType {
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
*/
@@ -151,10 +214,22 @@ static bool readMBCSlice(char const **name, char const *expected)
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)
char *endptr;
unsigned long mbc = strtoul(name, &endptr, 0);
unsigned long mbc = strtoul(name, &endptr, base);
if (*endptr)
return MBC_BAD;
@@ -248,10 +323,50 @@ do { \
mbc = BANDAI_TAMA5;
break;
case 'T': // TAMA5
case 'T': // TAMA5 / TPP1
case 't':
tryReadSlice("AMA5");
mbc = BANDAI_TAMA5;
switch (*ptr++) {
case 'A':
tryReadSlice("MA5");
mbc = BANDAI_TAMA5;
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}
@@ -280,6 +395,7 @@ do { \
#define TIMER 0x20
#define RUMBLE 0x10
#define SENSOR 0x08
#define MULTIRUMBLE 0x04
for (;;) {
// Trim off trailing whitespace
@@ -303,6 +419,12 @@ do { \
features |= BATTERY;
break;
case 'M':
case 'm':
tryReadSlice("ULTIRUMBLE");
features |= MULTIRUMBLE;
break;
case 'R': // RAM or RUMBLE
case 'r':
switch (*ptr++) {
@@ -424,6 +546,22 @@ do { \
if (features != (RAM | BATTERY)) // HuC1 expects RAM+BATTERY
return MBC_WRONG_FEATURES;
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
@@ -497,6 +635,34 @@ static char const *mbcName(enum MbcType type)
return "HUC3";
case 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
case MBC_NONE:
@@ -547,25 +713,30 @@ static bool hasRAM(enum MbcType type)
case HUC3:
case HUC1_RAM_BATTERY:
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_();
}
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[] = {
0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B,
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;
}
/**
* @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 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];
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) {
report("FATAL: Failed to read \"%s\"'s header: %s\n", name, strerror(errno));
return;
} else if (rom0Len < 0x150) {
report("FATAL: \"%s\" too short, expected at least 336 ($150) bytes, got only %ld\n",
name, rom0Len);
} else if (rom0Len < headerSize) {
report("FATAL: \"%s\" too short, expected at least %jd ($%jx) bytes, got only %jd\n",
name, (intmax_t)headerSize, (intmax_t)headerSize, (intmax_t)rom0Len);
return;
}
// Accept partial reads if the file contains at least the header
if (fixSpec & (FIX_LOGO | TRASH_LOGO)) {
warnNonZero(rom0, 0x0104, sizeof(ninLogo), "Nintendo logo");
if (fixSpec & FIX_LOGO) {
memcpy(&rom0[0x104], ninLogo, sizeof(ninLogo));
} 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);
}
if (gameID)
if (gameID) {
warnNonZero(rom0, 0x13f, gameIDLen, "manufacturer code");
memcpy(&rom0[0x13f], gameID, gameIDLen);
}
if (model != DMG)
if (model != DMG) {
warnNonZero(rom0, 0x143, 1, "CGB flag");
rom0[0x143] = model == BOTH ? 0x80 : 0xc0;
}
if (newLicensee)
if (newLicensee) {
warnNonZero(rom0, 0x144, newLicenseeLen, "new licensee code");
memcpy(&rom0[0x144], newLicensee, newLicenseeLen);
}
if (sgb)
if (sgb) {
warnNonZero(rom0, 0x146, 1, "SGB flag");
rom0[0x146] = 0x03;
}
// If a valid MBC was specified...
if (cartridgeType < MBC_NONE)
rom0[0x147] = cartridgeType;
if (cartridgeType < MBC_NONE) {
warnNonZero(rom0, 0x147, 1, "cartridge type");
uint8_t byte = cartridgeType;
if (ramSize != UNSPECIFIED)
rom0[0x149] = ramSize;
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;
}
if (!japanese)
rom0[0x14a] = 0x01;
// ROM size will be written last, after evaluating the file's size
if (oldLicensee != UNSPECIFIED)
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;
}
if (!japanese) {
warnNonZero(rom0, 0x14a, 1, "destination code");
rom0[0x14a] = 0x01;
}
}
if (oldLicensee != UNSPECIFIED) {
warnNonZero(rom0, 0x14b, 1, "old licensee code");
rom0[0x14b] = oldLicensee;
}
if (romVersion != UNSPECIFIED)
if (romVersion != UNSPECIFIED) {
warnNonZero(rom0, 0x14c, 1, "mask ROM version number");
rom0[0x14c] = romVersion;
}
// Remain to be handled the ROM size, and header checksum.
// 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++)
sum -= rom0[i] + 1;
warnNonZero(rom0, 0x14d, 1, "header checksum");
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)
globalSum = ~globalSum;
warnNonZero(rom0, 0x14e, 2, "global checksum");
rom0[0x14e] = globalSum >> 8;
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
// However, padding may have modified ROM0 (added padding), so don't in that case
if (padValue == UNSPECIFIED)
rom0Len = 0x150;
rom0Len = headerSize;
}
ssize_t writeLen = writeBytes(output, rom0, rom0Len);
@@ -863,20 +1104,22 @@ 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));
goto free_romx;
} else if (writeLen < rom0Len) {
report("FATAL: Could only write %ld of \"%s\"'s %ld ROM0 bytes\n",
writeLen, name, rom0Len);
report("FATAL: Could only write %jd of \"%s\"'s %jd ROM0 bytes\n",
(intmax_t)writeLen, name, (intmax_t)rom0Len);
goto free_romx;
}
// Output ROMX if it was buffered
if (romx) {
// The value returned is either -1, or smaller than `totalRomxLen`,
// so it's fine to cast to `size_t`
writeLen = writeBytes(output, romx, totalRomxLen);
if (writeLen == -1) {
report("FATAL: Failed to write \"%s\"'s ROMX: %s\n", name, strerror(errno));
goto free_romx;
} else if (writeLen < totalRomxLen) {
report("FATAL: Could only write %ld of \"%s\"'s %ld ROMX bytes\n",
writeLen, name, totalRomxLen);
} else if ((size_t)writeLen < totalRomxLen) {
report("FATAL: Could only write %jd of \"%s\"'s %zu ROMX bytes\n",
(intmax_t)writeLen, name, totalRomxLen);
goto free_romx;
}
}
@@ -898,7 +1141,9 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
size_t thisLen = len > sizeof(bank) ? sizeof(bank) : len;
ssize_t ret = writeBytes(output, bank, thisLen);
if (ret != thisLen) {
// The return value is either -1, or at most `thisLen`,
// so it's fine to cast to `size_t`
if ((size_t)ret != thisLen) {
report("FATAL: Failed to write \"%s\"'s padding: %s\n",
name, strerror(errno));
break;
@@ -911,8 +1156,6 @@ free_romx:
free(romx);
}
#undef trySeek
static bool processFilename(char const *name)
{
nbErrors = 0;
@@ -945,8 +1188,8 @@ static bool processFilename(char const *name)
} else if (stat.st_size < 0x150) {
// This check is in theory redundant with the one in `processFile`, but it
// 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",
name, stat.st_size);
report("FATAL: \"%s\" too short, expected at least 336 ($150) bytes, got only %jd\n",
name, (intmax_t)stat.st_size);
} else {
processFile(input, input, name, stat.st_size);
}
@@ -963,7 +1206,7 @@ fail:
int main(int argc, char *argv[])
{
nbErrors = 0;
char ch;
int ch;
while ((ch = musl_getopt_long_only(argc, argv, optstring, longopts, NULL)) != -1) {
switch (ch) {
@@ -1011,7 +1254,7 @@ do { \
#define SPEC_H TRASH_HEADER_SUM
#define SPEC_g FIX_GLOBAL_SUM
#define SPEC_G TRASH_GLOBAL_SUM
#define or(new, bad) \
#define overrideSpec(new, bad) \
do { \
if (fixSpec & SPEC_##bad) \
fprintf(stderr, \
@@ -1019,30 +1262,30 @@ do { \
fixSpec = (fixSpec & ~SPEC_##bad) | SPEC_##new; \
} while (0)
case 'l':
or(l, L);
overrideSpec(l, L);
break;
case 'L':
or(L, l);
overrideSpec(L, l);
break;
case 'h':
or(h, H);
overrideSpec(h, H);
break;
case 'H':
or(H, h);
overrideSpec(H, h);
break;
case 'g':
or(g, G);
overrideSpec(g, G);
break;
case 'G':
or(G, g);
overrideSpec(G, g);
break;
default:
fprintf(stderr, "warning: Ignoring '%c' in fix spec\n",
*musl_optarg);
#undef or
#undef overrideSpec
}
musl_optarg++;
}
@@ -1087,10 +1330,13 @@ do { \
case 'm':
cartridgeType = parseMBC(musl_optarg);
if (cartridgeType == MBC_BAD) {
report("error: Unknown MBC \"%s\"\n", musl_optarg);
} else if (cartridgeType == MBC_WRONG_FEATURES) {
report("error: Features incompatible with MBC (\"%s\")\n",
report("error: Unknown MBC \"%s\"\nAccepted MBC names:\n",
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) {
report("error: Specified MBC ID out of range 0-255: %s\n",
musl_optarg);
@@ -1144,7 +1390,11 @@ do { \
#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 (ramSize != 1)
fprintf(stderr, "warning: MBC \"%s\" should have 2kiB of RAM (-r 1)\n",

View File

@@ -1,11 +1,11 @@
.\"
.\" This file is part of RGBDS.
.\"
.\" Copyright (c) 2010-2017, Anthony J. Bentley and RGBDS contributors.
.\" Copyright (c) 2010-2021, Anthony J. Bentley and RGBDS contributors.
.\"
.\" SPDX-License-Identifier: MIT
.\"
.Dd December 5, 2019
.Dd March 28, 2021
.Dt RGBFIX 1
.Os
.Sh NAME
@@ -112,7 +112,13 @@ This value is deprecated and should be set to 0x33 in all new software.
Set the MBC type
.Pq Ad 0x147
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
Set the ROM version
.Pq Ad 0x14C
@@ -179,6 +185,46 @@ sans global checksum:
.Pp
.D1 $ rgbfix -cjsv -k A4 -l 0x33 -m 0x1B -p 0xFF -r 3 -t SURVIVALKIDAVKE \
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
Please report bugs on
.Lk https://github.com/gbdev/rgbds/issues GitHub .

View File

@@ -1,11 +1,11 @@
.\"
.\" This file is part of RGBDS.
.\"
.\" Copyright (c) 2017-2018, Antonio Nino Diaz and RGBDS contributors.
.\" Copyright (c) 2017-2021, Antonio Nino Diaz and RGBDS contributors.
.\"
.\" SPDX-License-Identifier: MIT
.\"
.Dd February 23, 2018
.Dd March 28, 2021
.Dt GBZ80 7
.Os
.Sh NAME
@@ -879,9 +879,9 @@ Bytes: 2
Flags: None affected.
.Pp
This is sometimes written as
.Ql ldio [n16], a ,
.Ql LDIO [n16],A ,
or
.Ql ld [$ff00+n8], a .
.Ql LD [$FF00+n8],A .
.Ss LDH [C],A
Store value in register
.Sy A
@@ -895,9 +895,9 @@ Bytes: 1
Flags: None affected.
.Pp
This is sometimes written as
.Ql ldio [c], a ,
.Ql LDIO [C],A ,
or
.Ql ld [$ff00+c], a .
.Ql LD [$FF00+C],A .
.Ss LD A,[r16]
Load value in register
.Sy A
@@ -937,9 +937,9 @@ Bytes: 2
Flags: None affected.
.Pp
This is sometimes written as
.Ql ldio a, [n16] ,
.Ql LDIO A,[n16] ,
or
.Ql ld a, [$ff00+n8] .
.Ql LD A,[$FF00+n8] .
.Ss LDH A,[C]
Load value in register
.Sy A
@@ -953,9 +953,9 @@ Bytes: 1
Flags: None affected.
.Pp
This is sometimes written as
.Ql ldio a, [c] ,
.Ql LDIO A,[C] ,
or
.Ql ld a, [$ff00+c] .
.Ql LD A,[$FF00+C] .
.Ss LD [HLI],A
Store value in register
.Sy A
@@ -970,6 +970,11 @@ Cycles: 2
Bytes: 1
.Pp
Flags: None affected.
.Pp
This is sometimes written as
.Ql LD [HL+],A ,
or
.Ql LDI [HL],A .
.Ss LD [HLD],A
Store value in register
.Sy A
@@ -984,6 +989,11 @@ Cycles: 2
Bytes: 1
.Pp
Flags: None affected.
.Pp
This is sometimes written as
.Ql LD [HL-],A ,
or
.Ql LDD [HL],A .
.Ss LD A,[HLD]
Load value into register
.Sy A
@@ -998,6 +1008,11 @@ Cycles: 2
Bytes: 1
.Pp
Flags: None affected.
.Pp
This is sometimes written as
.Ql LD A,[HL-] ,
or
.Ql LDD A,[HL] .
.Ss LD A,[HLI]
Load value into register
.Sy A
@@ -1012,6 +1027,11 @@ Cycles: 2
Bytes: 1
.Pp
Flags: None affected.
.Pp
This is sometimes written as
.Ql LD A,[HL+], ,
or
.Ql LDI A,[HL] .
.Ss LD SP,n16
Load value
.Ar n16

View File

@@ -224,7 +224,11 @@ void create_mapfiles(const struct Options *opts, struct GBImage *gb,
if (!tile)
err(1, "%s: Failed to allocate memory for tile",
__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];
gb_i++;
}

View File

@@ -1,11 +1,11 @@
.\"
.\" This file is part of RGBDS.
.\"
.\" Copyright (c) 2013-2018, stag019 and RGBDS contributors.
.\" Copyright (c) 2013-2021, stag019 and RGBDS contributors.
.\"
.\" SPDX-License-Identifier: MIT
.\"
.Dd December 5, 2019
.Dd March 28, 2021
.Dt RGBGFX 1
.Os
.Sh NAME
@@ -78,7 +78,8 @@ Same as
.Fl f ,
but additionally, the supplied command line parameters are saved within the PNG and will be loaded and automatically used next time.
.It Fl h , Fl Fl horizontal
Lay out tiles horizontally rather than vertically.
Lay out tiles in column-major order (column by column), instead of the default row-major order (line by line).
Especially useful for "8x16" OBJ mode, if the input image is 16 pixels tall.
.It Fl m , Fl Fl mirror-tiles
Truncate tiles by checking for tiles that are mirrored versions of others and omitting these from the output file.
Useful with tilemaps and attrmaps together to keep track of the duplicated tiles and the dimension mirrored.

View File

@@ -46,7 +46,7 @@ static HashType hash(char const *str)
return hash;
}
bool hash_AddElement(HashMap map, char const *key, void *element)
void **hash_AddElement(HashMap map, char const *key, void *element)
{
HashType hashedKey = hash(key);
HalfHashType index = hashedKey;
@@ -61,23 +61,7 @@ bool hash_AddElement(HashMap map, char const *key, void *element)
newEntry->next = map[index];
map[index] = newEntry;
return newEntry->next != NULL;
}
bool hash_ReplaceElement(HashMap const map, char const *key, void *element)
{
HashType hashedKey = hash(key);
struct HashMapEntry *ptr = map[(HalfHashType)hashedKey];
while (ptr) {
if (hashedKey >> HALF_HASH_NB_BITS == ptr->hash
&& !strcmp(ptr->key, key)) {
ptr->content = element;
return true;
}
ptr = ptr->next;
}
return false;
return &newEntry->content;
}
bool hash_RemoveElement(HashMap map, char const *key)
@@ -99,7 +83,7 @@ bool hash_RemoveElement(HashMap map, char const *key)
return false;
}
void *hash_GetElement(HashMap const map, char const *key)
void **hash_GetNode(HashMap const map, char const *key)
{
HashType hashedKey = hash(key);
struct HashMapEntry *ptr = map[(HalfHashType)hashedKey];
@@ -107,13 +91,20 @@ void *hash_GetElement(HashMap const map, char const *key)
while (ptr) {
if (hashedKey >> HALF_HASH_NB_BITS == ptr->hash
&& !strcmp(ptr->key, key)) {
return ptr->content;
return &ptr->content;
}
ptr = ptr->next;
}
return NULL;
}
void *hash_GetElement(HashMap const map, char const *key)
{
void **node = hash_GetNode(map, key);
return node ? *node : NULL;
}
void hash_ForEach(HashMap const map, void (*func)(void *, void *), void *arg)
{
for (size_t i = 0; i < HASHMAP_NB_BUCKETS; i++) {

View File

@@ -106,12 +106,18 @@ static void processLinkerScript(void)
* @param section The section to assign
* @param location The location to assign the section to
*/
static inline void assignSection(struct Section *section,
struct MemoryLocation const *location)
static void assignSection(struct Section *section, struct MemoryLocation const *location)
{
section->org = location->address;
section->bank = location->bank;
// Propagate the assigned location to all UNIONs/FRAGMENTs
// so `jr` patches in them will have the correct offset
for (struct Section *next = section->nextu; next != NULL; next = next->nextu) {
next->org = section->org;
next->bank = section->bank;
}
nbSectionsToAssign--;
out_AddSection(section);
@@ -331,7 +337,7 @@ static void placeSection(struct Section *section)
section->name, typeNames[section->type], where);
/* If the section just can't fit the bank, report that */
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->org + section->size, endaddr(section->type) + 1);
/* Otherwise there is overlap with another section */

View File

@@ -205,7 +205,7 @@ static void cleanup(void)
int main(int argc, char *argv[])
{
int optionChar;
char *endptr; /* For error checking with `strtol` */
char *endptr; /* For error checking with `strtoul` */
unsigned long value; /* For storing `strtoul`'s return value */
/* Parse options */

View File

@@ -48,6 +48,7 @@ static struct Assertion *assertions;
do { \
FILE *tmpFile = file; \
type tmpVal = func(tmpFile); \
/* TODO: maybe mark the condition as `unlikely`; how to do that portably? */ \
if (tmpVal == (errval)) { \
errx(1, __VA_ARGS__, feof(tmpFile) \
? "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.
* 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 file The file to read from. Its position will be advanced
* @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.
* Differs from `tryGetc` in that the backing function is fgetc(1).
* 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 file The file to read from. Its position will be advanced
* @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.
* Differs from `tryGetc` in that the backing function is fgetc(1).
* 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 file The file to read from. Its position will be advanced
* @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.
* 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 file The file to read from. Its position will be advanced
* @param ... A format string and related arguments; note that an extra string
@@ -188,7 +185,7 @@ static void readFileStackNode(FILE *file, struct FileStackNode fileNodes[], uint
tryReadlong(parentID, file,
"%s: Cannot read node #%" PRIu32 "'s parent ID: %s", fileName, i);
fileNodes[i].parent = parentID == -1 ? NULL : &fileNodes[parentID];
fileNodes[i].parent = parentID == (uint32_t)-1 ? NULL : &fileNodes[parentID];
tryReadlong(fileNodes[i].lineNo, file,
"%s: Cannot read node #%" PRIu32 "'s line number: %s", fileName, i);
tryGetc(fileNodes[i].type, file, "%s: Cannot read node #%" PRIu32 "'s type: %s",
@@ -261,7 +258,7 @@ static void readSymbol(FILE *file, struct Symbol *symbol,
* @param i The number of the patch to report in errors
*/
static void readPatch(FILE *file, struct Patch *patch, char const *fileName, char const *sectName,
uint32_t i, struct Section *fileSections[], struct FileStackNode fileNodes[])
uint32_t i, struct FileStackNode fileNodes[])
{
uint32_t nodeID;
uint8_t type;
@@ -279,7 +276,6 @@ static void readPatch(FILE *file, struct Patch *patch, char const *fileName, cha
tryReadlong(patch->pcSectionID, file,
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s",
fileName, sectName, i);
patch->pcSection = patch->pcSectionID == -1 ? NULL : fileSections[patch->pcSectionID];
tryReadlong(patch->pcOffset, file,
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s",
fileName, sectName, i);
@@ -304,6 +300,16 @@ static void readPatch(FILE *file, struct Patch *patch, char const *fileName, cha
feof(file) ? "Unexpected end of file" : strerror(errno));
}
/**
* Sets a patch's pcSection from its pcSectionID.
* @param patch The struct to fix
*/
static void linkPatchToPCSect(struct Patch *patch, struct Section *fileSections[])
{
patch->pcSection = patch->pcSectionID == (uint32_t)-1 ? NULL
: fileSections[patch->pcSectionID];
}
/**
* Reads a section from a file.
* @param file The file to read from
@@ -311,7 +317,7 @@ static void readPatch(FILE *file, struct Patch *patch, char const *fileName, cha
* @param fileName The filename to report in errors
*/
static void readSection(FILE *file, struct Section *section, char const *fileName,
struct Section *fileSections[], struct FileStackNode fileNodes[])
struct FileStackNode fileNodes[])
{
int32_t tmp;
uint8_t byte;
@@ -388,12 +394,9 @@ static void readSection(FILE *file, struct Section *section, char const *fileNam
malloc(sizeof(*patches) * section->nbPatches + 1);
if (!patches)
err(1, "%s: Unable to read \"%s\"'s patches", fileName,
section->name);
for (uint32_t i = 0; i < section->nbPatches; i++) {
readPatch(file, &patches[i], fileName, section->name,
i, fileSections, fileNodes);
}
err(1, "%s: Unable to read \"%s\"'s patches", fileName, section->name);
for (uint32_t i = 0; i < section->nbPatches; i++)
readPatch(file, &patches[i], fileName, section->name, i, fileNodes);
section->patches = patches;
}
}
@@ -435,18 +438,18 @@ static void linkSymToSect(struct Symbol const *symbol, struct Section *section)
*/
static void readAssertion(FILE *file, struct Assertion *assert,
char const *fileName, uint32_t i,
struct Section *fileSections[], struct FileStackNode fileNodes[])
struct FileStackNode fileNodes[])
{
char assertName[sizeof("Assertion #4294967295")]; // UINT32_MAX
snprintf(assertName, sizeof(assertName), "Assertion #%" PRIu32, i);
readPatch(file, &assert->patch, fileName, assertName, 0, fileSections, fileNodes);
readPatch(file, &assert->patch, fileName, assertName, 0, fileNodes);
tryReadstr(assert->message, file, "%s: Cannot read assertion's message: %s",
fileName);
}
static inline struct Section *getMainSection(struct Section *section)
static struct Section *getMainSection(struct Section *section)
{
if (section->modifier != SECTION_NORMAL)
section = sect_GetSection(section->name);
@@ -549,7 +552,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
err(1, "%s: Couldn't create new section", fileName);
fileSections[i]->nextu = NULL;
readSection(file, fileSections[i], fileName, fileSections, nodes[fileID].nodes);
readSection(file, fileSections[i], fileName, nodes[fileID].nodes);
fileSections[i]->fileSymbols = fileSymbols;
if (nbSymPerSect[i]) {
fileSections[i]->symbols = malloc(nbSymPerSect[i]
@@ -567,7 +570,15 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
free(nbSymPerSect);
/* Give symbols pointers to their sections */
/* Give patches' PC section pointers to their sections */
for (uint32_t i = 0; i < nbSections; i++) {
if (sect_HasData(fileSections[i]->type)) {
for (uint32_t j = 0; j < fileSections[i]->nbPatches; j++)
linkPatchToPCSect(&fileSections[i]->patches[j], fileSections);
}
}
/* Give symbols' section pointers to their sections */
for (uint32_t i = 0; i < nbSymbols; i++) {
int32_t sectionID = fileSymbols[i]->sectionID;
@@ -599,7 +610,8 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
if (!assertion)
err(1, "%s: Couldn't create new assertion", fileName);
readAssertion(file, assertion, fileName, i, fileSections, nodes[fileID].nodes);
readAssertion(file, assertion, fileName, i, nodes[fileID].nodes);
linkPatchToPCSect(&assertion->patch, fileSections);
assertion->fileSymbols = fileSymbols;
assertion->next = assertions;
assertions = assertion;
@@ -628,19 +640,33 @@ void obj_Setup(unsigned int 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)
{
(void)arg;
free(section->name);
if (sect_HasData(section->type)) {
free(section->data);
for (int32_t i = 0; i < section->nbPatches; i++)
free(section->patches[i].rpnExpression);
free(section->patches);
}
free(section->symbols);
free(section);
do {
struct Section *next = section->nextu;
free(section->name);
if (sect_HasData(section->type)) {
free(section->data);
for (uint32_t i = 0; i < section->nbPatches; i++)
free(section->patches[i].rpnExpression);
free(section->patches);
}
free(section->symbols);
free(section);
section = next;
} while (section);
}
static void freeSymbol(struct Symbol *symbol)
@@ -653,8 +679,7 @@ void obj_Cleanup(void)
{
for (unsigned int i = 0; i < nbObjFiles; i++) {
for (uint32_t j = 0; j < nodes[i].nbNodes; j++) {
if (nodes[i].nodes[j].type == NODE_REPT)
free(nodes[i].nodes[j].iters);
freeNode(&nodes[i].nodes[j]);
}
free(nodes[i].nodes);
}
@@ -665,16 +690,12 @@ void obj_Cleanup(void)
sect_ForEach(freeSection, NULL);
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++)
freeSymbol(list->symbolList[i]);
free(list->symbolList);
struct SymbolList *next = list->next;
free(list);
list = next;
}
}

View File

@@ -6,6 +6,7 @@
* SPDX-License-Identifier: MIT
*/
#include <assert.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdlib.h>
@@ -33,6 +34,12 @@ struct SortedSection {
struct SortedSection *next;
};
struct SortedSymbol {
struct Symbol const *sym;
uint32_t idx;
uint16_t addr;
};
static struct {
uint32_t nbBanks;
struct SortedSections {
@@ -268,75 +275,92 @@ static struct SortedSection const **nextSection(struct SortedSection const **s1,
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
* @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)
return;
struct {
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;
uint32_t nbSymbols = 0;
for (;;) {
while (sectList.sections
&& sectList.i == sectList.sect->nbSymbols) {
sectList.sections = sectList.sections->next;
sectList.i = 0;
}
while (zlSectList.sections
&& zlSectList.i == zlSectList.sect->nbSymbols) {
zlSectList.sections = zlSectList.sections->next;
zlSectList.i = 0;
}
if (!sectList.sections && !zlSectList.sections) {
break;
} 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
? &sectList
: &zlSectList;
} else if (sectList.sections) {
sectList.sym = sectList.sect->symbols[sectList.i];
sectList.addr =
sectList.sym->offset + sectList.sect->org;
minSectList = &sectList;
} else {
zlSectList.sym = zlSectList.sect->symbols[zlSectList.i];
zlSectList.addr =
zlSectList.sym->offset + zlSectList.sect->org;
minSectList = &zlSectList;
}
fprintf(symFile, "%02" PRIx32 ":%04" PRIx16 " %s\n",
minSectList->sect->bank, minSectList->addr,
minSectList->sym->name);
minSectList->i++;
for (struct SortedSection const *ptr = bankSections->zeroLenSections; ptr; ptr = ptr->next) {
for (struct Section const *sect = ptr->section; sect; sect = sect->nextu)
nbSymbols += sect->nbSymbols;
}
#undef sect
for (struct SortedSection const *ptr = bankSections->sections; ptr; ptr = ptr->next) {
for (struct Section const *sect = ptr->section; sect; sect = sect->nextu)
nbSymbols += sect->nbSymbols;
}
if (!nbSymbols)
return;
struct SortedSymbol *symList = malloc(sizeof(*symList) * nbSymbols);
if (!symList)
err(1, "Failed to allocate symbol list");
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",
symBank, sym->addr, sym->sym->name);
}
free(symList);
}
/**
* Write a bank's contents to the map file
* @param bankSections The bank's sections
* @return The bank's slack space
* @return The bank's used space
*/
static uint16_t writeMapBank(struct SortedSections const *sectList,
enum SectionType type, uint32_t bank)
@@ -350,17 +374,18 @@ static uint16_t writeMapBank(struct SortedSections const *sectList,
fprintf(mapFile, "%s bank #%" PRIu32 ":\n", typeNames[type],
bank + bankranges[type][0]);
uint16_t slack = maxsize[type];
uint16_t used = 0;
while (section || zeroLenSection) {
struct SortedSection const **pickedSection =
nextSection(&section, &zeroLenSection);
struct Section const *sect = (*pickedSection)->section;
slack -= sect->size;
used += sect->size;
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->size, sect->size == 1 ? "" : "s",
sect->name);
@@ -368,44 +393,54 @@ static uint16_t writeMapBank(struct SortedSections const *sectList,
fprintf(mapFile, " SECTION: $%04" PRIx16 " (0 bytes) [\"%s\"]\n",
sect->org, sect->name);
for (size_t i = 0; i < sect->nbSymbols; i++)
fprintf(mapFile, " $%04" PRIx32 " = %s\n",
sect->symbols[i]->offset + sect->org,
sect->symbols[i]->name);
uint16_t org = sect->org;
while (sect) {
for (size_t i = 0; i < sect->nbSymbols; i++)
fprintf(mapFile, " $%04" PRIx32 " = %s\n",
sect->symbols[i]->offset + org,
sect->symbols[i]->name);
sect = sect->nextu; // Also print symbols in the following "pieces"
}
*pickedSection = (*pickedSection)->next;
}
if (slack == maxsize[type])
if (used == 0) {
fputs(" EMPTY\n\n", mapFile);
else
} else {
uint16_t slack = maxsize[type] - used;
fprintf(mapFile, " SLACK: $%04" PRIx16 " byte%s\n\n", slack,
slack == 1 ? "" : "s");
}
return slack;
return used;
}
/**
* Write the total slack space by section type to the map file
* @param slackMap The total slack space by section type
* Write the total used space by section type to the map file
* @param usedMap The total used space by section type
*/
static void writeMapSlack(uint32_t slackMap[MIN_NB_ELMS(SECTTYPE_INVALID)])
static void writeMapUsed(uint32_t usedMap[MIN_NB_ELMS(SECTTYPE_INVALID)])
{
if (!mapFile)
return;
fputs("FREE:\n", mapFile);
fputs("USED:\n", mapFile);
for (uint8_t i = 0; i < SECTTYPE_INVALID; i++) {
enum SectionType type = typeMap[i];
// Do not output slack space for VRAM or OAM
// Do not output used space for VRAM or OAM
if (type == SECTTYPE_VRAM || type == SECTTYPE_OAM)
continue;
if (sections[type].nbBanks > 0) {
fprintf(mapFile, " %s: $%04" PRIx32 " byte%s in %" PRIu32 " bank%s\n",
typeNames[type], slackMap[type], slackMap[type] == 1 ? "" : "s",
typeNames[type], usedMap[type], usedMap[type] == 1 ? "" : "s",
sections[type].nbBanks, sections[type].nbBanks == 1 ? "" : "s");
}
}
@@ -419,7 +454,7 @@ static void writeSymAndMap(void)
if (!symFileName && !mapFileName)
return;
uint32_t slackMap[SECTTYPE_INVALID] = {0};
uint32_t usedMap[SECTTYPE_INVALID] = {0};
symFile = openFile(symFileName, "w");
mapFile = openFile(mapFileName, "w");
@@ -433,12 +468,12 @@ static void writeSymAndMap(void)
for (uint32_t bank = 0; bank < sections[type].nbBanks; bank++) {
struct SortedSections const *sect = &sections[type].banks[bank];
writeSymBank(sect);
slackMap[type] += writeMapBank(sect, type, bank);
writeSymBank(sect, type, bank);
usedMap[type] += writeMapBank(sect, type, bank);
}
}
writeMapSlack(slackMap);
writeMapUsed(usedMap);
closeFile(symFile);
closeFile(mapFile);

View File

@@ -37,7 +37,7 @@ struct RPNStack {
size_t capacity;
} stack;
static inline void initRPNStack(void)
static void initRPNStack(void)
{
stack.capacity = 64;
stack.values = malloc(sizeof(*stack.values) * stack.capacity);
@@ -46,7 +46,7 @@ static inline void initRPNStack(void)
err(1, "Failed to init RPN stack");
}
static inline void clearRPNStack(void)
static void clearRPNStack(void)
{
stack.size = 0;
}
@@ -92,7 +92,7 @@ static int32_t popRPN(struct FileStackNode const *node, uint32_t lineNo)
return stack.values[stack.size];
}
static inline void freeRPNStack(void)
static void freeRPNStack(void)
{
free(stack.values);
free(stack.errorFlags);
@@ -292,6 +292,11 @@ static int32_t computeRPNExpr(struct Patch const *patch,
break;
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;
while (getRPNByte(&expression, &size, patch->src, patch->lineNo))
;
@@ -320,6 +325,44 @@ static int32_t computeRPNExpr(struct Patch const *patch,
}
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:
value = popRPN();
if (!isError && (value < 0
@@ -400,7 +443,7 @@ static int32_t computeRPNExpr(struct Patch const *patch,
void patch_CheckAssertions(struct Assertion *assert)
{
verbosePrint("Checking assertions...");
verbosePrint("Checking assertions...\n");
initRPNStack();
while (assert) {
@@ -435,6 +478,8 @@ void patch_CheckAssertions(struct Assertion *assert)
}
struct Assertion *next = assert->next;
free(assert->patch.rpnExpression);
free(assert->message);
free(assert);
assert = next;
}

View File

@@ -1,11 +1,11 @@
.\"
.\" This file is part of RGBDS.
.\"
.\" Copyright (c) 2010-2019, Anthony J. Bentley and RGBDS contributors.
.\" Copyright (c) 2010-2021, Anthony J. Bentley and RGBDS contributors.
.\"
.\" SPDX-License-Identifier: MIT
.\"
.Dd November 26, 2019
.Dd March 28, 2021
.Dt RGBLINK 1
.Os
.Sh NAME

View File

@@ -1,11 +1,11 @@
.\"
.\" This file is part of RGBDS.
.\"
.\" Copyright (c) 2017-2018, Antonio Nino Diaz and RGBDS contributors.
.\" Copyright (c) 2017-2021, Antonio Nino Diaz and RGBDS contributors.
.\"
.\" SPDX-License-Identifier: MIT
.\"
.Dd November 26, 2019
.Dd March 28, 2021
.Dt RGBLINK 5
.Os
.Sh NAME

View File

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

View File

@@ -49,8 +49,8 @@ static void checkSectUnionCompat(struct Section *target, struct Section *other)
other->name, target->org, other->org);
} else if (target->isAlignFixed) {
if ((other->org - target->alignOfs) & target->alignMask)
errx(1, "Section \"%s\" is defined with conflicting %" PRIu16
"-byte alignment (offset %" PRIu16 ") and address $%04" PRIx16,
errx(1, "Section \"%s\" is defined with conflicting %d-byte alignment (offset %"
PRIu16 ") and address $%04" PRIx16,
other->name, target->alignMask + 1,
target->alignOfs, other->org);
}
@@ -61,15 +61,14 @@ static void checkSectUnionCompat(struct Section *target, struct Section *other)
if (target->isAddressFixed) {
if ((target->org - other->alignOfs) & other->alignMask)
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->alignMask + 1, other->alignOfs);
} else if (target->isAlignFixed
&& (other->alignMask & target->alignOfs)
!= (target->alignMask & other->alignOfs)) {
errx(1, "Section \"%s\" is defined with conflicting %" PRIu16
"-byte alignment (offset %" PRIu16 ") and %" PRIu16
"-byte alignment (offset %" PRIu16 ")",
errx(1, "Section \"%s\" is defined with conflicting %d-byte alignment (offset %"
PRIu16 ") and %d-byte alignment (offset %" PRIu16 ")",
other->name, target->alignMask + 1, target->alignOfs,
other->alignMask + 1, other->alignOfs);
} 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) {
if ((org - target->alignOfs) & target->alignMask)
errx(1, "Section \"%s\" is defined with conflicting %" PRIu16
"-byte alignment (offset %" PRIu16 ") and address $%04" PRIx16,
errx(1, "Section \"%s\" is defined with conflicting %d-byte alignment (offset %"
PRIu16 ") and address $%04" PRIx16,
other->name, target->alignMask + 1,
target->alignOfs, other->org);
}
@@ -109,15 +108,14 @@ static void checkFragmentCompat(struct Section *target, struct Section *other)
if (target->isAddressFixed) {
if ((target->org - ofs) & other->alignMask)
errx(1, "Section \"%s\" is defined with conflicting address $%04"
PRIx16 " and %" PRIu16 "-byte alignment (offset %" PRIu16 ")",
PRIx16 " and %d-byte alignment (offset %" PRIu16 ")",
other->name, target->org,
other->alignMask + 1, other->alignOfs);
} else if (target->isAlignFixed
&& (other->alignMask & target->alignOfs) != (target->alignMask & ofs)) {
errx(1, "Section \"%s\" is defined with conflicting %" PRIu16
"-byte alignment (offset %" PRIu16 ") and %" PRIu16
"-byte alignment (offset %" PRIu16 ")",
errx(1, "Section \"%s\" is defined with conflicting %d-byte alignment (offset %"
PRIu16 ") and %d-byte alignment (offset %" PRIu16 ")",
other->name, target->alignMask + 1, target->alignOfs,
other->alignMask + 1, other->alignOfs);
@@ -165,6 +163,9 @@ static void mergeSections(struct Section *target, struct Section *other, enum Se
if (!target->data)
errx(1, "Failed to concatenate \"%s\"'s fragments", target->name);
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;
@@ -194,11 +195,7 @@ void sect_AddSection(struct Section *section)
section->name, typeNames[section->type]);
} else {
/* If not, add it */
bool collided = hash_AddElement(sections, section->name,
section);
if (beVerbose && collided)
warnx("Section hashmap collision occurred!");
hash_AddElement(sections, section->name, section);
}
}
@@ -248,12 +245,12 @@ static void doSanityChecks(struct Section *section, void *ptr)
* Check if alignment is reasonable, this is important to avoid UB
* An alignment of zero is equivalent to no alignment, basically
*/
if (section->isAlignFixed && section->alignMask == 1)
if (section->isAlignFixed && section->alignMask == 0)
section->isAlignFixed = false;
/* Too large an alignment may not be satisfiable */
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);
uint32_t minbank = bankranges[section->type][0], maxbank = bankranges[section->type][1];
@@ -293,9 +290,9 @@ static void doSanityChecks(struct Section *section, void *ptr)
startaddr[section->type], endaddr(section->type));
if (section->org + section->size > endaddr(section->type) + 1)
fail("Section \"%s\"'s end address %#" PRIx16
" is greater than last address %#" PRIx16, section->name,
section->org + section->size, endaddr(section->type) + 1);
fail("Section \"%s\"'s end address %#x is greater than last address %#x",
section->name, section->org + section->size,
endaddr(section->type) + 1);
}
#undef fail

View File

@@ -54,10 +54,7 @@ void sym_AddSymbol(struct Symbol *symbol)
}
/* If not, add it */
bool collided = hash_AddElement(symbols, symbol->name, symbol);
if (beVerbose && collided)
warnx("Symbol hashmap collision occurred!");
hash_AddElement(symbols, symbol->name, symbol);
}
struct Symbol *sym_GetSymbol(char const *name)

View File

@@ -1,11 +1,11 @@
.\"
.\" This file is part of RGBDS.
.\"
.\" Copyright (c) 2017-2020, Antonio Nino Diaz and RGBDS contributors.
.\" Copyright (c) 2017-2021, Antonio Nino Diaz and RGBDS contributors.
.\"
.\" SPDX-License-Identifier: MIT
.\"
.Dd January 26, 2018
.Dd March 28, 2021
.Dt RGBDS 5
.Os
.Sh NAME
@@ -22,11 +22,11 @@ This toolchain is in development and new features may require adding more inform
The following types are used:
.Pp
.Ar LONG
is a 32bit integer stored in littleendian format.
is a 32-bit integer stored in little-endian format.
.Ar BYTE
is an 8bit integer.
is an 8-bit integer.
.Ar STRING
is a 0terminated string of
is a 0-terminated string of
.Ar BYTE .
.Bd -literal
; Header
@@ -226,7 +226,7 @@ with some bytes being special prefixes for integers and symbols.
.It Li $04 Ta Li % operator
.It Li $05 Ta Li unary -
.It Li $06 Ta Li ** operator
.It Li $10 Ta Li | operator
.It Li $10 Ta Li \&| operator
.It Li $11 Ta Li & operator
.It Li $12 Ta Li ^ operator
.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) ,
a null-terminated string follows.
.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 .
Checks if the value is in HRAM, ANDs it with 0xFF.
.It Li $61 Ta Li RSTCheck .

View File

@@ -1,11 +1,11 @@
.\"
.\" This file is part of RGBDS.
.\"
.\" Copyright (c) 2010-2018, Anthony J. Bentley and RGBDS contributors.
.\" Copyright (c) 2010-2021, Anthony J. Bentley and RGBDS contributors.
.\"
.\" SPDX-License-Identifier: MIT
.\"
.Dd March 7, 2018
.Dd March 28, 2021
.Dt RGBDS 7
.Os
.Sh NAME
@@ -18,6 +18,10 @@ $ rgbasm \-o bar.o foo.asm
$ rgblink \-o baz.gb bar.o
$ rgbfix \-v \-p 0 baz.gb
.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
.Xr rgbasm 1 ,
.Xr rgbfix 1 ,

View File

@@ -1,3 +1,3 @@
ERROR: align-large-ofs.asm(2):
Alignment offset (2) must be smaller than alignment size (2)
error: Assembly aborted (1 errors)!
error: Assembly aborted (1 error)!

View File

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

View File

@@ -0,0 +1,3 @@
SECTION UNION "X", WRAM0
SECTION UNION "X", WRAM0, ALIGN[16]

View File

@@ -0,0 +1,3 @@
ERROR: align-unattainable.asm(3):
Section "X"'s alignment cannot be attained in WRAM0
error: Assembly aborted (1 error)!

View File

@@ -1,3 +1,3 @@
ERROR: assert-nosect-bank.asm(1):
PC has no bank outside a section
error: Assembly aborted (1 errors)!
error: Assembly aborted (1 error)!

View File

@@ -1,3 +1,3 @@
ERROR: assert@-no-sect.asm(1):
PC has no value outside a section
error: Assembly aborted (1 errors)!
error: Assembly aborted (1 error)!

View File

@@ -1,5 +1,5 @@
ERROR: block-comment-termination-error.asm(1):
Unterminated block comment
ERROR: block-comment-termination-error.asm(1):
syntax error, unexpected newline
syntax error, unexpected end of buffer
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

View File

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

View File

@@ -0,0 +1,43 @@
macro tickle
; There once was a bug where overwriting worked only on the second try, so
; try everything twice for good measure
; Skip this syntax for EQUS, as it is invalid
IF \2
\1 = 0
\1 = 0
PRINTLN \1
\1 EQU 0
\1 EQU 0
PRINTLN \1
ENDC
PURGE \1
PURGE \1
PRINTLN \1
DEF \1 EQU 0
DEF \1 EQU 0
PRINTLN \1
DEF \1 = 0
DEF \1 = 0
PRINTLN \1
DEF \1 EQUS "hello"
DEF \1 EQUS "hello"
PRINTLN \1
REDEF \1 = 0
REDEF \1 = 0
PRINTLN \1
REDEF \1 EQUS "hello"
REDEF \1 EQUS "hello"
PRINTLN \1
endm
; Representative numeric and string builtins
tickle __LINE__, 1
tickle __FILE__, 0

View File

@@ -0,0 +1,57 @@
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(7):
'__LINE__' already defined as constant at <builtin>
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(8):
'__LINE__' already defined as constant at <builtin>
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(11):
'__LINE__' already defined at <builtin>
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(12):
'__LINE__' already defined at <builtin>
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(16):
Built-in symbol '__LINE__' cannot be purged
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(17):
Built-in symbol '__LINE__' cannot be purged
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(20):
'__LINE__' already defined at <builtin>
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(21):
'__LINE__' already defined at <builtin>
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(24):
'__LINE__' already defined as constant at <builtin>
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(25):
'__LINE__' already defined as constant at <builtin>
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(28):
'__LINE__' already defined at <builtin>
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(29):
'__LINE__' already defined at <builtin>
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(32):
'__LINE__' already defined as constant at <builtin>
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(33):
'__LINE__' already defined as constant at <builtin>
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(36):
'__LINE__' already defined as non-EQUS at <builtin>
ERROR: builtin-overwrite.asm(42) -> builtin-overwrite.asm::tickle(37):
'__LINE__' already defined as non-EQUS at <builtin>
ERROR: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(16):
Built-in symbol '__FILE__' cannot be purged
ERROR: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(17):
Built-in symbol '__FILE__' cannot be purged
ERROR: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(20):
'__FILE__' already defined at <builtin>
ERROR: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(21):
'__FILE__' already defined at <builtin>
ERROR: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(24):
'__FILE__' already defined as constant at <builtin>
ERROR: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(25):
'__FILE__' already defined as constant at <builtin>
ERROR: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(28):
'__FILE__' already defined at <builtin>
ERROR: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(29):
'__FILE__' already defined at <builtin>
ERROR: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(32):
'__FILE__' already defined as constant at <builtin>
ERROR: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(33):
'__FILE__' already defined as constant at <builtin>
ERROR: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(36):
Built-in symbol '__FILE__' cannot be redefined
ERROR: builtin-overwrite.asm(43) -> builtin-overwrite.asm::tickle(37):
Built-in symbol '__FILE__' cannot be redefined
error: Assembly aborted (28 errors)!

View File

@@ -0,0 +1,14 @@
$9
$D
$12
$16
$1A
$1E
$22
$26
builtin-overwrite.asm
builtin-overwrite.asm
builtin-overwrite.asm
builtin-overwrite.asm
builtin-overwrite.asm
builtin-overwrite.asm

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

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):
syntax error, unexpected PRINTLN, expecting newline
syntax error, unexpected PRINTLN, expecting newline or end of buffer
ERROR: code-after-endm-endr-endc.asm(7):
Macro "mac" not defined
ERROR: code-after-endm-endr-endc.asm(12):
syntax error, unexpected PRINTLN, expecting newline
syntax error, unexpected PRINTLN, expecting newline or end of buffer
ERROR: code-after-endm-endr-endc.asm(17):
syntax error, unexpected PRINTLN, expecting newline
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):
syntax error, unexpected PRINTLN, expecting newline
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)!

30
test/asm/def.asm Normal file
View File

@@ -0,0 +1,30 @@
def variable = 1
println variable
def variable set 2
println variable
redef variable = 3
println variable
redef variable set 4
println variable
DEF constant EQU 42
println constant
DEF string EQUS "here"
println "{string}"
rsreset
def _x rb
def _y rw 2
def _z rl
def _size rb 0
println "{_x} {_y} {_z} {_size}"
def constant equ 6*7 ; fails
println constant
redef string equs "there"
println "{string}"
redef constant equ 6*9
println constant

3
test/asm/def.err Normal file
View File

@@ -0,0 +1,3 @@
ERROR: def.asm(23):
'constant' already defined at def.asm(10)
error: Assembly aborted (1 error)!

10
test/asm/def.out Normal file
View File

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

5
test/asm/def.simple.err Normal file
View File

@@ -0,0 +1,5 @@
ERROR: def.asm(23):
'constant' already defined at def.asm(10)
ERROR: def.asm(29):
syntax error
error: Assembly aborted (2 errors)!

View File

@@ -2,4 +2,4 @@ warning: deprecated-pi.asm(2): [-Wobsolete]
`_PI` is deprecated; use 3.14159
ERROR: deprecated-pi.asm(3):
Built-in symbol '_PI' cannot be purged
error: Assembly aborted (1 errors)!
error: Assembly aborted (1 error)!

View File

@@ -1,36 +1,62 @@
_ASM equ 0
def _ASM equ 0
test: MACRO
; Test RGBASM
V equs "_ASM +"
; Test RGBASM
redef V equs "_ASM +"
static_assert \#
PURGE V
; Test RGBLINK
V equs "_LINK +"
; Test RGBLINK
redef V equs "_LINK +"
assert \#
PURGE V
ENDM
for x, -300, 301
for y, -x - 1, x + 2
if y != 0
q = x / y
r = x % y
test (V (q * y + r)) == (V x)
test (V (x + y) % y) == (V r)
test (V (x - y) % y) == (V r)
endc
endr
endr
test_mod: MACRO
def x = \1 ; dividend
def y = \2 ; divisor
shift 2
def q = x / y ; quotient
def r = x % y ; remainder
; identity laws
test (V (q * y + r)) == (V x)
test (V (x + y) % y) == (V r)
test (V (x - y) % y) == (V r)
ENDM
for x, -300, 301
for p, 31
y = 2 ** p
r = x % y
m = x & (y - 1)
test (V r) == (V m)
endr
endr
test_each_mod: MACRO
test_mod (\1), (\2)
test_mod (\1), -(\2)
test_mod -(\1), (\2)
test_mod -(\1), -(\2)
ENDM
test_pow: MACRO
def x = \1 ; dividend
def y = 2 ** \2 ; divisor
def r = x % y ; remainder
def m = x & (y - 1) ; mask
; identity law
test (V r) == (V m)
ENDM
test_each_pow: MACRO
test_pow (\1), (\2)
test_pow -(\1), (\2)
ENDM
test_each_mod 0, 1
test_each_mod 7, 5
test_each_mod 42, 256
test_each_mod 567, 256
test_each_mod 256, 512
test_each_mod 1, 65535
test_each_mod 100, 65535
test_each_mod 10000, 65535
test_each_mod 1000000, 65535
test_each_pow 5, 1
test_each_pow 42, 8
test_each_pow 567, 8
test_each_pow 12345, 16
test_each_pow 99999, 16
SECTION "LINK", ROM0
_LINK::

View File

@@ -2,4 +2,4 @@ ERROR: ds-bad.asm(3):
Expected constant expression: 'unknown' is not constant at assembly time
warning: ds-bad.asm(4): [-Wtruncation]
Expression must be 8-bit
error: Assembly aborted (1 errors)!
error: Assembly aborted (1 error)!

View File

@@ -0,0 +1,23 @@
if 1
println "taken if"
elif 2 / 0 ; avoided fatal "Division by zero" error
println "untaken elif"
elif 3 / 0 ; avoided fatal "Division by zero" error
println "untaken after untaken"
endc
if 0
println "untaken if"
elif 1
println "taken elif"
elif !@#$ ; avoided fatal syntax error
println "untaken elif"
elif %^&* ; avoided fatal syntax error
println "untaken after untaken"
endc
if 0
println "untaken if"
elif 1 / 0 ; fatal "Division by zero" error
println "unreached elif"
endc

View File

@@ -0,0 +1,2 @@
FATAL: elif-after-taken-if.asm(21):
Division by zero

View File

@@ -0,0 +1,2 @@
taken if
taken elif

View File

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

View File

@@ -1,6 +1,2 @@
recurse EQUS "recurse "
recurse EQUS "recurse"
recurse
; FIXME: also handle the following:
; recurse EQUS "recurse"
; recurse

View File

@@ -0,0 +1,6 @@
test: MACRO
v equs "X"
X equs "" ; should not be expanded
\1
ENDM
test v 0

View File

@@ -0,0 +1,3 @@
ERROR: expand-empty-string.asm(6) -> expand-empty-string.asm::test(4):
syntax error, unexpected number
error: Assembly aborted (1 error)!

View File

View File

@@ -0,0 +1,3 @@
ERROR: expand-empty-string.asm(6) -> expand-empty-string.asm::test(4):
syntax error
error: Assembly aborted (1 error)!

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