Compare commits

..

201 Commits

Author SHA1 Message Date
Rangi
9d993d84e8 Release 0.5.2 2021-11-23 17:08:03 -05:00
ISSOtm
fef168b2a5 Add the new completion scripts to checkdiff 2021-11-23 22:12:57 +01:00
ISSOtm
4a4b22c78c Add Bash completion scripts for the last 3 2021-11-23 22:10:08 +01:00
Rangi
db79689e81 Add -Wnumeric-string to shell autocompletion 2021-11-22 17:58:12 -05:00
Rangi
c2ca46c27d Remove TRACE_PARSER support
Attempting to build with this gave an "undefined reference to `yydebug'"
error (maybe a version issue with bison?), and I don't think it's been
used for recent parser debugging either.
2021-11-22 23:49:59 +01:00
Rangi
aac839f389 Remove dbgPrint and TRACE_LEXER support
I have not found `TRACE_LEXER` to be useful in debugging
actual lexer issues.
2021-11-22 23:49:59 +01:00
Rangi
9d9febe1d3 Consistent title case for rgbasm.5 headings 2021-11-22 17:12:16 -05:00
Rangi
b9fd85470e Reword docs now that SET is deprecated (#946)
- '=' constants are "variables" (not "mutable constants")
- EQU constants are "numeric constants" (not "immutable constants")
- EQUS constants are "string constants" (not "string equates")
- DS declarations are "static allocations" (not "variables")
2021-11-22 17:08:29 -05:00
Rangi
ec6d63bce3 Allow underscores in gfx literals (#951)
Fixes #950
2021-11-21 16:18:23 -05:00
Rangi
bdcef6f252 Refactor error reporting to simplify BSD-style err (#949) 2021-11-21 16:16:54 -05:00
ISSOtm
54293a9184 Remove unused "MAX_PATH" header
The header's name was also quite misleading.
Also remove an unused define in `asm/symbol.h`.
2021-11-21 18:32:29 +01:00
ISSOtm
b04e71ed34 Use correct length type for Abs*Group 2021-11-21 18:28:42 +01:00
Rangi
f82603f196 Lowercase "error:" in -Werror output 2021-11-20 11:21:46 +01:00
Rangi
cedfd2582a Move more statements into for loop clauses 2021-11-19 22:55:20 -05:00
Rangi
c7322258fc Refactor readGfxConstant for consistency, and edit warning message 2021-11-19 21:36:56 -05:00
Rangi
036b6c1b89 Capitalize "FATAL:" in rgblink error messages
"warning:" and "error:" are lowercase
This matches rgbasm's formatting
2021-11-19 19:52:57 -05:00
Rangi
8e2a164a32 Implement compound assignment operators for mutable constants
Fixes #943
2021-11-19 08:50:00 +01:00
Rangi
b76819792d Deprecate SET in favor of =
`SET` is redundant with `=`, and is already the name of an instruction.
2021-11-19 00:05:49 +01:00
Rangi
3e945679ad Standardize on "east const" (type const * not const type *)
Avoid "WARNING: Move const after static - use 'static const char'"
2021-11-18 09:12:00 +01:00
Rangi
efccf6c931 A few stylistic tweaks
- `goto free_romx` -> the more typical `goto cleanup`
- `goto fail` -> the more typical `goto finish`
- Remove a redundant `todo` variable
2021-11-17 23:51:40 -05:00
Rangi
e5552e27f2 strrstr can take and return char const * 2021-11-17 22:47:05 -05:00
Rangi
438963fb24 Remove unused #include "extern/utf8decoder.h" (#940)
Fixes #937
2021-11-12 23:37:19 +01:00
Rangi
0bb815edc0 Implement -Wnumeric-string[=0|1|2] (#935)
Fixes #934
2021-11-12 23:09:35 +01:00
Rangi
55a02981b5 Small updates to documentation wording (#939)
Fixes #936
2021-11-12 23:06:02 +01:00
ISSOtm
b06e3b239d Clean up -MT/-MQ code
Remove unreachable argument presence check (handled by `getopt`)
Merge allocation paths into a single `realloc` call
Avoid searching for string lengths multiple times
Tiny (compatible) change: no space between last dependent and colon if
`-MT` or `-MQ` is specified
2021-11-06 13:21:39 +01:00
Rangi
47442941b6 Support ! operator for condition codes (#720)
Fixes #719
2021-11-02 00:16:52 +01:00
DaKnig
b16d2d0695 Add Bash completion script for rgbasm (#895)
* Add Bash completion script for RGBASM

Should have large feature parity with the Zsh completion

Co-authored-by: DaKnig <37626476+DaKnig@users.noreply.github.com>

* Register RGBASM Bash completion in `checkdiff`

Co-authored-by: ISSOtm <eldredhabert0@gmail.com>
2021-10-31 23:44:01 +01:00
Eievui
8b1cc72f09 Added scramble flags to RGBLINK. (#921)
* Add scramble flags to RGBLINK

-S and -W will scramble ROMX and WRAMX respectively.

* Modify scramble CLI

CLI now takes a list of comma-separated values.
Added arg_error to clean up messages.

Co-authored-by: Eldred Habert <eldredhabert0@gmail.com>

* Document scrambling functionality

Co-authored-by: Eldred Habert <eldredhabert0@gmail.com>
2021-10-31 22:58:26 +01:00
Rangi
11a6a81169 Implement -Wtruncation=level (#931)
* Implement -Wtruncation=level

-Wtruncation=0 is the same as the current -Wno-truncation.
-Wtruncation=2 is the same as the current -Wtruncation.
-Wtruncation=1 is the new default; it's less strict, allowing
N-bit values to be between -2**N and 2**N (exclusive).

* Implement generic "parametrized warning" system

* Test more `Wtruncation` variants

Co-authored-by: ISSOtm <eldredhabert0@gmail.com>
2021-10-31 17:47:31 -04:00
ISSOtm
0ce66009c1 Parallelize CMake builds 2021-10-31 22:01:35 +01:00
ISSOtm
33f2f555a6 Enable sanitizers with Clang on Ubuntu as well 2021-10-31 21:41:58 +01:00
ISSOtm
0487f9f841 Enable more checks in CMake builds on macOS 2021-10-31 21:17:51 +01:00
ISSOtm
0d6bfb84ce Ignore unknown warning options
GCC and Clang do not understand the exact same warning option sets
2021-10-31 21:17:51 +01:00
ISSOtm
1e4ace8974 Update tested subproject commits 2021-10-31 20:50:26 +01:00
Eldred Habert
a378d1e8cb Reword label definition docs (#887)
* Reword label definition docs

A bunch of short sentences isn't very readable, this should be better

* Use correct wording for "computing difference"

Co-authored-by: Rangi <35663410+Rangi42@users.noreply.github.com>

* Explain how to define a label before mentioning local ones

* Move double-colon paragraph to make the explanation flow better

* Clarify what a label's value is

Co-authored-by: Rangi <35663410+Rangi42@users.noreply.github.com>
2021-10-31 20:20:57 +01:00
ISSOtm
81ea39effe Add two forgotten warnings to RGBASM Zsh completion 2021-10-31 20:16:03 +01:00
ISSOtm
1a07391a97 Introduce ARRAY_SIZE macro
Checked by `checkpatch`, and you know what? Not a bad thing
See https://github.com/gbdev/rgbds/pull/931#discussion_r738856724
2021-10-31 07:53:33 +01:00
Rangi
b002d95459 Fix precison of fixed-point formatting
Fixes #908
2021-10-28 23:29:16 +02:00
ISSOtm
646fc62b89 Avoid running duplicate CI on macOS
`gcc` is just an alias to `clang` on macOS, so save on CI time.
2021-10-26 01:39:05 +02:00
ISSOtm
699c00dc20 Enable MacOS Big Sur in CI 2021-10-20 23:11:19 +02:00
Eldred Habert
9b9f3ffb96 Remove Ubuntu 16.04 from CI
It's been deprecated, see https://github.com/actions/virtual-environments/issues/3287
2021-10-05 22:24:24 +02:00
Eldred Habert
00a67c3fb2 Clarify xref to fmt spec
Co-authored-by: Rangi <35663410+Rangi42@users.noreply.github.com>
2021-09-09 15:08:52 +02:00
ISSOtm
50d6403c72 Move section interpolation to its own section
Fixes #907
2021-09-09 15:08:52 +02:00
Eldred Habert
9111157b82 Add NieDzejkob and JL2210 to contributor list
Significant contributions all around are worth acknowledging.
2021-09-09 14:53:34 +02:00
Eldred Habert
794dd6cd7e Mark Rangi and I as main contributors
I think we've earned it, at least for being maintainers for a couple years.
I believe the work done by those before us is more important (relicensing & cleanup),
but we've both made significant changes on top of what they made possible.
2021-09-09 14:37:23 +02:00
ISSOtm
ae4352c198 Fix typos in rgbasm(5) 2021-08-23 18:39:19 +02:00
Rangi
4a73eb56ea Make peek() tail recursive instead of using goto
Compilation is identical with `gcc` or `clang`, -O3` or `-O2`
2021-08-18 01:30:47 +02:00
martendo
0f321bc797 Fix section merge alignment error message (#919)
Co-authored-by: Eldred Habert <eldredhabert0@gmail.com>
2021-08-17 18:03:46 -04:00
ISSOtm
94d07c78d8 Fix MBC3+TIMER + handle lack of BATTERY
`TIMER & BATTERY` is 0, but even `|` would only be checking if
*either* is set; instead, imply BATTERY as soon as TIMER is given,
printing a warning if it was not given.
2021-08-12 10:20:09 +02:00
Rangi
b51e1c7c2c Compare FOR ranges to Python's range 2021-08-01 10:45:44 +02:00
Rangi
26ddf1ff4d Prevent defining invalid local labels
Fixes #913
2021-07-30 15:21:47 +02:00
ISSOtm
20fd6eabbb Fix up gitignores
Move tests gitignore into its own directory
Use stricter patterns where they make sense
2021-07-25 12:15:56 +02:00
ISSOtm
fbe29006d4 Document two two .github subdirectories 2021-07-25 12:12:36 +02:00
Rangi
03bb510588 endCapture shouldn't handle lexerState->atLineStart
`startCapture` did not initialize `lexerState->atLineStart`;
its final value is a consequence of the separate but similar
behaviors within `lexer_CaptureRept` and `lexer_CaptureMacroBody`.
2021-07-04 18:31:46 -04:00
Rangi
695dfe9dbd Add missing file line-continuation-string.asm
Also make some minor formatting corrections
2021-07-04 16:12:34 -04:00
Rangi
9782f7d942 Factor out endCapture to go with startCapture (#904)
This also refactors `startCapture` to modify the
capture body as an argument.
2021-07-04 16:08:59 -04:00
Rangi
1b5648bb06 Line continuations *do* work inside strings
The rgbasm.5(5) documentation was outdated here
2021-07-01 17:56:49 -04:00
Rangi
a67f5d6e01 SIZEOF("Section") and STARTOF("Section") can be known
Fixes #890
2021-06-27 21:03:06 +02:00
Rangi
06b57aa1ce Avoid unnecessary "overwriting a non-zero byte" warnings
- Don't warn if the non-zero byte being written is the same as the original byte
- Add a `-O` / `--overwrite` flag to silence all such warnings

Fixes #897
2021-06-27 19:16:11 +02:00
Rangi
6d2db2ef64 make checkdiff does CI documentation checks (#900)
Fixes #744
2021-06-24 17:49:08 -04:00
Eldred Habert
9868a01163 Format -m help better in the man page
Use semantic macros instead of plain text
2021-05-30 13:59:02 +02:00
Eldred Habert
0c8cdd92d6 Make instruction descriptions more proper English
Use articles where appropriate
Use adjectives where it makes sense
2021-05-24 22:07:36 +02:00
Rangi
80a376f045 Syntax errors resets the lexer right away
`DEF`, `REDEF`, etc disable EQUS expansion, and reading
macro or OPT arguments sets the lexer to raw mode.
Syntax errors resume normal parsing at the line's end,
but should resume normal tokenization even before that.
2021-05-22 16:08:55 -04:00
Rangi
0068c1375c Syntax error message hints to indent macro invocations
This message is only printed for identifiers parsed as
`T_LABEL` (since they're at the start of a line) but
already defined as macros. Otherwise it may not be
relevant, e.g. for `MyLabel;:` or `My Label::`.
2021-05-22 14:58:26 +02:00
ISSOtm
872af9c7ed Remove dead store in linker script CRLF handling 2021-05-21 17:20:47 -04:00
ISSOtm
06ea7b20bf Reinstate "empty filename" assertion in __FILE__ callback
This assertion was mentioned by a comment, but deleted for some reason.
2021-05-21 17:20:47 -04:00
ISSOtm
a3c4652bfd Fix dead stores in charmap_ConvertNext
Also cleanup / rearrange some of the function while we're at it.
2021-05-21 17:20:47 -04:00
Rangi
5ad48851ed Allow error messages for subsequent syntax errors (#892)
Fixes #891
2021-05-21 17:04:27 -04:00
ISSOtm
e3b7339dd6 Save UNION stack when using PUSHS as well
This allows using the latter within the former
2021-05-21 09:47:27 +02:00
ISSOtm
69d7f84502 Reset LOAD offset when changing SECTIONs
This would cause spurious section overflow messages, since the load offset
is added to the section's when computing its size.
2021-05-21 09:37:17 +02:00
ISSOtm
e970b6d6eb Update Zsh completions with CLI changes
Also fix some misc. issues with them, and fix an incorrect reported ID for MBC1
2021-05-15 19:21:39 +02:00
ISSOtm
d9cce3fa1f Update TPP1 canonical name to "TIMER", not "RTC"
See previous commit
2021-05-15 19:13:24 +02:00
Rangi
23721694ea Comment that anonymous labels internally start with '!'
`startsIdentifier` should not accept this character so
anonymous labels won't conflict with nonymous ones.
2021-05-15 12:57:22 -04:00
ISSOtm
aa02958e18 Fix mandoc warnings 2021-05-15 12:23:05 +02:00
ISSOtm
011d4ec392 Use the Ad macro for RST addresses
Sy has no semantic meaning, and Ad may fix how the "x" is rendered
in the HTML export
2021-05-15 12:03:20 +02:00
ISSOtm
afbaf10185 Fix MBC help string
It's called TIMER, not RTC >_<
2021-05-15 11:25:09 +02:00
Rangi
6a5e2f439e Fix the STRFMT documentation in rgbasm(5)
Fixes #886
2021-05-09 17:23:57 -04:00
Rangi
fba77c4dce Specify to update the release branch in RELEASE.rst
This is done manually after publishing a release.
2021-05-08 23:44:53 -04:00
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
452 changed files with 7707 additions and 4205 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

17
.github/workflows/checkdiff.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: "Code coverage checking"
on: pull_request
jobs:
checkdiff:
runs-on: ubuntu-latest
steps:
- name: Set up repo
run: |
git clone -b "${{ github.event.pull_request.head.ref }}" "${{ github.event.pull_request.head.repo.clone_url }}" rgbds
cd rgbds
git remote add upstream "${{ github.event.pull_request.base.repo.clone_url }}"
git fetch upstream
- name: Checkdiff
working-directory: rgbds
run: |
make checkdiff "BASE_REF=${{ github.event.pull_request.base.sha }}" Q= | tee log

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

View File

@@ -7,16 +7,26 @@ jobs:
unix-testing:
strategy:
matrix:
os: [ubuntu-20.04, ubuntu-18.04, ubuntu-16.04, macos-10.15]
os: [ubuntu-20.04, ubuntu-18.04, macos-11.0, macos-10.15]
cc: [gcc, clang]
buildsys: [make, cmake]
exclude:
# `gcc` is just an alias to `clang` on macOS, don't bother
- os: macos-10.15
cc: gcc
- os: macos-11.0
cc: gcc
include:
- os: ubuntu-18.04
cc: gcc
target: develop
cmakevars: -DSANITIZERS=ON -DMORE_WARNINGS=ON -DCMAKE_BUILD_TYPE=Debug
- os: ubuntu-20.04
cc: gcc
target: develop
cmakevars: -DSANITIZERS=ON -DMORE_WARNINGS=ON -DCMAKE_BUILD_TYPE=Debug
- os: macos-11.0
target: develop
cmakevars: -DSANITIZERS=ON -DMORE_WARNINGS=ON -DCMAKE_BUILD_TYPE=Debug
- os: macos-10.15
target: develop
cmakevars: -DSANITIZERS=ON -DMORE_WARNINGS=ON -DCMAKE_BUILD_TYPE=Debug
fail-fast: false
@@ -40,7 +50,7 @@ jobs:
run: |
export PATH="/usr/local/opt/bison/bin:$PATH"
cmake -S . -B build -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=${{ matrix.cc }} ${{ matrix.cmakevars }}
cmake --build build
cmake --build build -j
cp build/src/rgb{asm,link,fix,gfx} .
sudo cmake --install build
if: matrix.buildsys == 'cmake'
@@ -88,9 +98,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"
@@ -101,17 +111,17 @@ jobs:
- name: Build zlib
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
cmake -S zlib -B zbuild -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
cmake --build zbuild --config Release
cmake --build zbuild --config Release -j
cmake --install zbuild
- name: Build libpng
run: |
cmake -S libpng -B pngbuild -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DPNG_SHARED=ON -DPNG_STATIC=ON -DPNG_TESTS=OFF
cmake --build pngbuild --config Release
cmake --build pngbuild --config Release -j
cmake --install pngbuild
- name: Build Windows binaries
run: |
cmake -S . -B build -A ${{ matrix.platform }} -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release
cmake --build build --config Release -j
cmake --install build
- name: Package binaries
shell: bash

View File

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

16
.gitignore vendored
View File

@@ -1,16 +1,12 @@
rgbasm
rgblink
rgbfix
rgbgfx
rgbshim.sh
/rgbasm
/rgblink
/rgbfix
/rgbgfx
/rgbshim.sh
*.o
*.exe
*.dll
.checkpatch-camelcase.*
CMakeCache.txt
CMakeFiles
CMakeFiles/
cmake_install.cmake
test/pokecrystal
test/pokered
test/ucity

View File

@@ -25,8 +25,6 @@ endif()
option(SANITIZERS "Build with sanitizers enabled" OFF) # Ignored on MSVC
option(MORE_WARNINGS "Turn on more warnings" OFF) # Ignored on MSVC
option(TRACE_PARSER "Trace parser execution" OFF)
option(TRACE_LEXER "Trace lexer execution" OFF)
if(MSVC)
# MSVC's standard library triggers warning C5105,
@@ -35,12 +33,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()
@@ -53,7 +52,8 @@ else()
-Wstringop-overflow=4 -Walloc-zero -Wduplicated-cond
-Wfloat-equal -Wshadow -Wcast-qual -Wcast-align -Wlogical-op
-Wnested-externs -Wno-aggressive-loop-optimizations -Winline
-Wundef -Wstrict-prototypes -Wold-style-definition)
-Wundef -Wstrict-prototypes -Wold-style-definition
-Wno-unknown-warning-option -Wno-tautological-constant-out-of-range-compare)
endif()
endif()
@@ -87,11 +87,3 @@ if(CMAKE_BUILD_TYPE STREQUAL "Release")
message(CHECK_FAIL "no")
endif()
endif()
if(TRACE_PARSER)
target_compile_definitions(rgbasm PRIVATE -DYYDEBUG)
endif()
if(TRACE_LEXER)
target_compile_definitions(rgbasm PRIVATE -DLEXER_DEBUG)
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

@@ -19,6 +19,10 @@ Main contributors
- Antonio Niño Díaz <antonio_nd@outlook.com>
- Eldred "ISSOtm" Habert <eldredhabert0@gmail.com>
- Rangi <http://github.com/Rangi42>
Other contributors
------------------
@@ -30,7 +34,9 @@ Other contributors
- David Brotz <dbrotz007@gmail.com>
- Eldred Habert <eldredhabert0@gmail.com>
- Jakub Kądziołka <kuba@kadziolka.net>
- James "JL2210" Larrowe <https://github.com/JL2210>
- The Musl C library <http://www.musl-libc.org>
@@ -40,8 +46,6 @@ Other contributors
- Quint Guvernator <quint@guvernator.net>
- Rangi <http://github.com/Rangi42>
- Sanqui <gsanky@gmail.com>
- YamaArashi <shadow962@live.com>

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
@@ -48,6 +49,9 @@ YFLAGS ?= -Wall
BISON := bison
RM := rm -rf
# Used for checking pull requests
BASE_REF := origin/master
# Rules to build the RGBDS binaries
all: rgbasm rgblink rgbfix rgbgfx
@@ -60,17 +64,17 @@ 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 \
src/asm/util.o \
src/asm/warning.o \
src/extern/err.o \
src/extern/getopt.o \
src/extern/utf8decoder.o \
src/error.o \
src/hashmap.o \
src/linkdefs.o \
src/opmath.o
@@ -86,23 +90,23 @@ rgblink_obj := \
src/link/script.o \
src/link/section.o \
src/link/symbol.o \
src/extern/err.o \
src/extern/getopt.o \
src/error.o \
src/hashmap.o \
src/linkdefs.o \
src/opmath.o
rgbfix_obj := \
src/fix/main.o \
src/extern/err.o \
src/extern/getopt.o
src/extern/getopt.o \
src/error.o
rgbgfx_obj := \
src/gfx/gb.o \
src/gfx/main.o \
src/gfx/makepng.o \
src/extern/err.o \
src/extern/getopt.o
src/extern/getopt.o \
src/error.o
rgbasm: ${rgbasm_obj}
$Q${CC} ${REALLDFLAGS} -o $@ ${rgbasm_obj} ${REALCFLAGS} src/version.c -lm
@@ -188,9 +192,8 @@ checkcodebase:
# the first common commit between the HEAD and origin/master.
# `.y` files aren't checked, unfortunately...
BASE_REF:= origin/master
checkpatch:
$Qeval COMMON_COMMIT=$$(git merge-base HEAD ${BASE_REF}); \
$QCOMMON_COMMIT=`git merge-base HEAD ${BASE_REF}`; \
for commit in `git rev-list $$COMMON_COMMIT..HEAD`; do \
echo "[*] Analyzing commit '$$commit'"; \
git format-patch --stdout "$$commit~..$$commit" \
@@ -198,12 +201,17 @@ checkpatch:
| ${CHECKPATCH} - || true; \
done
# Target used to check for suspiciously missing changed files.
checkdiff:
$Qcontrib/checkdiff.bash `git merge-base HEAD ${BASE_REF}`
# This target is used during development in order to prevent adding new issues
# to the source code. All warnings are treated as errors in order to block the
# 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 \
@@ -211,11 +219,13 @@ develop:
-Wfloat-equal -Wshadow -Wcast-qual -Wcast-align -Wlogical-op \
-Wnested-externs -Wno-aggressive-loop-optimizations -Winline \
-Wundef -Wstrict-prototypes -Wold-style-definition \
-Wno-unknown-warning-option -Wno-tautological-constant-out-of-range-compare \
-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" 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
-------------------
@@ -45,31 +47,31 @@ The RGBDS source code file structure somewhat resembles the following:
.
├── .github/
   ├── actions/
   │   └── ...
   └── workflows/
      └── ...
├── actions/
└── ...
└── workflows/
└── ...
├── contrib/
   ├── zsh_compl/
   │   └── ...
   └── ...
├── zsh_compl/
└── ...
└── ...
├── include/
   └── ...
└── ...
├── src/
   ├── asm/
   │   └── ...
   ├── extern/
   │   └── ...
   ├── fix/
   │   └── ...
   ├── gfx/
   │   └── ...
   ├── link/
   │   └── ...
   ├── CMakeLists.txt
   └── ...
├── asm/
└── ...
├── extern/
└── ...
├── fix/
└── ...
├── gfx/
└── ...
├── link/
└── ...
├── CMakeLists.txt
└── ...
├── test/
   ├── ...
├── ...
│ └── run-tests.sh
├── CMakeLists.txt
├── Makefile
@@ -78,11 +80,16 @@ The RGBDS source code file structure somewhat resembles the following:
- ``.github/`` - files and scripts related to the integration of the RGBDS codebase with
GitHub.
* ``actions/`` - scripts used by workflow files.
* ``workflows/`` - CI workflow description files.
- ``contrib/`` - scripts and other resources which may be useful to users and developers of
RGBDS.
* ``zsh_compl`` contains tab completion scripts for use with zsh. Put them somewhere in your ``fpath``, and they should auto-load.
* ``bash_compl`` contains tab completion scripts for use with bash. Run them with ``source`` somewhere in your ``.bashrc``, and they should load every time you open a shell.
- ``include/`` - header files for each respective C files in `src`.
- ``src/`` - source code and manual pages for RGBDS.

67
RELEASE.rst Normal file
View File

@@ -0,0 +1,67 @@
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!
7. Update the `release` branch. You can use ``git push origin release``.

215
contrib/bash_compl/_rgbasm.bash Executable file
View File

@@ -0,0 +1,215 @@
#/usr/bin/env bash
# Known bugs:
# - Newlines in file/directory names break this script
# This is because we rely on `compgen -A`, which is broken like this.
# A fix would require implementing it ourselves, and no thanks!
# - `rgbasm --binary-digits=a` is treated the same as `rgbasm --binary-digits=` (for example)
# This is not our fault, Bash passes both of these identically.
# Maybe it could be worked around, but such a fix would likely be involved.
# The user can work around it by typing `--binary-digits ''` instead, for example.
# - Directories are not completed as such in "coalesced" short-opt arguments. For example,
# `rgbasm -M d<tab>` can autocomplete to `rgbasm -M dir/` (no space), but
# `rgbasm -Md<tab>` would autocomplete to `rgbasm -Mdir ` (trailing space) instead.
# This is because dircetory handling is performed by Readline, whom we can't tell about the short
# opt kerfuffle. The user can work around by separating the argument, as shown above.
# (Also, there might be more possible bugs if `-Mdir` is actually a directory. Ugh.)
# Something to note:
# `rgbasm --binary-digits=a` gets passed to us as ('rgbasm' '--binary-digits' '=' 'a')
# Thus, we don't need to do much to handle that form of argument passing: skip '=' after long opts.
_rgbasm_completions() {
COMPREPLY=()
# Format: "long_opt:state_after"
# Empty long opt = it doesn't exit
# See the `state` variable below for info about `state_after`
declare -A opts=(
[V]="version:normal"
[E]="export-all:normal"
[h]="halt-without-nop:normal"
[L]="preserve-ld:normal"
[v]="verbose:normal"
[w]=":normal"
[b]="binary-digits:unk"
[D]="define:unk"
[g]="gfx-chars:unk"
[i]="include:dir"
[M]="dependfile:glob-*.mk *.d"
[o]="output:glob-*.o"
[p]="pad-value:unk"
[r]="recursion-depth:unk"
[W]="warning:warning"
)
# Parse command-line up to current word
local opt_ena=true
# Possible states:
# - normal = Well, normal. Options are parsed normally.
# - unk = An argument that can't be completed, and should just be skipped.
# - warning = A warning flag.
# - dir = A directory path
# - glob-* = A glob, after the dash is a whitespace-separated list of file globs to use
local state=normal
# The length of the option, used as a return value by the function below
local optlen=0
# $1: a short option word
# `state` will be set to the parsing state after the last option character in the word. If
# "normal" is not returned, `optlen` will be set to the length (dash included) of the "option"
# part of the argument.
parse_short_opt() {
for (( i = 1; i < "${#1}"; i++ )); do
# If the option is not known, assume it doesn't take an argument
local opt="${opts["${1:$i:1}"]:-":normal"}"
state="${opt#*:}"
# If the option takes an argument, record the length and exit
if [[ "$state" != 'normal' ]]; then
let optlen="$i + 1"
return
fi
done
optlen=0
}
for (( i = 1; i < $COMP_CWORD; i++ )); do
local word="${COMP_WORDS[$i]}"
# If currently processing an argument, skip this word
if [[ "$state" != 'normal' ]]; then
state=normal
continue
fi
if [[ "$word" = '--' ]]; then
# Options stop being parsed after this
opt_ena=false
break
fi
# Check if it's a long option
if [[ "${word:0:2}" = '--' ]]; then
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
for long_opt in "${opts[@]}"; do
if [[ "$word" = "--${long_opt%%:*}" ]]; then
state="${long_opt#*:}"
# Check if the next word is just '='; if so, skip it, the argument must follow
# (See "known bugs" at the top of this script)
let i++
if [[ "${COMP_WORDS[$i]}" != '=' ]]; then
let i--
fi
optlen=0
break
fi
done
# Check if it's a short option
elif [[ "${word:0:1}" = '-' ]]; then
# The `-M?` ones are a mix of short and long, augh
# They must match the *full* word, but only take a single dash
# So, handle them here
if [[ "$1" = "-M"[GP] ]]; then
state=normal
elif [[ "$1" = "-M"[TQ] ]]; then
state='glob-*.d *.mk *.o'
else
parse_short_opt "$word"
# The last option takes an argument...
if [[ "$state" != 'normal' ]]; then
if [[ "$optlen" -ne "${#word}" ]]; then
# If it's contained within the word, we won't complete it, revert to "normal"
state=normal
else
# Otherwise, complete it, but start at the beginning of *that* word
optlen=0
fi
fi
fi
fi
done
# Parse current word
# Careful that it might look like an option, so use `--` aggressively!
local cur_word="${COMP_WORDS[$COMP_CWORD]}"
# Process options, as short ones may change the state
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then
# We might want to complete to an option or an arg to that option
# Parse the option word to check
# There's no whitespace in the option names, so we can ride a little dirty...
# Is this a long option?
if [[ "${cur_word:1:1}" = '-' ]]; then
# It is, try to complete one
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") )
return 0
else
# Short options may be grouped, parse them to determine what to complete
# The `-M?` ones may not be followed by anything
if [[ "$1" != "-M"[GPTQ] ]]; then
parse_short_opt "$cur_word"
# We got some short options that behave like long ones
COMPREPLY+=( $(compgen -W '-MG -MP -MT -MQ' -- "$cur_word") )
if [[ "$state" = 'normal' ]]; then
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') )
return 0
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
# This short option group only awaits its argument!
# Post the option group as-is as a reply so that Readline inserts a space,
# so that the next completion request switches to the argument
# An exception is made for warnings, since it's idiomatic to stick them to the
# `-W`, and it doesn't break anything.
COMPREPLY+=( "$cur_word" )
return 0
fi
fi
fi
fi
case "$state" in
unk) # Return with no replies: no idea what to complete!
;;
warning)
COMPREPLY+=( $(compgen -W "
assert
backwards-for
builtin-args
charmap-redef
div
empty-data-directive
empty-macro-arg
empty-strrpl
large-constant
long-string
macro-shift
nested-comment
numeric-string
obsolete
shift
shift-amount
truncation
user
all
extra
everything
error" -P "${cur_word:0:$optlen}" -- "${cur_word:$optlen}") )
;;
normal) # Acts like a glob...
state="glob-*.asm *.inc *.sm83"
;&
glob-*)
while read -r word; do
COMPREPLY+=("${cur_word:0:$optlen}$word")
done < <(for glob in ${state#glob-}; do compgen -A file -X \!"$glob" -- "${cur_word:$optlen}"; done)
# Also complete directories
;&
dir)
while read -r word; do
COMPREPLY+=("${cur_word:0:$optlen}$word")
done < <(compgen -A directory -- "${cur_word:$optlen}")
compopt -o filenames
;;
esac
}
complete -F _rgbasm_completions rgbasm

181
contrib/bash_compl/_rgbfix.bash Executable file
View File

@@ -0,0 +1,181 @@
#/usr/bin/env bash
# Same notes as RGBASM
_rgbfix_completions() {
COMPREPLY=()
# Format: "long_opt:state_after"
# Empty long opt = it doesn't exit
# See the `state` variable below for info about `state_after`
declare -A opts=(
[V]="version:normal"
[j]="non-japanese:normal"
[s]="sgb-compatible:normal"
[v]="validate:normal"
[C]="color-only:normal"
[c]="color-compatible:normal"
[f]="fix-spec:fix-spec"
[i]="game-id:unk"
[k]="new-licensee:unk"
[l]="old-licensee:unk"
[m]="mbc-type:mbc"
[n]="rom-version:unk"
[p]="pad-value:unk"
[r]="ram-size:unk"
[t]="title:unk"
)
# Parse command-line up to current word
local opt_ena=true
# Possible states:
# - normal = Well, normal. Options are parsed normally.
# - unk = An argument that can't be completed, and should just be skipped.
# - warning = A warning flag.
# - dir = A directory path
# - glob-* = A glob, after the dash is a whitespace-separated list of file globs to use
local state=normal
# The length of the option, used as a return value by the function below
local optlen=0
# $1: a short option word
# `state` will be set to the parsing state after the last option character in the word. If
# "normal" is not returned, `optlen` will be set to the length (dash included) of the "option"
# part of the argument.
parse_short_opt() {
for (( i = 1; i < "${#1}"; i++ )); do
# If the option is not known, assume it doesn't take an argument
local opt="${opts["${1:$i:1}"]:-":normal"}"
state="${opt#*:}"
# If the option takes an argument, record the length and exit
if [[ "$state" != 'normal' ]]; then
let optlen="$i + 1"
return
fi
done
optlen=0
}
for (( i = 1; i < $COMP_CWORD; i++ )); do
local word="${COMP_WORDS[$i]}"
# If currently processing an argument, skip this word
if [[ "$state" != 'normal' ]]; then
state=normal
continue
fi
if [[ "$word" = '--' ]]; then
# Options stop being parsed after this
opt_ena=false
break
fi
# Check if it's a long option
if [[ "${word:0:2}" = '--' ]]; then
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
for long_opt in "${opts[@]}"; do
if [[ "$word" = "--${long_opt%%:*}" ]]; then
state="${long_opt#*:}"
# Check if the next word is just '='; if so, skip it, the argument must follow
# (See "known bugs" at the top of this script)
let i++
if [[ "${COMP_WORDS[$i]}" != '=' ]]; then
let i--
fi
optlen=0
break
fi
done
# Check if it's a short option
elif [[ "${word:0:1}" = '-' ]]; then
parse_short_opt "$word"
# The last option takes an argument...
if [[ "$state" != 'normal' ]]; then
if [[ "$optlen" -ne "${#word}" ]]; then
# If it's contained within the word, we won't complete it, revert to "normal"
state=normal
else
# Otherwise, complete it, but start at the beginning of *that* word
optlen=0
fi
fi
fi
done
# Parse current word
# Careful that it might look like an option, so use `--` aggressively!
local cur_word="${COMP_WORDS[$COMP_CWORD]}"
# Process options, as short ones may change the state
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then
# We might want to complete to an option or an arg to that option
# Parse the option word to check
# There's no whitespace in the option names, so we can ride a little dirty...
# Is this a long option?
if [[ "${cur_word:1:1}" = '-' ]]; then
# It is, try to complete one
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") )
return 0
else
# Short options may be grouped, parse them to determine what to complete
parse_short_opt "$cur_word"
if [[ "$state" = 'normal' ]]; then
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') )
return 0
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
# This short option group only awaits its argument!
# Post the option group as-is as a reply so that Readline inserts a space,
# so that the next completion request switches to the argument
# An exception is made for warnings, since it's idiomatic to stick them to the
# `-W`, and it doesn't break anything.
COMPREPLY+=( "$cur_word" )
return 0
fi
fi
fi
case "$state" in
unk) # Return with no replies: no idea what to complete!
;;
fix-spec)
COMPREPLY+=( "${cur_word}"{l,h,g,L,H,G} )
;;
mbc)
local cur_arg="${cur_word:$optlen}"
cur_arg="${cur_arg@U}"
COMPREPLY=( $(compgen -W "
ROM_ONLY
MBC1{,+RAM,+RAM+BATTERY}
MBC2{,+BATTERY}
MMM01{,+RAM}
MBC3{+TIMER+BATTERY,+TIMER+RAM+BATTERY,,+RAM,+RAM+BATTERY}
MBC5{,+RAM,+RAM+BATTERY,+RUMBLE,+RUMBLE+RAM,+RUMBLE+RAM+BATTERY}
MBC6
MBC7+SENSOR+RUMBLE+RAM+BATTERY
POCKET_CAMERA
BANDAI_TAMA5
HUC3
HUC1+RAM+BATTERY
TPP1_1.0{,+BATTERY}{,+RTC}{,+RUMBLE,+MULTIRUMBLE}" -P "${cur_word:0:$optlen}" -- "`tr 'a-z ' 'A-Z_' <<<"${cur_word/ /_}"`") )
COMPREPLY+=( $(compgen -W "help" -P "${cur_word:0:$optlen}" -- "${cur_word:$optlen}") )
;;
normal) # Acts like a glob...
state="glob-*.gb *.gbc *.sgb"
;&
glob-*)
while read -r word; do
COMPREPLY+=("${cur_word:0:$optlen}$word")
done < <(for glob in ${state#glob-}; do compgen -A file -X \!"$glob" -- "${cur_word:$optlen}"; done)
# Also complete directories
;&
dir)
while read -r word; do
COMPREPLY+=("${cur_word:0:$optlen}$word")
done < <(compgen -A directory -- "${cur_word:$optlen}")
compopt -o filenames
;;
esac
}
complete -F _rgbfix_completions rgbfix

162
contrib/bash_compl/_rgbgfx.bash Executable file
View File

@@ -0,0 +1,162 @@
#/usr/bin/env bash
# Same notes as RGBASM
_rgbgfx_completions() {
COMPREPLY=()
# Format: "long_opt:state_after"
# Empty long opt = it doesn't exit
# See the `state` variable below for info about `state_after`
declare -A opts=(
[V]="version:normal"
[C]="color-curve:normal"
[D]="debug:normal"
[h]="horizontal:normal"
[m]="mirror-tiles:normal"
[u]="unique-tiles:normal"
[v]="verbose:normal"
[f]="fix:normal"
[F]="fix-and-save:normal"
[a]="attr-map:*.attrmap"
[A]="output-attr-map:normal"
[d]="depth:unk"
[o]="output:glob *.2bpp"
[p]="palette:glob *.pal"
[P]="output-palette:normal"
[t]="tilemap:glob *.tilemap"
[T]="output-tilemap:normal"
[x]="trim-end:unk"
)
# Parse command-line up to current word
local opt_ena=true
# Possible states:
# - normal = Well, normal. Options are parsed normally.
# - unk = An argument that can't be completed, and should just be skipped.
# - warning = A warning flag.
# - dir = A directory path
# - glob-* = A glob, after the dash is a whitespace-separated list of file globs to use
local state=normal
# The length of the option, used as a return value by the function below
local optlen=0
# $1: a short option word
# `state` will be set to the parsing state after the last option character in the word. If
# "normal" is not returned, `optlen` will be set to the length (dash included) of the "option"
# part of the argument.
parse_short_opt() {
for (( i = 1; i < "${#1}"; i++ )); do
# If the option is not known, assume it doesn't take an argument
local opt="${opts["${1:$i:1}"]:-":normal"}"
state="${opt#*:}"
# If the option takes an argument, record the length and exit
if [[ "$state" != 'normal' ]]; then
let optlen="$i + 1"
return
fi
done
optlen=0
}
for (( i = 1; i < $COMP_CWORD; i++ )); do
local word="${COMP_WORDS[$i]}"
# If currently processing an argument, skip this word
if [[ "$state" != 'normal' ]]; then
state=normal
continue
fi
if [[ "$word" = '--' ]]; then
# Options stop being parsed after this
opt_ena=false
break
fi
# Check if it's a long option
if [[ "${word:0:2}" = '--' ]]; then
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
for long_opt in "${opts[@]}"; do
if [[ "$word" = "--${long_opt%%:*}" ]]; then
state="${long_opt#*:}"
# Check if the next word is just '='; if so, skip it, the argument must follow
# (See "known bugs" at the top of this script)
let i++
if [[ "${COMP_WORDS[$i]}" != '=' ]]; then
let i--
fi
optlen=0
break
fi
done
# Check if it's a short option
elif [[ "${word:0:1}" = '-' ]]; then
parse_short_opt "$word"
# The last option takes an argument...
if [[ "$state" != 'normal' ]]; then
if [[ "$optlen" -ne "${#word}" ]]; then
# If it's contained within the word, we won't complete it, revert to "normal"
state=normal
else
# Otherwise, complete it, but start at the beginning of *that* word
optlen=0
fi
fi
fi
done
# Parse current word
# Careful that it might look like an option, so use `--` aggressively!
local cur_word="${COMP_WORDS[$COMP_CWORD]}"
# Process options, as short ones may change the state
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then
# We might want to complete to an option or an arg to that option
# Parse the option word to check
# There's no whitespace in the option names, so we can ride a little dirty...
# Is this a long option?
if [[ "${cur_word:1:1}" = '-' ]]; then
# It is, try to complete one
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") )
return 0
else
# Short options may be grouped, parse them to determine what to complete
parse_short_opt "$cur_word"
if [[ "$state" = 'normal' ]]; then
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') )
return 0
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
# This short option group only awaits its argument!
# Post the option group as-is as a reply so that Readline inserts a space,
# so that the next completion request switches to the argument
# An exception is made for warnings, since it's idiomatic to stick them to the
# `-W`, and it doesn't break anything.
COMPREPLY+=( "$cur_word" )
return 0
fi
fi
fi
case "$state" in
unk) # Return with no replies: no idea what to complete!
;;
normal) # Acts like a glob...
state="glob-*.png"
;&
glob-*)
while read -r word; do
COMPREPLY+=("${cur_word:0:$optlen}$word")
done < <(for glob in ${state#glob-}; do compgen -A file -X \!"$glob" -- "${cur_word:$optlen}"; done)
# Also complete directories
;&
dir)
while read -r word; do
COMPREPLY+=("${cur_word:0:$optlen}$word")
done < <(compgen -A directory -- "${cur_word:$optlen}")
compopt -o filenames
;;
esac
}
complete -F _rgbgfx_completions rgbgfx

157
contrib/bash_compl/_rgblink.bash Executable file
View File

@@ -0,0 +1,157 @@
#/usr/bin/env bash
# Same notes as RGBASM
_rgblink_completions() {
COMPREPLY=()
# Format: "long_opt:state_after"
# Empty long opt = it doesn't exit
# See the `state` variable below for info about `state_after`
declare -A opts=(
[V]="version:normal"
[d]="dmg:normal"
[t]="tiny:normal"
[v]="verbose:normal"
[w]="wramx:normal"
[x]="nopad:normal"
[l]="linkerscript:glob-*"
[m]="map:glob-*.map"
[n]="sym:glob-*.sym"
[O]="overlay:glob-*.gb *.gbc *.sgb"
[o]="output:glob-*.gb *.gbc *.sgb"
[p]="pad:unk"
[s]="smart:unk"
)
# Parse command-line up to current word
local opt_ena=true
# Possible states:
# - normal = Well, normal. Options are parsed normally.
# - unk = An argument that can't be completed, and should just be skipped.
# - warning = A warning flag.
# - dir = A directory path
# - glob-* = A glob, after the dash is a whitespace-separated list of file globs to use
local state=normal
# The length of the option, used as a return value by the function below
local optlen=0
# $1: a short option word
# `state` will be set to the parsing state after the last option character in the word. If
# "normal" is not returned, `optlen` will be set to the length (dash included) of the "option"
# part of the argument.
parse_short_opt() {
for (( i = 1; i < "${#1}"; i++ )); do
# If the option is not known, assume it doesn't take an argument
local opt="${opts["${1:$i:1}"]:-":normal"}"
state="${opt#*:}"
# If the option takes an argument, record the length and exit
if [[ "$state" != 'normal' ]]; then
let optlen="$i + 1"
return
fi
done
optlen=0
}
for (( i = 1; i < $COMP_CWORD; i++ )); do
local word="${COMP_WORDS[$i]}"
# If currently processing an argument, skip this word
if [[ "$state" != 'normal' ]]; then
state=normal
continue
fi
if [[ "$word" = '--' ]]; then
# Options stop being parsed after this
opt_ena=false
break
fi
# Check if it's a long option
if [[ "${word:0:2}" = '--' ]]; then
# If the option is unknown, assume it takes no arguments: keep the state at "normal"
for long_opt in "${opts[@]}"; do
if [[ "$word" = "--${long_opt%%:*}" ]]; then
state="${long_opt#*:}"
# Check if the next word is just '='; if so, skip it, the argument must follow
# (See "known bugs" at the top of this script)
let i++
if [[ "${COMP_WORDS[$i]}" != '=' ]]; then
let i--
fi
optlen=0
break
fi
done
# Check if it's a short option
elif [[ "${word:0:1}" = '-' ]]; then
parse_short_opt "$word"
# The last option takes an argument...
if [[ "$state" != 'normal' ]]; then
if [[ "$optlen" -ne "${#word}" ]]; then
# If it's contained within the word, we won't complete it, revert to "normal"
state=normal
else
# Otherwise, complete it, but start at the beginning of *that* word
optlen=0
fi
fi
fi
done
# Parse current word
# Careful that it might look like an option, so use `--` aggressively!
local cur_word="${COMP_WORDS[$COMP_CWORD]}"
# Process options, as short ones may change the state
if $opt_ena && [[ "$state" = 'normal' && "${cur_word:0:1}" = '-' ]]; then
# We might want to complete to an option or an arg to that option
# Parse the option word to check
# There's no whitespace in the option names, so we can ride a little dirty...
# Is this a long option?
if [[ "${cur_word:1:1}" = '-' ]]; then
# It is, try to complete one
COMPREPLY+=( $(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}") )
return 0
else
# Short options may be grouped, parse them to determine what to complete
parse_short_opt "$cur_word"
if [[ "$state" = 'normal' ]]; then
COMPREPLY+=( $(compgen -W "${!opts[*]}" -P "$cur_word" '') )
return 0
elif [[ "$optlen" = "${#cur_word}" && "$state" != "warning" ]]; then
# This short option group only awaits its argument!
# Post the option group as-is as a reply so that Readline inserts a space,
# so that the next completion request switches to the argument
# An exception is made for warnings, since it's idiomatic to stick them to the
# `-W`, and it doesn't break anything.
COMPREPLY+=( "$cur_word" )
return 0
fi
fi
fi
case "$state" in
unk) # Return with no replies: no idea what to complete!
;;
normal) # Acts like a glob...
state="glob-*.o *.obj"
;&
glob-*)
while read -r word; do
COMPREPLY+=("${cur_word:0:$optlen}$word")
done < <(for glob in ${state#glob-}; do compgen -A file -X \!"$glob" -- "${cur_word:$optlen}"; done)
# Also complete directories
;&
dir)
while read -r word; do
COMPREPLY+=("${cur_word:0:$optlen}$word")
done < <(compgen -A directory -- "${cur_word:$optlen}")
compopt -o filenames
;;
esac
}
complete -F _rgblink_completions rgblink

85
contrib/checkdiff.bash Executable file
View File

@@ -0,0 +1,85 @@
#!/bin/bash
# SPDX-License-Identifier: MIT
#
# Copyright (c) 2021 Rangi
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
declare -A FILES
while read -r -d '' file; do
FILES["$file"]="true"
done < <(git diff --name-only -z $1 HEAD)
edited () {
${FILES["$1"]:-"false"}
}
dependency () {
if edited "$1" && ! edited "$2"; then
echo "'$1' was modified, but not '$2'! $3" | xargs
fi
}
# Pull requests that edit the first file without the second may be correct,
# but are suspicious enough to require review.
dependency include/linkdefs.h src/rgbds.5 \
"Was the object file format changed?"
dependency src/asm/parser.y src/asm/rgbasm.5 \
"Was the rgbasm grammar changed?"
dependency include/asm/warning.h src/asm/rgbasm.1 \
"Were the rgbasm warnings changed?"
dependency src/asm/object.c include/linkdefs.h \
"Should the object file revision be bumped?"
dependency src/link/object.c include/linkdefs.h \
"Should the object file revision be bumped?"
dependency Makefile CMakeLists.txt \
"Did the build process change?"
dependency Makefile src/CMakeLists.txt \
"Did the build process change?"
dependency src/asm/main.c src/asm/rgbasm.1 \
"Did the rgbasm CLI change?"
dependency src/asm/main.c contrib/zsh_compl/_rgbasm \
"Did the rgbasm CLI change?"
dependency src/asm/main.c contrib/bash_compl/_rgbasm.bash \
"Did the rgbasm CLI change?"
dependency src/link/main.c src/link/rgblink.1 \
"Did the rgblink CLI change?"
dependency src/link/main.c contrib/zsh_compl/_rgblink \
"Did the rgblink CLI change?"
dependency src/link/main.c contrib/bash_compl/_rgblink.bash \
"Did the rgblink CLI change?"
dependency src/fix/main.c src/fix/rgbfix.1 \
"Did the rgbfix CLI change?"
dependency src/fix/main.c contrib/zsh_compl/_rgbfix \
"Did the rgbfix CLI change?"
dependency src/fix/main.c contrib/bash_compl/_rgbfix.bash \
"Did the rgbfix CLI change?"
dependency src/gfx/main.c src/gfx/rgbgfx.1 \
"Did the rgbgfx CLI change?"
dependency src/gfx/main.c contrib/zsh_compl/_rgbgfx \
"Did the rgbgfx CLI change?"
dependency src/gfx/main.c contrib/bash_compl/_rgbgfx.bash \
"Did the rgbgfx CLI change?"

View File

@@ -9,18 +9,26 @@ _rgbasm_warnings() {
'everything:Enable literally everything'
'assert:Warn when WARN-type asserts fail'
'backwards-for:Warn when start and stop are backwards relative to step'
'builtin-args:Report incorrect args to built-in funcs'
'charmap-redef:Warn when redefining a charmap mapping'
'div:Warn when dividing the smallest int by -1'
'empty-entry:Warn on empty entries in db, dw, dl args'
'empty-data-directive:Warn on arg-less d[bwl] in ROM'
'empty-macro-arg:Warn on empty macro arg'
'empty-strrpl:Warn on calling STRRPL with empty pattern'
'large-constant:Warn on constants too large for a signed 32-bit int'
'long-string:Warn on strings too long'
'macro-shift:Warn when shifting macro args part their limits'
'nested-comment:Warn on "/*" inside block comments'
'numeric-string:Warn when a multi-character string is treated as a number'
'obsolete:Warn when using deprecated features'
'shift:Warn when shifting negative values'
'shift-amount:Warn when a shift'\''s operand it negative or \> 32'
'truncation:Warn when implicit truncations lose bits'
'truncation:Warn when implicit truncation loses bits'
'user:Warn when executing the WARN built-in'
)
# TODO: handle `no-` and `error=` somehow?
# TODO: handle `=0|1|2` levels for `numeric-string` and `truncation`?
_describe warning warnings
}
@@ -35,15 +43,19 @@ local args=(
-w'[Disable all warnings]'
'(-b --binary-digits)'{-b,--binary-digits}'+[Change chars for binary constants]:digit spec:'
'(-D --define)'{-D,--define}'+[Define a string symbol]:name + value (default 1):'
'*'{-D,--define}'+[Define a string symbol]:name + value (default 1):'
'(-g --gfx-chars)'{-g,--gfx-chars}'+[Change chars for gfx constants]:chars spec:'
'(-i --include)'{-i,--include}'+[Add an include directory]:include path:_files -/'
'(-M --dependfile)'{-M,--dependfile}"+[List deps in make format]:output file:_files -g '*.{d,mk}"
'(-M --dependfile)'{-M,--dependfile}"+[List deps in make format]:output file:_files -g '*.{d,mk}'"
-MG'[Assume missing files should be generated]'
-MP'[Add phony targets to all deps]'
'*'-MT"+[Add a target to the rules]:target:_files -g '*.{d,mk,o}'"
'*'-MQ"+[Add a target to the rules]:target:_files -g '*.{d,mk,o}'"
'(-o --output)'{-o,--output}'+[Output file]:output file:_files'
'(-p --pad-value)'{-p,--pad-value}'+[Set padding byte]:padding byte:'
'(-r --recursion-depth)'{-r,--recursion-depth}'+[Set maximum recursion depth]:depth:'
'(-W --warning)'{-W,--warning}'+[Toggle warning flags]:warning flag:_rgbasm_warnings'
'*'":assembly sources:_files -g '*.asm'"
":assembly sources:_files -g '*.asm'"
)
_arguments -s -S : $args

View File

@@ -1,12 +1,47 @@
#compdef rgbfix
_mbc_names() {
local mbc_names=(
'ROM:$00'
'MBC1:$01'
'MBC1+RAM:$02'
'MBC1+RAM+BATTERY:$03'
'MBC2:$05'
'MBC2+BATTERY:$06'
'ROM+RAM:$08'
'ROM+RAM+BATTERY:$09'
'MMM01:$0B'
'MMM01+RAM:$0C'
'MMM01+RAM+BATTERY:$0D'
'MBC3+TIMER+BATTERY:$0F'
'MBC3+TIMER+RAM+BATTERY:$10'
'MBC3:$11'
'MBC3+RAM:$12'
'MBC3+RAM+BATTERY:$13'
'MBC5:$19'
'MBC5+RAM:$1A'
'MBC5+RAM+BATTERY:$1B'
'MBC5+RUMBLE:$1C'
'MBC5+RUMBLE+RAM:$1D'
'MBC5+RUMBLE+RAM+BATTERY:$1E'
'MBC6:$20'
'MBC7+SENSOR+RUMBLE+RAM+BATTERY:$22'
'POCKET_CAMERA:$FC'
'BANDAI_TAMA5:$FD'
'HUC3:$FE'
'HUC1+RAM+BATTERY:$FF'
)
_describe "MBC name" mbc_names
}
local args=(
# Arguments are listed here in the same order as in the manual, except for the version
'(- : * options)'{-V,--version}'[Print version number]'
'(-C --color-only)'{-C,--color-only}'[Mark ROM as GBC-only]'
'(-c --color-compatible)'{-c,--color-compatible}'[Mark ROM as GBC-compatible]'
'(-C --color-only -c --color-compatible)'{-C,--color-only}'[Mark ROM as GBC-only]'
'(-C --color-only -c --color-compatible)'{-c,--color-compatible}'[Mark ROM as GBC-compatible]'
'(-j --non-japanese)'{-j,--non-japanese}'[Set the non-Japanese region flag]'
'(-O --overwrite)'{-O,--overwrite}'[Allow overwriting non-zero bytes]'
'(-s --sgb-compatible)'{-s,--sgb-compatible}'[Set the SGB flag]'
'(-f --fix-spec -v --validate)'{-v,--validate}'[Shorthand for -f lhg]'
@@ -14,7 +49,7 @@ local args=(
'(-i --game-id)'{-i,--game-id}'+[Set game ID string]:4-char game ID:'
'(-k --new-licensee)'{-k,--new-licensee}'+[Set new licensee string]:2-char licensee ID:'
'(-l --old-licensee)'{-l,--old-licensee}'+[Set old licensee ID]:licensee number:'
'(-m --mbc-type)'{-m,--mbc-type}'+[Set MBC flags]:mbc flags byte:'
'(-m --mbc-type)'{-m,--mbc-type}"+[Set MBC flags]:mbc name:_mbc_names"
'(-n --rom-version)'{-n,--rom-version}'+[Set ROM version]:rom version byte:'
'(-p --pad-value)'{-p,--pad-value}'+[Pad to next valid size using this byte as padding]:padding byte:'
'(-r --ram-size)'{-r,--ram-size}'+[Set RAM size]:ram size byte:'

View File

@@ -32,6 +32,6 @@ local args=(
'(-t --tilemap -T --output-tilemap)'{-t,--tilemap}'+[Generate a map of tile indices]:tilemap file:_files'
'(-x --trim-end)'{-x,--trim-end}'+[Trim end of output by this many tiles]:tile count:'
'*'":input png files:_files -g '*.png'"
":input png file:_files -g '*.png'"
)
_arguments -s -S : $args

View File

@@ -8,6 +8,7 @@ local args=(
'(-t --tiny)'{-t,--tiny}'[Enable tiny mode, disabling ROM banking]'
'(-v --verbose)'{-v,--verbose}'[Enable verbose output]'
'(-w --wramx)'{-w,--wramx}'[Disable WRAM banking]'
'(-x --nopad)'{-x,--nopad}'[Disable padding the end of the final file]'
'(-l --linkerscript)'{-l,--linkerscript}"+[Use a linker script]:linker script:_files -g '*.link'"
'(-m --map)'{-m,--map}"+[Produce a map file]:map file:_files -g '*.map'"
@@ -15,6 +16,7 @@ local args=(
'(-O --overlay)'{-O,--overlay}'+[Overlay sections over on top of bin file]:base overlay:_files'
'(-o --output)'{-o,--output}"+[Write ROM image to this file]:rom file:_files -g '*.{gb,sgb,gbc}'"
'(-p --pad-value)'{-p,--pad-value}'+[Set padding byte]:padding byte:'
'(-S --scramble)'{-s,--scramble}'+[Activate scrambling]:scramble spec'
'(-s --smart)'{-s,--smart}'+[!BROKEN! Perform smart linking from this symbol]:symbol name:'
'*'":object files:_files -g '*.o'"

View File

@@ -11,12 +11,13 @@
#include <stdint.h>
struct Charmap *charmap_New(const char *name, const char *baseName);
struct Charmap *charmap_New(char const *name, char const *baseName);
void charmap_Delete(struct Charmap *charmap);
void charmap_Set(const char *name);
void charmap_Set(char const *name);
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

@@ -19,7 +19,6 @@
#include "asm/lexer.h"
#include "types.h"
struct FileStackNode {
struct FileStackNode *parent; /* Pointer to parent node, for error reporting */
@@ -49,7 +48,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 +71,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 object 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

@@ -13,6 +13,7 @@
#include <stdbool.h>
#include "linkdefs.h"
#include "platform.h" // NONNULL
extern uint8_t fillByte;
@@ -40,14 +41,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(char const *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 +60,23 @@ 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, size_t length);
void sect_AbsWordGroup(uint8_t const *s, size_t length);
void sect_AbsLongGroup(uint8_t const *s, size_t length);
void sect_Skip(uint32_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);
bool sect_IsSizeKnown(struct Section const NONNULL(name));
#endif

View File

@@ -17,15 +17,13 @@
#include "asm/section.h"
#include "platform.h" // MIN_NB_ELMS
#include "types.h"
#define HASHSIZE (1 << 16)
#define MAXSYMLEN 256
#define MAXSYMLEN 255
enum SymbolType {
SYM_LABEL,
SYM_EQU,
SYM_SET,
SYM_VAR,
SYM_MACRO,
SYM_EQUS,
SYM_REF // Forward reference to a label
@@ -45,13 +43,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) */
@@ -77,13 +75,12 @@ static inline bool sym_IsConstant(struct Symbol const *sym)
return sect && sect->org != (uint32_t)-1;
}
return sym->type == SYM_EQU || sym->type == SYM_SET;
return sym->type == SYM_EQU || sym->type == SYM_VAR;
}
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_VAR;
}
static inline bool sym_IsLabel(struct Symbol const *sym)
@@ -121,22 +118,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_AddSet(char const *symName, int32_t value);
struct Symbol *sym_RedefEqu(char const *symName, int32_t value);
struct Symbol *sym_AddVar(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,42 +13,65 @@
extern unsigned int nbErrors;
enum WarningState {
WARNING_DEFAULT,
WARNING_DISABLED,
WARNING_ENABLED,
WARNING_ERROR
};
enum WarningID {
WARNING_ASSERT, /* Assertions */
WARNING_BUILTIN_ARG, /* Invalid args to builtins */
WARNING_CHARMAP_REDEF, /* Charmap entry re-definition */
WARNING_DIV, /* Division undefined behavior */
WARNING_EMPTY_DATA_DIRECTIVE, /* `db`, `dw` or `dl` directive without data in ROM */
WARNING_EMPTY_MACRO_ARG, /* Empty macro argument */
WARNING_EMPTY_STRRPL, /* Empty second argument in `STRRPL` */
WARNING_LARGE_CONSTANT, /* Constants too large */
WARNING_LONG_STR, /* String too long for internal buffers */
WARNING_MACRO_SHIFT, /* Shift past available arguments in macro */
WARNING_NESTED_COMMENT, /* Comment-start delimiter in a block comment */
WARNING_OBSOLETE, /* Obsolete things */
WARNING_SHIFT, /* Shifting undefined behavior */
WARNING_SHIFT_AMOUNT, /* Strange shift amount */
WARNING_TRUNCATION, /* Implicit truncation loses some bits */
WARNING_USER, /* User warnings */
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
WARNING_EMPTY_DATA_DIRECTIVE, // `db`, `dw` or `dl` directive without data in ROM
WARNING_EMPTY_MACRO_ARG, // Empty macro argument
WARNING_EMPTY_STRRPL, // Empty second argument in `STRRPL`
WARNING_LARGE_CONSTANT, // Constants too large
WARNING_LONG_STR, // String too long for internal buffers
WARNING_MACRO_SHIFT, // Shift past available arguments in macro
WARNING_NESTED_COMMENT, // Comment-start delimiter in a block comment
WARNING_OBSOLETE, // Obsolete things
WARNING_SHIFT, // Shifting undefined behavior
WARNING_SHIFT_AMOUNT, // Strange shift amount
WARNING_USER, // User warnings
NB_WARNINGS,
NB_PLAIN_WARNINGS,
/* Warnings past this point are "meta" warnings */
WARNING_ALL = NB_WARNINGS,
// Warnings past this point are "parametric" warnings, only mapping to a single flag
#define PARAM_WARNINGS_START NB_PLAIN_WARNINGS
// Treating string as number may lose some bits
WARNING_NUMERIC_STRING_1 = PARAM_WARNINGS_START,
WARNING_NUMERIC_STRING_2,
// Implicit truncation loses some bits
WARNING_TRUNCATION_1,
WARNING_TRUNCATION_2,
NB_PLAIN_AND_PARAM_WARNINGS,
#define NB_PARAM_WARNINGS (NB_PLAIN_AND_PARAM_WARNINGS - PARAM_WARNINGS_START)
// Warnings past this point are "meta" warnings
#define META_WARNINGS_START NB_PLAIN_AND_PARAM_WARNINGS
WARNING_ALL = META_WARNINGS_START,
WARNING_EXTRA,
WARNING_EVERYTHING,
NB_WARNINGS_ALL
#define NB_META_WARNINGS (NB_WARNINGS_ALL - NB_WARNINGS)
NB_WARNINGS,
#define NB_META_WARNINGS (NB_WARNINGS - META_WARNINGS_START)
};
void processWarningFlag(char const *flag);
extern enum WarningState warningStates[NB_PLAIN_AND_PARAM_WARNINGS];
extern bool warningsAreErrors;
void processWarningFlag(char *flag);
/*
* Used to warn the user about problems that don't prevent the generation of
* valid code.
*/
void warning(enum WarningID id, const char *fmt, ...) format_(printf, 2, 3);
void warning(enum WarningID id, char const *fmt, ...) format_(printf, 2, 3);
/*
* Used for errors that compromise the whole assembly process by affecting the
@@ -57,7 +80,7 @@ void warning(enum WarningID id, const char *fmt, ...) format_(printf, 2, 3);
* It is also used when the assembler goes into an invalid state (for example,
* when it fails to allocate memory).
*/
_Noreturn void fatalerror(const char *fmt, ...) format_(printf, 1, 2);
_Noreturn void fatalerror(char const *fmt, ...) format_(printf, 1, 2);
/*
* Used for errors that make it impossible to assemble correctly, but don't
@@ -65,6 +88,6 @@ _Noreturn void fatalerror(const char *fmt, ...) format_(printf, 1, 2);
* get a list of all errors at the end, making it easier to fix all of them at
* once.
*/
void error(const char *fmt, ...) format_(printf, 1, 2);
void error(char const *fmt, ...) format_(printf, 1, 2);
#endif

21
include/error.h Normal file
View File

@@ -0,0 +1,21 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2021, RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef RGBDS_ERROR_H
#define RGBDS_ERROR_H
#include "helpers.h"
#include "platform.h"
void warn(char const NONNULL(fmt), ...) format_(printf, 1, 2);
void warnx(char const NONNULL(fmt), ...) format_(printf, 1, 2);
_Noreturn void err(char const NONNULL(fmt), ...) format_(printf, 1, 2);
_Noreturn void errx(char const NONNULL(fmt), ...) format_(printf, 1, 2);
#endif /* RGBDS_ERROR_H */

44
include/extern/err.h vendored
View File

@@ -1,44 +0,0 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2018, RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef EXTERN_ERR_H
#define EXTERN_ERR_H
#ifdef ERR_IN_LIBC
#include <err.h>
#else /* ERR_IN_LIBC */
#include <stdarg.h>
#include "helpers.h"
#define warn rgbds_warn
#define vwarn rgbds_vwarn
#define warnx rgbds_warnx
#define vwarnx rgbds_vwarnx
#define err rgbds_err
#define verr rgbds_verr
#define errx rgbds_errx
#define verrx rgbds_verrx
void warn(const char *fmt, ...) format_(printf, 1, 2);
void vwarn(const char *fmt, va_list ap) format_(printf, 1, 0);
void warnx(const char *fmt, ...) format_(printf, 1, 2);
void vwarnx(const char *fmt, va_list ap) format_(printf, 1, 0);
_Noreturn void err(int status, const char *fmt, ...) format_(printf, 2, 3);
_Noreturn void verr(int status, const char *fmt, va_list ap) format_(printf, 2, 0);
_Noreturn void errx(int status, const char *fmt, ...) format_(printf, 2, 3);
_Noreturn void verrx(int status, const char *fmt, va_list ap) format_(printf, 2, 0);
#endif /* ERR_IN_LIBC */
#endif /* EXTERN_ERR_H */

View File

@@ -30,13 +30,14 @@ extern char *musl_optarg;
extern int musl_optind, musl_opterr, musl_optopt, musl_optreset;
struct option {
const char *name;
char const *name;
int has_arg;
int *flag;
int val;
};
int musl_getopt_long_only(int, char **, const char *, const struct option *, int *);
int musl_getopt_long_only(int argc, char **argv, char const *optstring,
const struct option *longopts, int *idx);
#define no_argument 0
#define required_argument 1

View File

@@ -13,7 +13,7 @@
#include <stdbool.h>
#include <stdint.h>
#include "extern/err.h"
#include "error.h"
struct Options {
bool debug;

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)
{
@@ -88,4 +89,8 @@
#define STR(x) #x
#define EXPAND_AND_STR(x) STR(x)
// Obtaining the size of an array; `arr` must be an expression, not a type!
// (Having two instances of `arr` is OK because the contents of `sizeof` are not evaluated.)
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof *(arr))
#endif /* HELPERS_H */

View File

@@ -24,6 +24,9 @@ extern char const *symFileName;
extern char const *overlayFileName;
extern char const *outputFileName;
extern uint8_t padValue;
extern uint16_t scrambleROMX;
extern uint8_t scrambleWRAMX;
extern uint8_t scrambleSRAM;
extern bool is32kMode;
extern bool beVerbose;
extern bool isWRA0Mode;

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

@@ -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>
@@ -48,8 +49,10 @@
/* MSVC doesn't support `[static N]` for array arguments from C99 */
#ifdef _MSC_VER
# define MIN_NB_ELMS(N)
# define NONNULL(ptr) *ptr
#else
# define MIN_NB_ELMS(N) static (N)
# define NONNULL(ptr) ptr[static 1]
#endif
// MSVC uses a different name for O_RDWR, and needs an additional _O_BINARY flag

View File

@@ -1,16 +0,0 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef RGBDS_TYPES_H
#define RGBDS_TYPES_H
#ifndef _MAX_PATH
#define _MAX_PATH 512
#endif
#endif /* RGBDS_TYPES_H */

View File

@@ -11,9 +11,8 @@
#define PACKAGE_VERSION_MAJOR 0
#define PACKAGE_VERSION_MINOR 5
#define PACKAGE_VERSION_PATCH 0
#define PACKAGE_VERSION_RC 2
#define PACKAGE_VERSION_PATCH 2
const char *get_package_version_string(void);
char const *get_package_version_string(void);
#endif /* EXTERN_VERSION_H */

View File

@@ -7,7 +7,7 @@
#
set(common_src
"extern/err.c"
"error.c"
"extern/getopt.c"
"version.c"
)

View File

@@ -44,38 +44,41 @@ 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));
}
struct Charmap *charmap_New(const char *name, const char *baseName)
struct Charmap *charmap_New(char const *name, char const *baseName)
{
struct Charmap *base = NULL;
@@ -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;
}
@@ -118,9 +120,9 @@ void charmap_Delete(struct Charmap *charmap)
free(charmap);
}
void charmap_Set(const char *name)
void charmap_Set(char const *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,56 @@ 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]) {
// Consume that char
(*input)++;
rewindDistance++;
node = &currentCharmap->nodes[node->next[c]];
// Advance to next node (index starts at 1)
node = &charmap->nodes[node->next[c]];
if (node->isTerminal) {
// This node matches, register it
match = node;
rewindDistance = 0; /* Rewind from after the match */
rewindDistance = 0; // If no longer match is found, rewind here
}
} else {
input -= rewindDistance; /* Rewind */
rewindDistance = 0;
node = &currentCharmap->nodes[0];
// We are at a dead end (either because we reached the end of input, or of
// the trie), so rewind up to the last match, and output.
*input -= rewindDistance; // This will rewind all the way if no match found
if (match) { /* Arrived at a dead end with a match found */
*output++ = match->value;
outputLen++;
match = NULL; /* Reset match for next round */
if (match) { // A match was found, use it
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, but there is some input left
// This will write the codepoint's value to `output`, little-endian
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;
}

View File

@@ -46,7 +46,7 @@ int32_t fix_Callback_PI(void)
void fix_Print(int32_t i)
{
uint32_t u = i;
const char *sign = "";
char const *sign = "";
if (i < 0) {
u = -u;

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,17 +226,15 @@ 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) {
char spec[16]; /* Max "%" + 5-char PRIu32 + ".%0255.f" + terminator */
snprintf(spec, sizeof(spec), "%%" PRIu32 ".%%0%d.f", fracWidth);
snprintf(valueBuf, sizeof(valueBuf), spec, value >> 16,
(value % 65536) / 65536.0 * pow(10, fracWidth) + 0.5);
} else {
snprintf(valueBuf, sizeof(valueBuf), "%" PRIu32, value >> 16);
if (fracWidth > 255) {
error("Fractional width %zu too long, limiting to 255\n",
fracWidth);
fracWidth = 255;
}
snprintf(valueBuf, sizeof(valueBuf), "%.*f", (int)fracWidth, value / 65536.0);
} else {
char const *spec = fmt->type == 'd' ? "%" PRId32
: fmt->type == 'u' ? "%" PRIu32
@@ -244,55 +247,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

@@ -23,12 +23,6 @@
#define MAXINCPATHS 128
#ifdef LEXER_DEBUG
#define dbgPrint(...) fprintf(stderr, "[fstack] " __VA_ARGS__)
#else
#define dbgPrint(...)
#endif
struct Context {
struct Context *parent;
struct FileStackNode *fileInfo;
@@ -44,7 +38,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 +137,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);
}
}
@@ -191,6 +185,11 @@ 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 (isPathValid(*fullPath)) {
@@ -201,18 +200,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;
@@ -236,11 +235,11 @@ bool yywrap(void)
/* If this is a FOR, update the symbol value */
if (contextStack->forName && fileInfo->iters[0] <= contextStack->nbReptIters) {
contextStack->forValue += contextStack->forStep;
struct Symbol *sym = sym_AddSet(contextStack->forName,
struct Symbol *sym = sym_AddVar(contextStack->forName,
contextStack->forValue);
/* This error message will refer to the current iteration */
if (sym->type != SYM_SET)
if (sym->type != SYM_VAR)
fatalerror("Failed to update FOR symbol value\n");
}
/* Advance to the next iteration */
@@ -254,7 +253,6 @@ bool yywrap(void)
} else if (!contextStack->parent) {
return true;
}
dbgPrint("Popping context\n");
struct Context *context = contextStack;
@@ -264,10 +262,8 @@ bool yywrap(void)
lexer_DeleteState(context->lexerState);
/* Restore args if a macro (not REPT) saved them */
if (context->fileInfo->type == NODE_MACRO) {
dbgPrint("Restoring macro args %p\n", (void *)contextStack->macroArgs);
if (context->fileInfo->type == NODE_MACRO)
macro_UseNewArgs(contextStack->macroArgs);
}
/* Free the file stack node */
if (!context->fileInfo->referenced)
free(context->fileInfo);
@@ -288,8 +284,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)
@@ -310,24 +306,21 @@ static void newContext(struct FileStackNode *fileInfo)
void fstk_RunInclude(char const *path)
{
dbgPrint("Including path \"%s\"\n", path);
char *fullPath = NULL;
size_t size = 0;
if (!fstk_FindFile(path, &fullPath, &size)) {
free(fullPath);
if (oGeneratedMissingIncludes) {
if (generatedMissingIncludes) {
if (verbose)
printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n",
path, strerror(errno));
oFailedOnMissingInclude = true;
failedOnMissingInclude = true;
} else {
error("Unable to open included file '%s': %s\n", path, strerror(errno));
}
return;
}
dbgPrint("Full path: \"%s\"\n", fullPath);
struct FileStackNamedNode *fileInfo = malloc(sizeof(*fileInfo) + size);
@@ -351,8 +344,6 @@ void fstk_RunInclude(char const *path)
void fstk_RunMacro(char const *macroName, struct MacroArgs *args)
{
dbgPrint("Running macro \"%s\"\n", macroName);
struct Symbol *macro = sym_FindExactSymbol(macroName);
if (!macro) {
@@ -412,7 +403,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");
@@ -446,7 +437,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);
@@ -456,8 +447,6 @@ static bool newReptContext(int32_t reptLineNo, char *body, size_t size)
void fstk_RunRept(uint32_t count, int32_t reptLineNo, char *body, size_t size)
{
dbgPrint("Running REPT(%" PRIu32 ")\n", count);
if (count == 0)
return;
if (!newReptContext(reptLineNo, body, size))
@@ -470,12 +459,9 @@ 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)
{
dbgPrint("Running FOR(\"%s\", %" PRId32 ", %" PRId32 ", %" PRId32 ")\n",
symName, start, stop, step);
struct Symbol *sym = sym_AddVar(symName, start);
struct Symbol *sym = sym_AddSet(symName, start);
if (sym->type != SYM_SET)
if (sym->type != SYM_VAR)
return;
uint32_t count = 0;
@@ -487,6 +473,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))
@@ -508,8 +498,6 @@ void fstk_StopRept(void)
bool fstk_Break(void)
{
dbgPrint("Breaking out of REPT/FOR\n");
if (contextStack->fileInfo->type != NODE_REPT) {
error("BREAK can only be used inside a REPT/FOR block\n");
return false;
@@ -519,7 +507,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);
@@ -561,12 +549,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

@@ -30,39 +30,47 @@
#include "asm/warning.h"
#include "parser.h"
#include "extern/err.h"
#include "extern/getopt.h"
#include "helpers.h"
#include "error.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.
char const *__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);
#if defined(YYDEBUG) && YYDEBUG
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. */
/* Escapes Make-special chars from a string */
static char *make_escape(const char *str)
static char *make_escape(char const *str)
{
char * const escaped_str = malloc(strlen(str) * 2 + 1);
char *dest = escaped_str;
if (escaped_str == NULL)
err(1, "%s: Failed to allocate memory", __func__);
err("%s: Failed to allocate memory", __func__);
while (*str) {
/* All dollars needs to be doubled */
@@ -149,30 +157,26 @@ int main(int argc, char *argv[])
dependfile = NULL;
#if defined(YYDEBUG) && YYDEBUG
yydebug = 1;
#endif
// Perform some init for below
sym_Init(now);
// 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) {
@@ -180,7 +184,7 @@ int main(int argc, char *argv[])
if (strlen(musl_optarg) == 2)
opt_B(&musl_optarg[1]);
else
errx(1, "Must specify exactly 2 characters for option 'b'");
errx("Must specify exactly 2 characters for option 'b'");
break;
char *equals;
@@ -202,7 +206,7 @@ int main(int argc, char *argv[])
if (strlen(musl_optarg) == 4)
opt_G(&musl_optarg[1]);
else
errx(1, "Must specify exactly 4 characters for option 'g'");
errx("Must specify exactly 4 characters for option 'g'");
break;
case 'h':
@@ -214,7 +218,7 @@ int main(int argc, char *argv[])
break;
case 'L':
optimizeloads = false;
optimizeLoads = false;
break;
case 'M':
@@ -223,7 +227,7 @@ int main(int argc, char *argv[])
else
dependfile = fopen(musl_optarg, "w");
if (dependfile == NULL)
err(1, "Could not open dependfile %s", musl_optarg);
err("Could not open dependfile %s", musl_optarg);
break;
case 'o':
@@ -235,19 +239,19 @@ int main(int argc, char *argv[])
fill = strtoul(musl_optarg, &ep, 0);
if (musl_optarg[0] == '\0' || *ep != '\0')
errx(1, "Invalid argument for option 'p'");
errx("Invalid argument for option 'p'");
if (fill < 0 || fill > 0xFF)
errx(1, "Argument for option 'p' must be between 0 and 0xFF");
errx("Argument for option 'p' must be between 0 and 0xFF");
opt_P(fill);
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'");
errx("Invalid argument for option 'r'");
break;
case 'V':
@@ -269,40 +273,30 @@ int main(int argc, char *argv[])
case 0:
switch (depType) {
case 'G':
oGeneratedMissingIncludes = true;
generatedMissingIncludes = true;
break;
case 'P':
oGeneratePhonyDeps = true;
generatePhonyDeps = true;
break;
char *newTarget;
case 'Q':
case 'T':
if (musl_optind == argc)
errx(1, "-M%c takes a target file name argument", depType);
ep = musl_optarg;
newTarget = musl_optarg;
if (depType == 'Q')
ep = make_escape(ep);
newTarget = make_escape(newTarget);
size_t newTargetLen = strlen(newTarget) + 1; // Plus the space
nTargetFileNameLen += strlen(ep) + 1;
if (!tzTargetFileName) {
/* On first alloc, make an empty str */
tzTargetFileName = malloc(nTargetFileNameLen + 1);
if (tzTargetFileName)
*tzTargetFileName = '\0';
} else {
tzTargetFileName = realloc(tzTargetFileName,
nTargetFileNameLen + 1);
}
if (tzTargetFileName == NULL)
err(1, "Cannot append new file to target file list");
strcat(tzTargetFileName, ep);
targetFileName = realloc(targetFileName,
targetFileNameLen + newTargetLen + 1);
if (targetFileName == NULL)
err("Cannot append new file to target file list");
memcpy(&targetFileName[targetFileNameLen], newTarget, newTargetLen);
if (depType == 'Q')
free(ep);
char *ptr = tzTargetFileName + strlen(tzTargetFileName);
*ptr++ = ' ';
*ptr = '\0';
free(newTarget);
targetFileNameLen += newTargetLen;
targetFileName[targetFileNameLen - 1] = ' ';
break;
}
break;
@@ -314,8 +308,10 @@ int main(int argc, char *argv[])
}
}
if (tzTargetFileName == NULL)
tzTargetFileName = tzObjectname;
if (targetFileName == NULL)
targetFileName = objectName;
else
targetFileName[targetFileNameLen - 1] = '\0'; // Overwrite the last space
if (argc == musl_optind) {
fputs("FATAL: No input files\n", stderr);
@@ -331,17 +327,17 @@ int main(int argc, char *argv[])
printf("Assembling %s\n", mainFileName);
if (dependfile) {
if (!tzTargetFileName)
errx(1, "Dependency files can only be created if a target file is specified with either -o, -MQ or -MT\n");
if (!targetFileName)
errx("Dependency files can only be created if a target file is specified with either -o, -MQ or -MT");
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,15 +349,15 @@ int main(int argc, char *argv[])
sect_CheckUnionClosed();
if (nbErrors != 0)
errx(1, "Assembly aborted (%u error%s)!", nbErrors,
errx("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,11 @@ struct OptStackEntry {
char binary[2];
char gbgfx[4];
int32_t fillByte;
bool haltnop;
bool optimizeLoads;
bool warningsAreErrors;
// Don't be confused: we use the size of the **global variable** `warningStates`!
enum WarningState warningStates[sizeof(warningStates)];
struct OptStackEntry *next;
};
@@ -34,6 +39,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 *flag)
{
processWarningFlag(flag);
}
void opt_Parse(char *s)
{
switch (s[0]) {
@@ -66,6 +86,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 +153,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 +177,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

@@ -27,20 +27,19 @@
#include "asm/symbol.h"
#include "asm/warning.h"
#include "extern/err.h"
#include "error.h"
#include "linkdefs.h"
#include "platform.h" // strdup
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 +50,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 +70,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;
@@ -179,7 +177,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 +203,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 +240,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
*/
@@ -286,7 +299,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 +323,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 +345,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 +367,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 +399,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 +447,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 +481,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);
@@ -485,13 +521,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("Couldn't write file '%s'", objectName);
/* Also write symbols that weren't written above */
sym_ForEach(registerUnregisteredSymbol, NULL);
@@ -514,13 +550,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 +574,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

@@ -83,7 +83,7 @@ Add an include path.
Disable the optimization that turns loads of the form
.Ic LD [$FF00+n8],A
into the opcode
.Ic LD [H $FF00+n8],A
.Ic LDH [$FF00+n8],A
in order to have full control of the result in the final ROM.
.It Fl M Ar depend_file , Fl Fl dependfile Ar depend_file
Print
@@ -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
@@ -208,7 +214,7 @@ Warn when re-defining a charmap mapping.
This warning is enabled by
.Fl Wall .
.It Fl Wdiv
Warn when dividing the smallest negative integer by -1, which yields itself due to integer overflow.
Warn when dividing the smallest negative integer (-2**31) by -1, which yields itself due to integer overflow.
.It Fl Wempty-macro-arg
Warn when a macro argument is empty.
This warning is enabled by
@@ -237,15 +243,37 @@ Warn when obsolete constructs such as the
constant or
.Ic PRINTT
directive are encountered.
.It Fl Wnumeric-string=
Warn when a multi-character string is treated as a number.
.Fl Wnumeric-string=0
or
.Fl Wno-numeric-string
disables this warning.
.Fl Wnumeric-string=1
or just
.Fl Wnumeric-string
warns about strings longer than four characters, since four or fewer characters fit within a 32-bit integer.
.Fl Wnumeric-string=2
warns about any multi-character string.
.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
.It Fl Wtruncation=
Warn when an implicit truncation (for example,
.Ic LD [B @] )
loses some bits.
.Ic db
to an 8-bit value) loses some bits.
.Fl Wtruncation=0
or
.Fl Wno-truncation
disables this warning.
.Fl Wtruncation=1
warns when an N-bit value's absolute value is 2**N or greater.
.Fl Wtruncation=2
or just
.Fl Wtruncation
also warns when an N-bit value is less than -2**(N-1), which will not fit in two's complement encoding.
.It Fl Wno-user
Warn when the
.Ic WARN

File diff suppressed because it is too large Load Diff

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 == (uint32_t)-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 != (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,24 +181,63 @@ 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 != (uint32_t)-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);
struct Section *section = sect_FindSectionByName(sectionName);
if (section && sect_IsSizeKnown(section)) {
expr->val = section->size;
} else {
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);
struct Section *section = sect_FindSectionByName(sectionName);
if (section && section->org != (uint32_t)-1) {
expr->val = section->org;
} else {
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);
}
}
@@ -207,13 +247,13 @@ void rpn_CheckHRAM(struct Expression *expr, const struct Expression *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 +263,52 @@ 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) || val >= 1 << n)
warning(WARNING_TRUNCATION_1, "Expression must be %u-bit\n", n);
else if (val < -(1 << (n - 1)))
warning(WARNING_TRUNCATION_2, "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 +317,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 +350,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 +462,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 +475,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 +497,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 +525,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 +536,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 +551,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 +563,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 +574,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 +587,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

@@ -15,79 +15,104 @@
#include "asm/symbol.h"
#include "asm/warning.h"
#include "extern/err.h"
#include "error.h"
#include "platform.h" // strdup
uint8_t fillByte;
struct SectionStackEntry {
struct Section *section;
char const *scope; /* Section's symbol scope */
uint32_t offset;
struct SectionStackEntry *next;
};
struct SectionStackEntry *sectionStack;
uint32_t curOffset; /* Offset into the current section (see sect_GetSymbolOffset) */
static struct Section *currentLoadSection = NULL;
int32_t loadOffset; /* Offset into the LOAD section's parent (see sect_GetOutputOffset) */
struct UnionStackEntry {
uint32_t start;
uint32_t size;
struct UnionStackEntry *next;
} *unionStack = NULL;
struct SectionStackEntry {
struct Section *section;
struct Section *loadSection;
char const *scope; /* Section's symbol scope */
uint32_t offset;
int32_t loadOffset;
struct UnionStackEntry *unionStack;
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) */
/*
* 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(char const *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;
}
@@ -135,9 +160,9 @@ static unsigned int mergeSectUnion(struct Section *sect, enum SectionType type,
/* Check if alignment offsets are compatible */
} else if ((alignOffset & mask(sect->align))
!= (sect->alignOfs & mask(alignment))) {
fail("Section already declared with incompatible %" PRIu8
fail("Section already declared with incompatible %u"
"-byte alignment (offset %" PRIu16 ")\n",
sect->align, sect->alignOfs);
1u << sect->align, sect->alignOfs);
} else if (alignment > sect->align) {
// If the section is not fixed, its alignment is the largest of both
sect->align = alignment;
@@ -188,9 +213,9 @@ static unsigned int mergeFragments(struct Section *sect, enum SectionType type,
PRIx32 "\n", sect->org);
/* Check if alignment offsets are compatible */
} else if ((curOfs & mask(sect->align)) != (sect->alignOfs & mask(alignment))) {
fail("Section already declared with incompatible %" PRIu8
fail("Section already declared with incompatible %u"
"-byte alignment (offset %" PRIu16 ")\n",
sect->align, sect->alignOfs);
1u << sect->align, sect->alignOfs);
} else if (alignment > sect->align) {
// If the section is not fixed, its alignment is the largest of both
sect->align = alignment;
@@ -339,6 +364,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 +376,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 +404,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 +419,38 @@ void out_NewSection(char const *name, uint32_t type, uint32_t org,
changeSection();
curOffset = mod == SECTION_UNION ? 0 : sect->size;
pCurrentSection = sect;
loadOffset = 0; // This is still used when checking for section size overflow!
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();
// Important info: currently, UNION and LOAD cannot interact, since UNION is prohibited in
// "code" sections, whereas LOAD is restricted to them.
// Therefore, any interactions are NOT TESTED, so lift either of those restrictions at
// your own peril! ^^
if (currentLoadSection)
fatalerror("`LOAD` blocks cannot be nested\n");
if (!checkcodesection())
return;
if (sect_HasData(type))
if (currentLoadSection) {
error("`LOAD` blocks cannot be nested\n");
return;
}
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 +460,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 +475,7 @@ void out_EndLoadSection(void)
struct Section *sect_GetSymbolSection(void)
{
return currentLoadSection ? currentLoadSection : pCurrentSection;
return currentLoadSection ? currentLoadSection : currentSection;
}
/*
@@ -449,7 +493,9 @@ 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"
@@ -472,28 +518,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 +547,26 @@ 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");
// Important info: currently, UNION and LOAD cannot interact, since UNION is prohibited in
// "code" sections, whereas LOAD is restricted to them.
// Therefore, any interactions are NOT TESTED, so lift either of those restrictions at
// your own peril! ^^
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 +588,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 +618,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, size_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, size_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, size_t length)
{
checkcodesection();
reserveSpace(length * 4);
if (!checkcodesection())
return;
if (!reserveSpace(length * 4))
return;
while (length--)
writelong(*s++);
@@ -598,19 +664,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(uint32_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 +686,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 +701,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 +721,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 +735,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 +747,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 +767,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 +787,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 +823,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,10 +841,10 @@ void out_BinaryFile(char const *s, int32_t startPos)
free(fullPath);
if (!f) {
if (oGeneratedMissingIncludes) {
if (generatedMissingIncludes) {
if (verbose)
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", s, strerror(errno));
oFailedOnMissingInclude = true;
failedOnMissingInclude = true;
return;
}
error("Error opening INCBIN file '%s': %s\n", s, strerror(errno));
@@ -773,22 +854,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);
@@ -803,10 +883,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);
@@ -817,8 +898,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;
@@ -826,22 +912,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) {
if (generatedMissingIncludes) {
if (verbose)
printf("Aborting (-MG) on INCBIN file '%s' (%s)\n", s, strerror(errno));
oFailedOnMissingInclude = true;
return;
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) {
@@ -849,13 +932,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);
@@ -868,9 +951,7 @@ void out_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
(void)fgetc(f);
}
int32_t todo = length;
while (todo--) {
while (length--) {
int byte = fgetc(f);
if (byte != EOF) {
@@ -879,32 +960,40 @@ void out_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
error("Error reading INCBIN file '%s': %s\n", s, strerror(errno));
} else {
error("Premature end of file (%" PRId32 " bytes left to read)\n",
todo + 1);
length + 1);
}
}
cleanup:
fclose(f);
free(fullPath);
}
/*
* Section stack routines
*/
void out_PushSection(void)
void sect_PushSection(void)
{
struct SectionStackEntry *sect = malloc(sizeof(*sect));
struct SectionStackEntry *entry = malloc(sizeof(*entry));
if (sect == NULL)
if (entry == NULL)
fatalerror("No memory for section stack: %s\n", strerror(errno));
sect->section = pCurrentSection;
sect->scope = sym_GetCurrentSymbolScope();
sect->offset = curOffset;
sect->next = sectionStack;
sectionStack = sect;
/* TODO: maybe set current section to NULL? */
entry->section = currentSection;
entry->loadSection = currentLoadSection;
entry->scope = sym_GetCurrentSymbolScope();
entry->offset = curOffset;
entry->loadOffset = loadOffset;
entry->unionStack = unionStack;
entry->next = sectionStack;
sectionStack = entry;
// Reset the section scope
currentSection = NULL;
currentLoadSection = NULL;
sym_SetCurrentSymbolScope(NULL);
unionStack = NULL;
}
void out_PopSection(void)
void sect_PopSection(void)
{
if (!sectionStack)
fatalerror("No entries in the section stack\n");
@@ -912,14 +1001,35 @@ void out_PopSection(void)
if (currentLoadSection)
fatalerror("Cannot change the section within a `LOAD` block!\n");
struct SectionStackEntry *sect;
struct SectionStackEntry *entry = sectionStack;
sect = sectionStack;
changeSection();
pCurrentSection = sect->section;
sym_SetCurrentSymbolScope(sect->scope);
curOffset = sect->offset;
currentSection = entry->section;
currentLoadSection = entry->loadSection;
sym_SetCurrentSymbolScope(entry->scope);
curOffset = entry->offset;
loadOffset = entry->loadOffset;
unionStack = entry->unionStack;
sectionStack = sect->next;
free(sect);
sectionStack = entry->next;
free(entry);
}
bool sect_IsSizeKnown(struct Section const NONNULL(sect))
{
// SECTION UNION and SECTION FRAGMENT can still grow
if (sect->modifier != SECTION_NORMAL)
return false;
// The current section (or current load section if within one) is still growing
if (sect == currentSection || sect == currentLoadSection)
return false;
// Any section on the stack is still growing
for (struct SectionStackEntry *stack = sectionStack; stack; stack = stack->next) {
if (stack->section && !strcmp(sect->name, stack->section->name))
return false;
}
return true;
}

View File

@@ -29,8 +29,7 @@
#include "asm/util.h"
#include "asm/warning.h"
#include "extern/err.h"
#include "error.h"
#include "hashmap.h"
#include "helpers.h"
#include "version.h"
@@ -43,12 +42,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 +50,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,7 +95,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++) {
@@ -181,32 +173,32 @@ static void updateSymbolFilename(struct Symbol *sym)
/* If the old node was referenced, ensure the new one is */
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 *localName = strchr(symName, '.');
if (dotPtr) {
if (strchr(dotPtr + 1, '.'))
if (localName) {
if (strchr(localName + 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 */
char fullname[MAXSYMLEN + 1];
if (localName == symName) { /* Meaning, the name begins with the dot */
char fullName[MAXSYMLEN + 1];
fullSymbolName(fullname, sizeof(fullname), name, labelScope);
return sym_FindExactSymbol(fullname);
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,7 +266,7 @@ 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 != (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);
}
}
@@ -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,34 +448,42 @@ 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 mutable symbol's value
*/
struct Symbol *sym_AddSet(char const *symName, int32_t value)
struct Symbol *sym_AddVar(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) {
} else if (sym_IsDefined(sym) && sym->type != SYM_VAR) {
error("'%s' already defined as %s at ",
symName, sym->type == SYM_LABEL ? "label" : "constant");
dumpFilename(sym);
@@ -470,7 +493,7 @@ struct Symbol *sym_AddSet(char const *symName, int32_t value)
updateSymbolFilename(sym);
}
sym->type = SYM_SET;
sym->type = SYM_VAR;
sym->value = value;
return sym;
@@ -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,68 @@ 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
* 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;
}
assert(!strchr(labelScope, '.')); /* Assuming no dots in `labelScope` */
char fullname[MAXSYMLEN + 1];
char fullName[MAXSYMLEN + 1];
char const *localName = strchr(symName, '.');
if (name[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 */
assert(localName); /* There should be at least one dot in `symName` */
/* Check for something after the dot in `localName` */
if (localName[1] == '\0') {
fatalerror("'%s' is a nonsensical reference to an empty local label\n",
symName);
}
/* Check for more than one dot in `localName` */
if (strchr(localName + 1, '.'))
fatalerror("'%s' is a nonsensical reference to a nested local label\n",
symName);
if (localName == symName) {
/* Expand `symName` to the full `labelScope.symName` name */
fullSymbolName(fullName, sizeof(fullName), symName, labelScope);
symName = fullName;
} else {
size_t i = 0;
/* Otherwise, check that `Parent` is in fact the current scope */
while (labelScope[i] && name[i] == labelScope[i])
/* Find where `labelScope` and `symName` first differ */
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);
/*
* 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`.
*/
if (labelScope[i] != '\0' || name[i] != '.') {
/* Check that `symName` starts with `labelScope` and then a '.' */
if (labelScope[i] != '\0' || symName[i] != '.') {
size_t parentLen = localName - symName;
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 */
fatalerror("'%s' is a nonsensical reference to a nested local label\n",
name);
}
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 +683,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 +695,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 +710,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 +740,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;
sym_AddVar("_RS", 0)->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 +773,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", (uint8_t)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

@@ -6,6 +6,7 @@
* SPDX-License-Identifier: MIT
*/
#include <inttypes.h>
#include <limits.h>
#include <stdarg.h>
#include <stdint.h>
@@ -17,19 +18,13 @@
#include "asm/main.h"
#include "asm/warning.h"
#include "extern/err.h"
#include "error.h"
unsigned int nbErrors = 0;
enum WarningState {
WARNING_DEFAULT,
WARNING_DISABLED,
WARNING_ENABLED,
WARNING_ERROR
};
static enum WarningState const defaultWarnings[NB_WARNINGS] = {
static const enum WarningState defaultWarnings[ARRAY_SIZE(warningStates)] = {
[WARNING_ASSERT] = WARNING_ENABLED,
[WARNING_BACKWARDS_FOR] = WARNING_DISABLED,
[WARNING_BUILTIN_ARG] = WARNING_DISABLED,
[WARNING_CHARMAP_REDEF] = WARNING_DISABLED,
[WARNING_DIV] = WARNING_DISABLED,
@@ -43,13 +38,17 @@ static enum WarningState const defaultWarnings[NB_WARNINGS] = {
[WARNING_OBSOLETE] = WARNING_ENABLED,
[WARNING_SHIFT] = WARNING_DISABLED,
[WARNING_SHIFT_AMOUNT] = WARNING_DISABLED,
[WARNING_TRUNCATION] = WARNING_ENABLED,
[WARNING_USER] = WARNING_ENABLED,
[WARNING_NUMERIC_STRING_1] = WARNING_ENABLED,
[WARNING_NUMERIC_STRING_2] = WARNING_DISABLED,
[WARNING_TRUNCATION_1] = WARNING_ENABLED,
[WARNING_TRUNCATION_2] = WARNING_DISABLED,
};
static enum WarningState warningStates[NB_WARNINGS];
enum WarningState warningStates[ARRAY_SIZE(warningStates)];
static bool warningsAreErrors; /* Set if `-Werror` was specified */
bool warningsAreErrors; /* Set if `-Werror` was specified */
static enum WarningState warningState(enum WarningID id)
{
@@ -70,8 +69,9 @@ static enum WarningState warningState(enum WarningID id)
return state;
}
static char const *warningFlags[NB_WARNINGS_ALL] = {
static const char * const warningFlags[NB_WARNINGS] = {
"assert",
"backwards-for",
"builtin-args",
"charmap-redef",
"div",
@@ -85,27 +85,82 @@ static char const *warningFlags[NB_WARNINGS_ALL] = {
"obsolete",
"shift",
"shift-amount",
"truncation",
"user",
// Parametric warnings
"numeric-string",
"numeric-string",
"truncation",
"truncation",
/* Meta warnings */
"all",
"extra",
"everything" /* Especially useful for testing */
"everything", /* Especially useful for testing */
};
static const struct {
char const *name;
uint8_t nbLevels;
uint8_t defaultLevel;
} paramWarnings[] = {
{ "numeric-string", 2, 1 },
{ "truncation", 2, 2 },
};
static bool tryProcessParamWarning(char const *flag, uint8_t param, enum WarningState state)
{
enum WarningID baseID = PARAM_WARNINGS_START;
for (size_t i = 0; i < ARRAY_SIZE(paramWarnings); i++) {
uint8_t maxParam = paramWarnings[i].nbLevels;
if (!strcmp(paramWarnings[i].name, flag)) { // Match!
// If making the warning an error but param is 0, set to the maximum
// This accommodates `-Werror=flag`, but also `-Werror=flag=0`, which is
// thus filtered out by the caller.
// A param of 0 makes sense for disabling everything, but neither for
// enabling nor "erroring". Use the default for those.
if (param == 0 && state != WARNING_DISABLED) {
param = paramWarnings[i].defaultLevel;
} else if (param > maxParam) {
if (param != 255) // Don't warn if already capped
warnx("Got parameter %" PRIu8
" for warning flag \"%s\", but the maximum is %"
PRIu8 "; capping.\n",
param, flag, maxParam);
param = maxParam;
}
// Set the first <param> to enabled/error, and disable the rest
for (uint8_t ofs = 0; ofs < maxParam; ofs++) {
warningStates[baseID + ofs] =
ofs < param ? state : WARNING_DISABLED;
}
return true;
}
baseID += maxParam;
}
return false;
}
enum MetaWarningCommand {
META_WARNING_DONE = NB_WARNINGS
};
/* Warnings that probably indicate an error */
static uint8_t const _wallCommands[] = {
WARNING_BACKWARDS_FOR,
WARNING_BUILTIN_ARG,
WARNING_CHARMAP_REDEF,
WARNING_EMPTY_DATA_DIRECTIVE,
WARNING_EMPTY_STRRPL,
WARNING_LARGE_CONSTANT,
WARNING_LONG_STR,
WARNING_NESTED_COMMENT,
WARNING_OBSOLETE,
WARNING_NUMERIC_STRING_1,
META_WARNING_DONE
};
@@ -114,11 +169,16 @@ static uint8_t const _wextraCommands[] = {
WARNING_EMPTY_MACRO_ARG,
WARNING_MACRO_SHIFT,
WARNING_NESTED_COMMENT,
WARNING_OBSOLETE,
WARNING_NUMERIC_STRING_2,
WARNING_TRUNCATION_1,
WARNING_TRUNCATION_2,
META_WARNING_DONE
};
/* Literally everything. Notably useful for testing */
static uint8_t const _weverythingCommands[] = {
WARNING_BACKWARDS_FOR,
WARNING_BUILTIN_ARG,
WARNING_DIV,
WARNING_EMPTY_DATA_DIRECTIVE,
@@ -131,7 +191,10 @@ static uint8_t const _weverythingCommands[] = {
WARNING_OBSOLETE,
WARNING_SHIFT,
WARNING_SHIFT_AMOUNT,
/* WARNING_TRUNCATION, */
WARNING_NUMERIC_STRING_1,
WARNING_NUMERIC_STRING_2,
WARNING_TRUNCATION_1,
WARNING_TRUNCATION_2,
/* WARNING_USER, */
META_WARNING_DONE
};
@@ -142,37 +205,33 @@ static uint8_t const *metaWarningCommands[NB_META_WARNINGS] = {
_weverythingCommands
};
void processWarningFlag(char const *flag)
void processWarningFlag(char *flag)
{
static bool setError = false;
/* First, try to match against a "meta" warning */
for (enum WarningID id = NB_WARNINGS; id < NB_WARNINGS_ALL; id++) {
for (enum WarningID id = META_WARNINGS_START; id < NB_WARNINGS; id++) {
/* TODO: improve the matching performance? */
if (!strcmp(flag, warningFlags[id])) {
/* We got a match! */
if (setError)
errx(1, "Cannot make meta warning \"%s\" into an error",
errx("Cannot make meta warning \"%s\" into an error",
flag);
uint8_t const *ptr =
metaWarningCommands[id - NB_WARNINGS];
for (;;) {
if (*ptr == META_WARNING_DONE)
return;
for (uint8_t const *ptr = metaWarningCommands[id - META_WARNINGS_START];
*ptr != META_WARNING_DONE; ptr++) {
/* Warning flag, set without override */
if (warningStates[*ptr] == WARNING_DEFAULT)
warningStates[*ptr] = WARNING_ENABLED;
ptr++;
}
return;
}
}
/* If it's not a meta warning, specially check against `-Werror` */
if (!strncmp(flag, "error", strlen("error"))) {
char const *errorFlag = flag + strlen("error");
char *errorFlag = flag + strlen("error");
switch (*errorFlag) {
case '\0':
@@ -181,7 +240,7 @@ void processWarningFlag(char const *flag)
return;
case '=':
/* `-Werror=XXX */
/* `-Werror=XXX` */
setError = true;
processWarningFlag(errorFlag + 1); /* Skip the `=` */
setError = false;
@@ -193,14 +252,60 @@ void processWarningFlag(char const *flag)
/* Well, it's either a normal warning or a mistake */
/* Check if this is a negation */
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;
/* Not an error, then check if this is a negation */
strncmp(flag, "no-", strlen("no-")) ? WARNING_ENABLED
: WARNING_DISABLED;
char const *rootFlag = state == WARNING_DISABLED ? flag + strlen("no-") : flag;
// Is this a "parametric" warning?
if (state != WARNING_DISABLED) { // The `no-` form cannot be parametrized
// First, check if there is an "equals" sign followed by a decimal number
char *equals = strchr(rootFlag, '=');
if (equals && equals[1] != '\0') { // Ignore an equal sign at the very end as well
// Is the rest of the string a decimal number?
// We want to avoid `strtoul`'s whitespace and sign, so we parse manually
uint8_t param = 0;
char const *ptr = equals + 1;
bool warned = false;
// The `if`'s condition above ensures that this will run at least once
do {
// If we don't have a digit, bail
if (*ptr < '0' || *ptr > '9')
break;
// Avoid overflowing!
if (param > UINT8_MAX - (*ptr - '0')) {
if (!warned)
warnx("Invalid warning flag \"%s\": capping parameter at 255\n",
flag);
warned = true; // Only warn once, cap always
param = 255;
continue;
}
param = param * 10 + (*ptr - '0');
ptr++;
} while (*ptr);
// If we managed to the end of the string, check that the warning indeed
// accepts a parameter
if (*ptr == '\0') {
if (setError && param == 0) {
warnx("Ignoring nonsensical warning flag \"%s\"\n", flag);
return;
}
*equals = '\0'; // Truncate the param at the '='
if (tryProcessParamWarning(rootFlag, param,
param == 0 ? WARNING_DISABLED : state))
return;
}
}
}
/* Try to match the flag against a "normal" flag */
for (enum WarningID id = 0; id < NB_WARNINGS; id++) {
for (enum WarningID id = 0; id < NB_PLAIN_WARNINGS; id++) {
if (!strcmp(rootFlag, warningFlags[id])) {
/* We got a match! */
warningStates[id] = state;
@@ -208,10 +313,15 @@ void processWarningFlag(char const *flag)
}
}
// Lastly, this might be a "parametric" warning without an equals sign
// If it is, treat the param as 1 if enabling, or 0 if disabling
if (tryProcessParamWarning(rootFlag, 0, state))
return;
warnx("Unknown warning `%s`", flag);
}
void printDiag(const char *fmt, va_list args, char const *type,
void printDiag(char const *fmt, va_list args, char const *type,
char const *flagfmt, char const *flag)
{
fputs(type, stderr);
@@ -221,17 +331,17 @@ void printDiag(const char *fmt, va_list args, char const *type,
lexer_DumpStringExpansions();
}
void error(const char *fmt, ...)
void error(char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
printDiag(fmt, args, "ERROR: ", ":\n ", NULL);
printDiag(fmt, args, "error: ", ":\n ", NULL);
va_end(args);
nbErrors++;
}
_Noreturn void fatalerror(const char *fmt, ...)
_Noreturn void fatalerror(char const *fmt, ...)
{
va_list args;
@@ -254,7 +364,7 @@ void warning(enum WarningID id, char const *fmt, ...)
return;
case WARNING_ERROR:
printDiag(fmt, args, "ERROR: ", ": [-Werror=%s]\n ", flag);
printDiag(fmt, args, "error: ", ": [-Werror=%s]\n ", flag);
va_end(args);
return;

87
src/error.c Normal file
View File

@@ -0,0 +1,87 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2005-2021, Rich Felker and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "error.h"
#include "platform.h"
static void vwarn(char const NONNULL(fmt), va_list ap)
{
fprintf(stderr, "warning: ");
vfprintf(stderr, fmt, ap);
fputs(": ", stderr);
perror(NULL);
}
static void vwarnx(char const NONNULL(fmt), va_list ap)
{
fprintf(stderr, "warning");
fputs(": ", stderr);
vfprintf(stderr, fmt, ap);
putc('\n', stderr);
}
_Noreturn static void verr(char const NONNULL(fmt), va_list ap)
{
fprintf(stderr, "error: ");
vfprintf(stderr, fmt, ap);
fputs(": ", stderr);
fputs(strerror(errno), stderr);
putc('\n', stderr);
exit(1);
}
_Noreturn static void verrx(char const NONNULL(fmt), va_list ap)
{
fprintf(stderr, "error");
fputs(": ", stderr);
vfprintf(stderr, fmt, ap);
putc('\n', stderr);
exit(1);
}
void warn(char const NONNULL(fmt), ...)
{
va_list ap;
va_start(ap, fmt);
vwarn(fmt, ap);
va_end(ap);
}
void warnx(char const NONNULL(fmt), ...)
{
va_list ap;
va_start(ap, fmt);
vwarnx(fmt, ap);
va_end(ap);
}
_Noreturn void err(char const NONNULL(fmt), ...)
{
va_list ap;
va_start(ap, fmt);
verr(fmt, ap);
va_end(ap);
}
_Noreturn void errx(char const NONNULL(fmt), ...)
{
va_list ap;
va_start(ap, fmt);
verrx(fmt, ap);
va_end(ap);
}

94
src/extern/err.c vendored
View File

@@ -1,94 +0,0 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2005-2018, Rich Felker and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "extern/err.h"
void rgbds_vwarn(const char *fmt, va_list ap)
{
fprintf(stderr, "warning: ");
if (fmt) {
vfprintf(stderr, fmt, ap);
fputs(": ", stderr);
}
perror(NULL);
}
void rgbds_vwarnx(const char *fmt, va_list ap)
{
fprintf(stderr, "warning");
if (fmt) {
fputs(": ", stderr);
vfprintf(stderr, fmt, ap);
}
putc('\n', stderr);
}
_Noreturn void rgbds_verr(int status, const char *fmt, va_list ap)
{
fprintf(stderr, "error: ");
if (fmt) {
vfprintf(stderr, fmt, ap);
fputs(": ", stderr);
}
fputs(strerror(errno), stderr);
putc('\n', stderr);
exit(status);
}
_Noreturn void rgbds_verrx(int status, const char *fmt, va_list ap)
{
fprintf(stderr, "error");
if (fmt) {
fputs(": ", stderr);
vfprintf(stderr, fmt, ap);
}
putc('\n', stderr);
exit(status);
}
void rgbds_warn(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vwarn(fmt, ap);
va_end(ap);
}
void rgbds_warnx(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vwarnx(fmt, ap);
va_end(ap);
}
_Noreturn void rgbds_err(int status, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
verr(status, fmt, ap);
va_end(ap);
}
_Noreturn void rgbds_errx(int status, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
verrx(status, fmt, ap);
va_end(ap);
}

18
src/extern/getopt.c vendored
View File

@@ -37,7 +37,7 @@ int musl_optind = 1, musl_opterr = 1, musl_optopt;
int musl_optreset = 0;
static int musl_optpos;
static void musl_getopt_msg(const char *a, const char *b, const char *c, size_t l)
static void musl_getopt_msg(char const *a, char const *b, char const *c, size_t l)
{
FILE *f = stderr;
@@ -47,7 +47,7 @@ static void musl_getopt_msg(const char *a, const char *b, const char *c, size_t
putc('\n', f);
}
static int getopt(int argc, char *argv[], const char *optstring)
static int getopt(int argc, char *argv[], char const *optstring)
{
int i;
wchar_t c, d;
@@ -140,9 +140,11 @@ static void permute(char **argv, int dest, int src)
argv[dest] = tmp;
}
static int musl_getopt_long_core(int argc, char **argv, const char *optstring, const struct option *longopts, int *idx, int longonly);
static int musl_getopt_long_core(int argc, char **argv, char const *optstring,
const struct option *longopts, int *idx, int longonly);
static int musl_getopt_long(int argc, char **argv, const char *optstring, const struct option *longopts, int *idx, int longonly)
static int musl_getopt_long(int argc, char **argv, char const *optstring,
const struct option *longopts, int *idx, int longonly)
{
int ret, skipped, resumed;
@@ -178,7 +180,8 @@ static int musl_getopt_long(int argc, char **argv, const char *optstring, const
return ret;
}
static int musl_getopt_long_core(int argc, char **argv, const char *optstring, const struct option *longopts, int *idx, int longonly)
static int musl_getopt_long_core(int argc, char **argv, char const *optstring,
const struct option *longopts, int *idx, int longonly)
{
musl_optarg = 0;
if (longopts && argv[musl_optind][0] == '-' &&
@@ -189,7 +192,7 @@ static int musl_getopt_long_core(int argc, char **argv, const char *optstring, c
char *arg = 0, *opt, *start = argv[musl_optind] + 1;
for (cnt = i = 0; longopts[i].name; i++) {
const char *name = longopts[i].name;
char const *name = longopts[i].name;
opt = start;
if (*opt == '-')
@@ -277,7 +280,8 @@ static int musl_getopt_long_core(int argc, char **argv, const char *optstring, c
return getopt(argc, argv, optstring);
}
int musl_getopt_long_only(int argc, char **argv, const char *optstring, const struct option *longopts, int *idx)
int musl_getopt_long_only(int argc, char **argv, char const *optstring,
const struct option *longopts, int *idx)
{
return musl_getopt_long(argc, argv, optstring, longopts, idx, 1);
}

View File

@@ -25,12 +25,12 @@
#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
/* Short options */
static const char *optstring = "Ccf:i:jk:l:m:n:p:r:st:Vv";
static const char *optstring = "Ccf:i:jk:l:m:n:Op:r:st:Vv";
/*
* Equivalent long options
@@ -52,6 +52,7 @@ static struct option const longopts[] = {
{ "old-licensee", required_argument, NULL, 'l' },
{ "mbc-type", required_argument, NULL, 'm' },
{ "rom-version", required_argument, NULL, 'n' },
{ "overwrite", no_argument, NULL, 'O' },
{ "pad-value", required_argument, NULL, 'p' },
{ "ram-size", required_argument, NULL, 'r' },
{ "sgb-compatible", no_argument, NULL, 's' },
@@ -64,7 +65,7 @@ static struct option const longopts[] = {
static void printUsage(void)
{
fputs(
"Usage: rgbfix [-jsVv] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n"
"Usage: rgbfix [-jOsVv] [-C | -c] [-f <fix_spec>] [-i <game_id>] [-k <licensee>]\n"
" [-l <licensee_byte>] [-m <mbc_type>] [-n <rom_version>]\n"
" [-p <pad_value>] [-r <ram_size>] [-t <title_str>] [<file> ...]\n"
"Useful options:\n"
@@ -79,6 +80,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 +135,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_TIMER = 0x104,
TPP1_TIMER_RUMBLE = 0x105,
TPP1_TIMER_MULTIRUMBLE = 0x106, // Should not be possible
TPP1_TIMER_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_TIMER = 0x10C,
TPP1_BATTERY_TIMER_RUMBLE = 0x10D,
TPP1_BATTERY_TIMER_MULTIRUMBLE = 0x10E, // Should not be possible
TPP1_BATTERY_TIMER_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 +164,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 ($01), 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+TIMER,\n", stderr);
fputs("\tTPP1_1.0+TIMER+RUMBLE, TPP1_1.0+TIMER+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+TIMER, TPP1_1.0+BATTERY+TIMER+RUMBLE,\n", stderr);
fputs("\tTPP1_1.0+BATTERY+TIMER+MULTIRUMBLE\n", stderr);
}
static uint8_t tpp1Rev[2];
/**
* @return False on failure
*/
@@ -151,10 +215,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 +324,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 +396,7 @@ do { \
#define TIMER 0x20
#define RUMBLE 0x10
#define SENSOR 0x08
#define MULTIRUMBLE 0x04
for (;;) {
// Trim off trailing whitespace
@@ -303,6 +420,12 @@ do { \
features |= BATTERY;
break;
case 'M':
case 'm':
tryReadSlice("ULTIRUMBLE");
features |= MULTIRUMBLE;
break;
case 'R': // RAM or RUMBLE
case 'r':
switch (*ptr++) {
@@ -371,7 +494,9 @@ do { \
case MBC3:
// Handle timer, which also requires battery
if (features & (TIMER & BATTERY)) {
if (features & TIMER) {
if (!(features & BATTERY))
fprintf(stderr, "warning: MBC3+TIMER implies BATTERY\n");
features &= ~(TIMER | BATTERY); // Reset those bits
mbc = MBC3_TIMER_BATTERY;
// RAM is handled below
@@ -424,6 +549,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 +638,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_TIMER:
return "TPP1+TIMER";
case TPP1_TIMER_RUMBLE:
return "TPP1+TIMER+RUMBLE";
case TPP1_TIMER_MULTIRUMBLE:
case TPP1_TIMER_MULTIRUMBLE_RUMBLE:
return "TPP1+TIMER+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_TIMER:
return "TPP1+BATTERY+TIMER";
case TPP1_BATTERY_TIMER_RUMBLE:
return "TPP1+BATTERY+TIMER+RUMBLE";
case TPP1_BATTERY_TIMER_MULTIRUMBLE:
case TPP1_BATTERY_TIMER_MULTIRUMBLE_RUMBLE:
return "TPP1+BATTERY+TIMER+MULTIRUMBLE";
// Error values
case MBC_NONE:
@@ -547,25 +716,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_TIMER:
case TPP1_TIMER_RUMBLE:
case TPP1_TIMER_MULTIRUMBLE:
case TPP1_TIMER_MULTIRUMBLE_RUMBLE:
case TPP1_BATTERY:
case TPP1_BATTERY_RUMBLE:
case TPP1_BATTERY_MULTIRUMBLE:
case TPP1_BATTERY_MULTIRUMBLE_RUMBLE:
case TPP1_BATTERY_TIMER:
case TPP1_BATTERY_TIMER_RUMBLE:
case TPP1_BATTERY_TIMER_MULTIRUMBLE:
case TPP1_BATTERY_TIMER_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,
@@ -575,6 +749,15 @@ static const uint8_t ninLogo[] = {
0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E
};
static const uint8_t trashLogo[] = {
0xFF^0xCE, 0xFF^0xED, 0xFF^0x66, 0xFF^0x66, 0xFF^0xCC, 0xFF^0x0D, 0xFF^0x00, 0xFF^0x0B,
0xFF^0x03, 0xFF^0x73, 0xFF^0x00, 0xFF^0x83, 0xFF^0x00, 0xFF^0x0C, 0xFF^0x00, 0xFF^0x0D,
0xFF^0x00, 0xFF^0x08, 0xFF^0x11, 0xFF^0x1F, 0xFF^0x88, 0xFF^0x89, 0xFF^0x00, 0xFF^0x0E,
0xFF^0xDC, 0xFF^0xCC, 0xFF^0x6E, 0xFF^0xE6, 0xFF^0xDD, 0xFF^0xDD, 0xFF^0xD9, 0xFF^0x99,
0xFF^0xBB, 0xFF^0xBB, 0xFF^0x67, 0xFF^0x63, 0xFF^0x6E, 0xFF^0x0E, 0xFF^0xEC, 0xFF^0xCC,
0xFF^0xDD, 0xFF^0xDC, 0xFF^0x99, 0xFF^0x9F, 0xFF^0xBB, 0xFF^0xB9, 0xFF^0x33, 0xFF^0x3E
};
static enum { DMG, BOTH, CGB } model = DMG; // If DMG, byte is left alone
#define FIX_LOGO 0x80
#define TRASH_LOGO 0x40
@@ -591,6 +774,7 @@ static uint8_t newLicenseeLen;
static uint16_t oldLicensee = UNSPECIFIED;
static enum MbcType cartridgeType = MBC_NONE;
static uint16_t romVersion = UNSPECIFIED;
static bool overwriteRom = false; // If false, warn when overwriting non-zero non-identical bytes
static uint16_t padValue = UNSPECIFIED;
static uint16_t ramSize = UNSPECIFIED;
static bool sgb = false; // If false, SGB flags are left alone
@@ -599,7 +783,7 @@ static uint8_t titleLen;
static uint8_t maxTitleLen(void)
{
return gameID ? 11 : model != DMG ? 15 : 16;
return gameID ? 11 : model != DMG ? 15 : 16;
}
static ssize_t readBytes(int fd, uint8_t *buf, size_t len)
@@ -653,6 +837,47 @@ static ssize_t writeBytes(int fd, void *buf, size_t len)
return total;
}
/**
* @param rom0 A pointer to rom0
* @param addr What address to check
* @param fixedByte The fixed byte at the address
* @param areaName Name to be displayed in the warning message
*/
static void overwriteByte(uint8_t *rom0, uint16_t addr, uint8_t fixedByte, char const *areaName)
{
uint8_t origByte = rom0[addr];
if (!overwriteRom && origByte != 0 && origByte != fixedByte)
fprintf(stderr, "warning: Overwrote a non-zero byte in the %s\n", areaName);
rom0[addr] = fixedByte;
}
/**
* @param rom0 A pointer to rom0
* @param startAddr What address to begin checking from
* @param fixed The fixed bytes at the address
* @param size How many bytes to check
* @param areaName Name to be displayed in the warning message
*/
static void overwriteBytes(uint8_t *rom0, uint16_t startAddr, uint8_t const *fixed, uint8_t size,
char const *areaName)
{
if (!overwriteRom) {
for (uint8_t i = 0; i < size; i++) {
uint8_t origByte = rom0[i + startAddr];
if (origByte != 0 && origByte != fixed[i]) {
fprintf(stderr, "warning: Overwrote a non-zero byte in the %s\n",
areaName);
break;
}
}
}
memcpy(&rom0[startAddr], fixed, size);
}
/**
* @param input File descriptor to be used for reading
* @param output File descriptor to be used for writing, may be equal to `input`
@@ -669,56 +894,82 @@ 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)) {
if (fixSpec & FIX_LOGO) {
memcpy(&rom0[0x104], ninLogo, sizeof(ninLogo));
} else {
for (uint8_t i = 0; i < sizeof(ninLogo); i++)
rom0[i + 0x104] = ~ninLogo[i];
}
if (fixSpec & FIX_LOGO)
overwriteBytes(rom0, 0x0104, ninLogo, sizeof(ninLogo), "Nintendo logo");
else
overwriteBytes(rom0, 0x0104, trashLogo, sizeof(trashLogo), "Nintendo logo");
}
if (title)
memcpy(&rom0[0x134], title, titleLen);
overwriteBytes(rom0, 0x134, (uint8_t const *)title, titleLen, "title");
if (gameID)
memcpy(&rom0[0x13f], gameID, gameIDLen);
overwriteBytes(rom0, 0x13F, (uint8_t const *)gameID, gameIDLen, "manufacturer code");
if (model != DMG)
rom0[0x143] = model == BOTH ? 0x80 : 0xc0;
overwriteByte(rom0, 0x143, model == BOTH ? 0x80 : 0xC0, "CGB flag");
if (newLicensee)
memcpy(&rom0[0x144], newLicensee, newLicenseeLen);
overwriteBytes(rom0, 0x144, (uint8_t const *)newLicensee, newLicenseeLen,
"new licensee code");
if (sgb)
rom0[0x146] = 0x03;
overwriteByte(rom0, 0x146, 0x03, "SGB flag");
// If a valid MBC was specified...
if (cartridgeType < MBC_NONE)
rom0[0x147] = cartridgeType;
if (cartridgeType < MBC_NONE) {
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
}
overwriteByte(rom0, 0x147, byte, "cartridge type");
}
if (!japanese)
rom0[0x14a] = 0x01;
// ROM size will be written last, after evaluating the file's size
if ((cartridgeType & 0xFF00) == TPP1) {
uint8_t const tpp1Code[2] = {0xC1, 0x65};
overwriteBytes(rom0, 0x149, tpp1Code, sizeof(tpp1Code), "TPP1 identification code");
overwriteBytes(rom0, 0x150, tpp1Rev, sizeof(tpp1Rev), "TPP1 revision number");
if (ramSize != UNSPECIFIED)
overwriteByte(rom0, 0x152, ramSize, "RAM size");
overwriteByte(rom0, 0x153, cartridgeType & 0xFF, "TPP1 feature flags");
} else {
// Regular mappers
if (ramSize != UNSPECIFIED)
overwriteByte(rom0, 0x149, ramSize, "RAM size");
if (!japanese)
overwriteByte(rom0, 0x14A, 0x01, "destination code");
}
if (oldLicensee != UNSPECIFIED)
rom0[0x14b] = oldLicensee;
overwriteByte(rom0, 0x14B, oldLicensee, "old licensee code");
if (romVersion != UNSPECIFIED)
rom0[0x14c] = romVersion;
overwriteByte(rom0, 0x14C, romVersion, "mask ROM version number");
// Remain to be handled the ROM size, and header checksum.
// The latter depends on the former, and so will be handled after it.
@@ -765,8 +1016,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
static_assert(0x10000 * BANK_SIZE <= SSIZE_MAX, "Max input file size too large for OS");
if (nbBanks == 0x10000) {
report("FATAL: \"%s\" has more than 65536 banks\n", name);
free(romx);
return;
goto cleanup;
}
nbBanks++;
@@ -816,16 +1066,19 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
if (fixSpec & (FIX_HEADER_SUM | TRASH_HEADER_SUM)) {
uint8_t sum = 0;
for (uint16_t i = 0x134; i < 0x14d; i++)
for (uint16_t i = 0x134; i < 0x14D; i++)
sum -= rom0[i] + 1;
rom0[0x14d] = fixSpec & TRASH_HEADER_SUM ? ~sum : sum;
overwriteByte(rom0, 0x14D, fixSpec & TRASH_HEADER_SUM ? ~sum : sum,
"header checksum");
}
if (fixSpec & (FIX_GLOBAL_SUM | TRASH_GLOBAL_SUM)) {
// Computation of the global checksum assumes 0s being stored in its place
rom0[0x14e] = 0;
rom0[0x14f] = 0;
for (uint16_t i = 0; i < rom0Len; i++)
// Computation of the global checksum does not include the checksum bytes
assert(rom0Len >= 0x14E);
for (uint16_t i = 0; i < 0x14E; i++)
globalSum += rom0[i];
for (uint16_t i = 0x150; i < rom0Len; i++)
globalSum += rom0[i];
// Pipes have already read ROMX and updated globalSum, but not regular files
if (input == output) {
@@ -841,8 +1094,10 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
if (fixSpec & TRASH_GLOBAL_SUM)
globalSum = ~globalSum;
rom0[0x14e] = globalSum >> 8;
rom0[0x14f] = globalSum & 0xff;
uint8_t bytes[2] = {globalSum >> 8, globalSum & 0xFF};
overwriteBytes(rom0, 0x14E, bytes, sizeof(bytes), "global checksum");
}
// In case the output depends on the input, reset to the beginning of the file, and only
@@ -850,22 +1105,22 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
if (input == output) {
if (lseek(output, 0, SEEK_SET) == (off_t)-1) {
report("FATAL: Failed to rewind \"%s\": %s\n", name, strerror(errno));
goto free_romx;
goto cleanup;
}
// 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);
if (writeLen == -1) {
report("FATAL: Failed to write \"%s\"'s ROM0: %s\n", name, strerror(errno));
goto free_romx;
goto cleanup;
} else if (writeLen < rom0Len) {
report("FATAL: Could only write %ld of \"%s\"'s %ld ROM0 bytes\n",
writeLen, name, rom0Len);
goto free_romx;
report("FATAL: Could only write %jd of \"%s\"'s %jd ROM0 bytes\n",
(intmax_t)writeLen, name, (intmax_t)rom0Len);
goto cleanup;
}
// Output ROMX if it was buffered
@@ -875,11 +1130,11 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
writeLen = writeBytes(output, romx, totalRomxLen);
if (writeLen == -1) {
report("FATAL: Failed to write \"%s\"'s ROMX: %s\n", name, strerror(errno));
goto free_romx;
goto cleanup;
} else if ((size_t)writeLen < totalRomxLen) {
report("FATAL: Could only write %ld of \"%s\"'s %ld ROMX bytes\n",
writeLen, name, totalRomxLen);
goto free_romx;
report("FATAL: Could only write %jd of \"%s\"'s %zu ROMX bytes\n",
(intmax_t)writeLen, name, totalRomxLen);
goto cleanup;
}
}
@@ -889,7 +1144,7 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
if (lseek(output, 0, SEEK_END) == (off_t)-1) {
report("FATAL: Failed to seek to end of \"%s\": %s\n",
name, strerror(errno));
goto free_romx;
goto cleanup;
}
}
memset(bank, padValue, sizeof(bank));
@@ -911,12 +1166,10 @@ static void processFile(int input, int output, char const *name, off_t fileSize)
}
}
free_romx:
cleanup:
free(romx);
}
#undef trySeek
static bool processFilename(char const *name)
{
nbErrors = 0;
@@ -938,7 +1191,7 @@ static bool processFilename(char const *name)
if (input == -1) {
report("FATAL: Failed to open \"%s\" for reading+writing: %s\n",
name, strerror(errno));
goto fail;
goto finish;
}
if (fstat(input, &stat) == -1) {
@@ -949,16 +1202,16 @@ 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);
}
close(input);
}
finish:
if (nbErrors)
fail:
fprintf(stderr, "Fixing \"%s\" failed with %u error%s\n",
name, nbErrors, nbErrors == 1 ? "" : "s");
return nbErrors;
@@ -967,7 +1220,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) {
@@ -1015,7 +1268,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, \
@@ -1023,30 +1276,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++;
}
@@ -1091,10 +1344,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);
@@ -1107,6 +1363,10 @@ do { \
parseByte(romVersion, "n");
break;
case 'O':
overwriteRom = true;
break;
case 'p':
parseByte(padValue, "p");
break;
@@ -1148,7 +1408,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

@@ -13,7 +13,7 @@
.Nd Game Boy header utility and checksum fixer
.Sh SYNOPSIS
.Nm
.Op Fl jsVv
.Op Fl jOsVv
.Op Fl C | c
.Op Fl f Ar fix_spec
.Op Fl i Ar game_id
@@ -112,11 +112,21 @@ 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
.Ql Cm 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
to a given value from 0 to 0xFF.
.It Fl O , Fl Fl overwrite
Allow overwriting different non-zero bytes in the header without a warning being emitted.
.It Fl p Ar pad_value , Fl Fl pad-value Ar pad_value
Pad the ROM image to a valid size with a given pad value from 0 to 255 (0xFF).
.Nm
@@ -179,6 +189,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 .

File diff suppressed because it is too large Load Diff

View File

@@ -20,7 +20,7 @@ void transpose_tiles(struct GBImage *gb, int width)
newdata = calloc(gb->size, 1);
if (!newdata)
err(1, "%s: Failed to allocate memory for new data", __func__);
err("%s: Failed to allocate memory for new data", __func__);
for (i = 0; i < gb->size; i++) {
newbyte = i / (8 * depth) * width * 8 * depth;
@@ -65,7 +65,7 @@ void output_file(const struct Options *opts, const struct GBImage *gb)
f = fopen(opts->outfile, "wb");
if (!f)
err(1, "%s: Opening output file '%s' failed", __func__,
err("%s: Opening output file '%s' failed", __func__,
opts->outfile);
fwrite(gb->data, 1, gb->size - gb->trim * 8 * depth, f);
@@ -141,7 +141,7 @@ int get_mirrored_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles,
tile_yflip = malloc(tile_size);
if (!tile_yflip)
err(1, "%s: Failed to allocate memory for Y flip of tile",
err("%s: Failed to allocate memory for Y flip of tile",
__func__);
yflip(tile, tile_yflip, tile_size);
index = get_tile_index(tile_yflip, tiles, num_tiles, tile_size);
@@ -153,7 +153,7 @@ int get_mirrored_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles,
tile_xflip = malloc(tile_size);
if (!tile_xflip)
err(1, "%s: Failed to allocate memory for X flip of tile",
err("%s: Failed to allocate memory for X flip of tile",
__func__);
xflip(tile, tile_xflip, tile_size);
index = get_tile_index(tile_xflip, tiles, num_tiles, tile_size);
@@ -198,13 +198,13 @@ void create_mapfiles(const struct Options *opts, struct GBImage *gb,
tiles = calloc(max_tiles, sizeof(*tiles));
if (!tiles)
err(1, "%s: Failed to allocate memory for tiles", __func__);
err("%s: Failed to allocate memory for tiles", __func__);
num_tiles = 0;
if (*opts->tilemapfile) {
tilemap->data = calloc(max_tiles, sizeof(*tilemap->data));
if (!tilemap->data)
err(1, "%s: Failed to allocate memory for tilemap data",
err("%s: Failed to allocate memory for tilemap data",
__func__);
tilemap->size = 0;
}
@@ -212,7 +212,7 @@ void create_mapfiles(const struct Options *opts, struct GBImage *gb,
if (*opts->attrmapfile) {
attrmap->data = calloc(max_tiles, sizeof(*attrmap->data));
if (!attrmap->data)
err(1, "%s: Failed to allocate memory for attrmap data",
err("%s: Failed to allocate memory for attrmap data",
__func__);
attrmap->size = 0;
}
@@ -222,9 +222,13 @@ void create_mapfiles(const struct Options *opts, struct GBImage *gb,
flags = 0;
tile = malloc(tile_size);
if (!tile)
err(1, "%s: Failed to allocate memory for tile",
err("%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++;
}
@@ -265,7 +269,7 @@ void create_mapfiles(const struct Options *opts, struct GBImage *gb,
free(gb->data);
gb->data = malloc(tile_size * num_tiles);
if (!gb->data)
err(1, "%s: Failed to allocate memory for tile data",
err("%s: Failed to allocate memory for tile data",
__func__);
for (i = 0; i < num_tiles; i++) {
tile = tiles[i];
@@ -288,7 +292,7 @@ void output_tilemap_file(const struct Options *opts,
f = fopen(opts->tilemapfile, "wb");
if (!f)
err(1, "%s: Opening tilemap file '%s' failed", __func__,
err("%s: Opening tilemap file '%s' failed", __func__,
opts->tilemapfile);
fwrite(tilemap->data, 1, tilemap->size, f);
@@ -305,7 +309,7 @@ void output_attrmap_file(const struct Options *opts,
f = fopen(opts->attrmapfile, "wb");
if (!f)
err(1, "%s: Opening attrmap file '%s' failed", __func__,
err("%s: Opening attrmap file '%s' failed", __func__,
opts->attrmapfile);
fwrite(attrmap->data, 1, attrmap->size, f);
@@ -348,7 +352,7 @@ void output_palette_file(const struct Options *opts,
f = fopen(opts->palfile, "wb");
if (!f)
err(1, "%s: Opening palette file '%s' failed", __func__,
err("%s: Opening palette file '%s' failed", __func__,
opts->palfile);
for (i = 0; i < raw_image->num_colors; i++) {

View File

@@ -167,7 +167,7 @@ int main(int argc, char *argv[])
opts.infile = argv[argc - 1];
if (depth != 1 && depth != 2)
errx(1, "Depth option must be either 1 or 2.");
errx("Depth option must be either 1 or 2.");
colors = 1 << depth;
@@ -200,17 +200,17 @@ int main(int argc, char *argv[])
opts.trim = png_options.trim;
if (raw_image->width % 8) {
errx(1, "Input PNG file %s not sized correctly. The image's width must be a multiple of 8.",
errx("Input PNG file %s not sized correctly. The image's width must be a multiple of 8.",
opts.infile);
}
if (raw_image->width / 8 > 1 && raw_image->height % 8) {
errx(1, "Input PNG file %s not sized correctly. If the image is more than 1 tile wide, its height must be a multiple of 8.",
errx("Input PNG file %s not sized correctly. If the image is more than 1 tile wide, its height must be a multiple of 8.",
opts.infile);
}
if (opts.trim &&
opts.trim > (raw_image->width / 8) * (raw_image->height / 8) - 1) {
errx(1, "Trim (%d) for input raw_image file '%s' too large (max: %u)",
errx("Trim (%d) for input raw_image file '%s' too large (max: %u)",
opts.trim, opts.infile,
(raw_image->width / 8) * (raw_image->height / 8) - 1);
}

View File

@@ -32,7 +32,7 @@ struct RawIndexedImage *input_png_file(const struct Options *opts,
f = fopen(opts->infile, "rb");
if (!f)
err(1, "Opening input png file '%s' failed", opts->infile);
err("Opening input png file '%s' failed", opts->infile);
initialize_png(&img, f);
@@ -54,7 +54,7 @@ struct RawIndexedImage *input_png_file(const struct Options *opts,
raw_image = truecolor_png_to_raw(&img); break;
default:
/* Shouldn't happen, but might as well handle just in case. */
errx(1, "Input PNG file is of invalid color type.");
errx("Input PNG file is of invalid color type.");
}
get_text(&img, png_options);
@@ -83,7 +83,7 @@ void output_png_file(const struct Options *opts,
if (opts->debug) {
outfile = malloc(strlen(opts->infile) + 5);
if (!outfile)
err(1, "%s: Failed to allocate memory for outfile",
err("%s: Failed to allocate memory for outfile",
__func__);
strcpy(outfile, opts->infile);
strcat(outfile, ".out");
@@ -93,16 +93,16 @@ void output_png_file(const struct Options *opts,
f = fopen(outfile, "wb");
if (!f)
err(1, "Opening output png file '%s' failed", outfile);
err("Opening output png file '%s' failed", outfile);
img.png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
NULL, NULL, NULL);
if (!img.png)
errx(1, "Creating png structure failed");
errx("Creating png structure failed");
img.info = png_create_info_struct(img.png);
if (!img.info)
errx(1, "Creating png info structure failed");
errx("Creating png info structure failed");
if (setjmp(png_jmpbuf(img.png)))
exit(1);
@@ -115,7 +115,7 @@ void output_png_file(const struct Options *opts,
png_palette = malloc(sizeof(*png_palette) * raw_image->num_colors);
if (!png_palette)
err(1, "%s: Failed to allocate memory for PNG palette",
err("%s: Failed to allocate memory for PNG palette",
__func__);
for (i = 0; i < raw_image->num_colors; i++) {
png_palette[i].red = raw_image->palette[i].red;
@@ -159,11 +159,11 @@ static void initialize_png(struct PNGImage *img, FILE *f)
img->png = png_create_read_struct(PNG_LIBPNG_VER_STRING,
NULL, NULL, NULL);
if (!img->png)
errx(1, "Creating png structure failed");
errx("Creating png structure failed");
img->info = png_create_info_struct(img->png);
if (!img->info)
errx(1, "Creating png info structure failed");
errx("Creating png info structure failed");
if (setjmp(png_jmpbuf(img->png)))
exit(1);
@@ -182,7 +182,7 @@ static void read_png(struct PNGImage *img);
static struct RawIndexedImage *create_raw_image(int width, int height,
int num_colors);
static void set_raw_image_palette(struct RawIndexedImage *raw_image,
const png_color *palette, int num_colors);
png_color const *palette, int num_colors);
static struct RawIndexedImage *indexed_png_to_raw(struct PNGImage *img)
{
@@ -215,13 +215,13 @@ static struct RawIndexedImage *indexed_png_to_raw(struct PNGImage *img)
original_palette = palette;
palette = malloc(sizeof(*palette) * colors_in_PLTE);
if (!palette)
err(1, "%s: Failed to allocate memory for palette",
err("%s: Failed to allocate memory for palette",
__func__);
colors_in_new_palette = 0;
old_to_new_palette = malloc(sizeof(*old_to_new_palette)
* colors_in_PLTE);
if (!old_to_new_palette)
err(1, "%s: Failed to allocate memory for new palette",
err("%s: Failed to allocate memory for new palette",
__func__);
for (i = 0; i < num_trans; i++) {
@@ -243,7 +243,7 @@ static struct RawIndexedImage *indexed_png_to_raw(struct PNGImage *img)
sizeof(*palette) *
colors_in_new_palette);
if (!palette)
err(1, "%s: Failed to allocate memory for palette",
err("%s: Failed to allocate memory for palette",
__func__);
}
@@ -291,7 +291,7 @@ static void rgba_png_palette(struct PNGImage *img,
png_color **palette_ptr_ptr, int *num_colors);
static struct RawIndexedImage
*processed_rgba_png_to_raw(const struct PNGImage *img,
const png_color *palette,
png_color const *palette,
int colors_in_palette);
static struct RawIndexedImage *truecolor_png_to_raw(struct PNGImage *img)
@@ -352,7 +352,7 @@ static void rgba_PLTE_palette(struct PNGImage *img,
}
static void update_built_palette(png_color *palette,
const png_color *pixel_color, png_byte alpha,
png_color const *pixel_color, png_byte alpha,
int *num_colors, bool *only_grayscale);
static int fit_grayscale_palette(png_color *palette, int *num_colors);
static void order_color_palette(png_color *palette, int num_colors);
@@ -372,7 +372,7 @@ static void rgba_build_palette(struct PNGImage *img,
*/
*palette_ptr_ptr = calloc(colors, sizeof(**palette_ptr_ptr));
if (!*palette_ptr_ptr)
err(1, "%s: Failed to allocate memory for palette", __func__);
err("%s: Failed to allocate memory for palette", __func__);
palette = *palette_ptr_ptr;
*num_colors = 0;
@@ -398,7 +398,7 @@ static void rgba_build_palette(struct PNGImage *img,
}
static void update_built_palette(png_color *palette,
const png_color *pixel_color, png_byte alpha,
png_color const *pixel_color, png_byte alpha,
int *num_colors, bool *only_grayscale)
{
bool color_exists;
@@ -429,7 +429,7 @@ static void update_built_palette(png_color *palette,
}
if (!color_exists) {
if (*num_colors == colors) {
errx(1, "Too many colors in input PNG file to fit into a %d-bit palette (max %d).",
errx("Too many colors in input PNG file to fit into a %d-bit palette (max %d).",
depth, colors);
}
palette[*num_colors] = *pixel_color;
@@ -445,9 +445,9 @@ static int fit_grayscale_palette(png_color *palette, int *num_colors)
int i, shade_index;
if (!fitted_palette)
err(1, "%s: Failed to allocate memory for palette", __func__);
err("%s: Failed to allocate memory for palette", __func__);
if (!set_indices)
err(1, "%s: Failed to allocate memory for indices", __func__);
err("%s: Failed to allocate memory for indices", __func__);
fitted_palette[0].red = 0xFF;
fitted_palette[0].green = 0xFF;
@@ -491,7 +491,7 @@ struct ColorWithLuminance {
int luminance;
};
static int compare_luminance(const void *a, const void *b)
static int compare_luminance(void const *a, void const *b)
{
const struct ColorWithLuminance *x, *y;
@@ -508,7 +508,7 @@ static void order_color_palette(png_color *palette, int num_colors)
malloc(sizeof(*palette_with_luminance) * num_colors);
if (!palette_with_luminance)
err(1, "%s: Failed to allocate memory for palette", __func__);
err("%s: Failed to allocate memory for palette", __func__);
for (i = 0; i < num_colors; i++) {
/*
@@ -531,12 +531,12 @@ static void order_color_palette(png_color *palette, int num_colors)
static void put_raw_image_pixel(struct RawIndexedImage *raw_image,
const struct PNGImage *img,
int *value_index, int x, int y,
const png_color *palette,
png_color const *palette,
int colors_in_palette);
static struct RawIndexedImage
*processed_rgba_png_to_raw(const struct PNGImage *img,
const png_color *palette,
png_color const *palette,
int colors_in_palette)
{
struct RawIndexedImage *raw_image;
@@ -561,13 +561,13 @@ static struct RawIndexedImage
return raw_image;
}
static uint8_t palette_index_of(const png_color *palette,
int num_colors, const png_color *color);
static uint8_t palette_index_of(png_color const *palette,
int num_colors, png_color const *color);
static void put_raw_image_pixel(struct RawIndexedImage *raw_image,
const struct PNGImage *img,
int *value_index, int x, int y,
const png_color *palette,
png_color const *palette,
int colors_in_palette)
{
png_color pixel_color;
@@ -588,8 +588,8 @@ static void put_raw_image_pixel(struct RawIndexedImage *raw_image,
}
}
static uint8_t palette_index_of(const png_color *palette,
int num_colors, const png_color *color)
static uint8_t palette_index_of(png_color const *palette,
int num_colors, png_color const *color)
{
uint8_t i;
@@ -600,7 +600,7 @@ static uint8_t palette_index_of(const png_color *palette,
return i;
}
}
errx(1, "The input PNG file contains colors that don't appear in its embedded palette.");
errx("The input PNG file contains colors that don't appear in its embedded palette.");
}
static void read_png(struct PNGImage *img)
@@ -611,12 +611,12 @@ static void read_png(struct PNGImage *img)
img->data = malloc(sizeof(*img->data) * img->height);
if (!img->data)
err(1, "%s: Failed to allocate memory for image data",
err("%s: Failed to allocate memory for image data",
__func__);
for (y = 0; y < img->height; y++) {
img->data[y] = malloc(png_get_rowbytes(img->png, img->info));
if (!img->data[y])
err(1, "%s: Failed to allocate memory for image data",
err("%s: Failed to allocate memory for image data",
__func__);
}
@@ -632,7 +632,7 @@ static struct RawIndexedImage *create_raw_image(int width, int height,
raw_image = malloc(sizeof(*raw_image));
if (!raw_image)
err(1, "%s: Failed to allocate memory for raw image",
err("%s: Failed to allocate memory for raw image",
__func__);
raw_image->width = width;
@@ -641,18 +641,18 @@ static struct RawIndexedImage *create_raw_image(int width, int height,
raw_image->palette = malloc(sizeof(*raw_image->palette) * num_colors);
if (!raw_image->palette)
err(1, "%s: Failed to allocate memory for raw image palette",
err("%s: Failed to allocate memory for raw image palette",
__func__);
raw_image->data = malloc(sizeof(*raw_image->data) * height);
if (!raw_image->data)
err(1, "%s: Failed to allocate memory for raw image data",
err("%s: Failed to allocate memory for raw image data",
__func__);
for (y = 0; y < height; y++) {
raw_image->data[y] = malloc(sizeof(*raw_image->data[y])
* width);
if (!raw_image->data[y])
err(1, "%s: Failed to allocate memory for raw image data",
err("%s: Failed to allocate memory for raw image data",
__func__);
}
@@ -660,12 +660,12 @@ static struct RawIndexedImage *create_raw_image(int width, int height,
}
static void set_raw_image_palette(struct RawIndexedImage *raw_image,
const png_color *palette, int num_colors)
png_color const *palette, int num_colors)
{
int i;
if (num_colors > raw_image->num_colors) {
errx(1, "Too many colors in input PNG file's palette to fit into a %d-bit palette (%d in input palette, max %d).",
errx("Too many colors in input PNG file's palette to fit into a %d-bit palette (%d in input palette, max %d).",
raw_image->num_colors >> 1,
num_colors, raw_image->num_colors);
}
@@ -740,7 +740,7 @@ static void set_text(const struct PNGImage *img,
text = malloc(sizeof(*text));
if (!text)
err(1, "%s: Failed to allocate memory for PNG text",
err("%s: Failed to allocate memory for PNG text",
__func__);
if (png_options->horizontal) {

View File

@@ -12,8 +12,8 @@
#include <string.h>
#include <assert.h>
#include "error.h"
#include "hashmap.h"
#include "extern/err.h"
/*
* The lower half of the hash is used to index the "master" table,
@@ -46,14 +46,14 @@ 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;
struct HashMapEntry *newEntry = malloc(sizeof(*newEntry));
if (!newEntry)
err(1, "%s: Failed to allocate new entry", __func__);
err("%s: Failed to allocate new entry", __func__);
newEntry->hash = hashedKey >> HALF_HASH_NB_BITS;
newEntry->key = key;
@@ -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

@@ -19,7 +19,7 @@
#include "link/script.h"
#include "link/output.h"
#include "extern/err.h"
#include "error.h"
#include "helpers.h"
struct MemoryLocation {
@@ -46,13 +46,13 @@ static void initFreeSpace(void)
for (enum SectionType type = 0; type < SECTTYPE_INVALID; type++) {
memory[type] = malloc(sizeof(*memory[type]) * nbbanks(type));
if (!memory[type])
err(1, "Failed to init free space for region %d", type);
err("Failed to init free space for region %d", type);
for (uint32_t bank = 0; bank < nbbanks(type); bank++) {
memory[type][bank].next =
malloc(sizeof(*memory[type][0].next));
if (!memory[type][bank].next)
err(1, "Failed to init free space for region %d bank %" PRIu32,
err("Failed to init free space for region %d bank %" PRIu32,
type, bank);
memory[type][bank].next->address = startaddr[type];
memory[type][bank].next->size = maxsize[type];
@@ -106,8 +106,7 @@ 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;
@@ -160,9 +159,28 @@ static bool isLocationSuitable(struct Section const *section,
static struct FreeSpace *getPlacement(struct Section const *section,
struct MemoryLocation *location)
{
location->bank = section->isBankFixed
? section->bank
: bankranges[section->type][0];
static uint16_t curScrambleROM = 1;
static uint8_t curScrambleWRAM = 1;
static uint8_t curScrambleSRAM = 1;
// Determine which bank we should start searching in
if (section->isBankFixed) {
location->bank = section->bank;
} else if (scrambleROMX && section->type == SECTTYPE_ROMX) {
location->bank = curScrambleROM++;
if (curScrambleROM > scrambleROMX)
curScrambleROM = 1;
} else if (scrambleWRAMX && section->type == SECTTYPE_WRAMX) {
location->bank = curScrambleWRAM++;
if (curScrambleWRAM > scrambleWRAMX)
curScrambleWRAM = 1;
} else if (scrambleSRAM && section->type == SECTTYPE_SRAM) {
location->bank = curScrambleSRAM++;
if (curScrambleSRAM > scrambleSRAM)
curScrambleSRAM = 0;
} else {
location->bank = bankranges[section->type][0];
}
struct FreeSpace *space;
for (;;) {
@@ -283,7 +301,7 @@ static void placeSection(struct Section *section)
struct FreeSpace *newSpace = malloc(sizeof(*newSpace));
if (!newSpace)
err(1, "Failed to split new free space");
err("Failed to split new free space");
/* Append the new space after the chosen one */
newSpace->prev = freeSpace;
newSpace->next = freeSpace->next;
@@ -334,16 +352,16 @@ static void placeSection(struct Section *section)
/* If a section failed to go to several places, nothing we can report */
if (!section->isBankFixed || !section->isAddressFixed)
errx(1, "Unable to place \"%s\" (%s section) %s",
errx("Unable to place \"%s\" (%s section) %s",
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("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 */
else
errx(1, "Unable to place \"%s\" (%s section) %s: section overlaps with \"%s\"",
errx("Unable to place \"%s\" (%s section) %s: section overlaps with \"%s\"",
section->name, typeNames[section->type], where,
out_OverlappingSection(section)->name);
}
@@ -400,7 +418,7 @@ void assign_AssignSections(void)
/* Generate linked lists of sections to assign */
sections = malloc(sizeof(*sections) * nbSectionsToAssign + 1);
if (!sections)
err(1, "Failed to allocate memory for section assignment");
err("Failed to allocate memory for section assignment");
initFreeSpace();
@@ -429,7 +447,7 @@ void assign_AssignSections(void)
/* Overlaying requires only fully-constrained sections */
verbosePrint("Assigning other sections...\n");
if (overlayFileName)
errx(1, "All sections must be fixed when using an overlay file; %" PRIu64 " %sn't",
errx("All sections must be fixed when using an overlay file; %" PRIu64 " %sn't",
nbSectionsToAssign, nbSectionsToAssign == 1 ? "is" : "are");
/* Assign all remaining sections by decreasing constraint order */

View File

@@ -8,6 +8,7 @@
#include <assert.h>
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
#include <stdarg.h>
#include <stdio.h>
@@ -24,8 +25,10 @@
#include "link/patch.h"
#include "link/output.h"
#include "extern/err.h"
#include "extern/getopt.h"
#include "error.h"
#include "platform.h"
#include "version.h"
bool isDmgMode; /* -d */
@@ -35,6 +38,10 @@ char const *symFileName; /* -n */
char const *overlayFileName; /* -O */
char const *outputFileName; /* -o */
uint8_t padValue; /* -p */
// Setting these three to 0 disables the functionality
uint16_t scrambleROMX = 0; /* -S */
uint8_t scrambleWRAMX = 0;
uint8_t scrambleSRAM = 0;
bool is32kMode; /* -t */
bool beVerbose; /* -v */
bool isWRA0Mode; /* -w */
@@ -100,11 +107,25 @@ void error(struct FileStackNode const *where, uint32_t lineNo, char const *fmt,
nbErrors++;
}
void argErr(char flag, char const *fmt, ...)
{
va_list ap;
fprintf(stderr, "error: Invalid argument for option '%c': ", flag);
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
putc('\n', stderr);
if (nbErrors != UINT32_MAX)
nbErrors++;
}
_Noreturn void fatal(struct FileStackNode const *where, uint32_t lineNo, char const *fmt, ...)
{
va_list ap;
fputs("fatal: ", stderr);
fputs("FATAL: ", stderr);
if (where) {
dumpFileStack(where);
fprintf(stderr, "(%" PRIu32 "): ", lineNo);
@@ -118,7 +139,7 @@ _Noreturn void fatal(struct FileStackNode const *where, uint32_t lineNo, char co
nbErrors++;
fprintf(stderr, "Linking aborted after %" PRIu32 " error%s\n", nbErrors,
nbErrors != 1 ? "s" : "");
nbErrors == 1 ? "" : "s");
exit(1);
}
@@ -136,13 +157,13 @@ FILE *openFile(char const *fileName, char const *mode)
file = fdopen(1, mode);
if (!file)
err(1, "Could not open file \"%s\"", fileName);
err("Could not open file \"%s\"", fileName);
return file;
}
/* Short options */
static char const *optstring = "dl:m:n:O:o:p:s:tVvwx";
static const char *optstring = "dl:m:n:O:o:p:S:s:tVvWwx";
/*
* Equivalent long options
@@ -155,20 +176,21 @@ static char const *optstring = "dl:m:n:O:o:p:s:tVvwx";
* over short opt matching
*/
static struct option const longopts[] = {
{ "dmg", no_argument, NULL, 'd' },
{ "linkerscript", required_argument, NULL, 'l' },
{ "map", required_argument, NULL, 'm' },
{ "sym", required_argument, NULL, 'n' },
{ "overlay", required_argument, NULL, 'O' },
{ "output", required_argument, NULL, 'o' },
{ "pad", required_argument, NULL, 'p' },
{ "smart", required_argument, NULL, 's' },
{ "tiny", no_argument, NULL, 't' },
{ "version", no_argument, NULL, 'V' },
{ "verbose", no_argument, NULL, 'v' },
{ "wramx", no_argument, NULL, 'w' },
{ "nopad", no_argument, NULL, 'x' },
{ NULL, no_argument, NULL, 0 }
{ "dmg", no_argument, NULL, 'd' },
{ "linkerscript", required_argument, NULL, 'l' },
{ "map", required_argument, NULL, 'm' },
{ "sym", required_argument, NULL, 'n' },
{ "overlay", required_argument, NULL, 'O' },
{ "output", required_argument, NULL, 'o' },
{ "pad", required_argument, NULL, 'p' },
{ "scramble", required_argument, NULL, 'S' },
{ "smart", required_argument, NULL, 's' },
{ "tiny", no_argument, NULL, 't' },
{ "version", no_argument, NULL, 'V' },
{ "verbose", no_argument, NULL, 'v' },
{ "wramx", no_argument, NULL, 'w' },
{ "nopad", no_argument, NULL, 'x' },
{ NULL, no_argument, NULL, 0 }
};
/**
@@ -178,8 +200,8 @@ static void printUsage(void)
{
fputs(
"Usage: rgblink [-dtVvwx] [-l script] [-m map_file] [-n sym_file]\n"
" [-O overlay_file] [-o out_file] [-p pad_value] [-s symbol]\n"
" <file> ...\n"
" [-O overlay_file] [-o out_file] [-p pad_value]\n"
" [-S spec] [-s symbol] <file> ...\n"
"Useful options:\n"
" -l, --linkerscript <path> set the input linker script\n"
" -m, --map <path> set the output map file\n"
@@ -202,10 +224,136 @@ static void cleanup(void)
obj_Cleanup();
}
enum ScrambledRegion {
SCRAMBLE_ROMX,
SCRAMBLE_SRAM,
SCRAMBLE_WRAMX,
SCRAMBLE_UNK, // Used for errors
};
struct {
char const *name;
uint16_t max;
} scrambleSpecs[SCRAMBLE_UNK] = {
[SCRAMBLE_ROMX] = { "romx", 65535 },
[SCRAMBLE_SRAM] = { "sram", 255 },
[SCRAMBLE_WRAMX] = { "wramx", 7},
};
static void parseScrambleSpec(char const *spec)
{
// Skip any leading whitespace
spec += strspn(spec, " \t");
// The argument to `-S` should be a comma-separated list of sections followed by an '='
// indicating their scramble limit.
while (spec) {
// Invariant: we should not be pointing at whitespace at this point
assert(*spec != ' ' && *spec != '\t');
// Remember where the region's name begins and ends
char const *regionName = spec;
size_t regionNameLen = strcspn(spec, "=, \t");
// Length of region name string slice for printing, truncated if too long
int regionNamePrintLen = regionNameLen > INT_MAX ? INT_MAX : (int)regionNameLen;
// If this trips, `spec` must be pointing at a ',' or '=' (or NUL) due to the assert
if (regionNameLen == 0) {
argErr('S', "Missing region name");
if (*spec == '\0')
break;
if (*spec == '=') // Skip the limit, too
spec = strchr(&spec[1], ','); // Skip to next comma, if any
goto next;
}
// Find the next non-blank char after the region name's end
spec += regionNameLen + strspn(&spec[regionNameLen], " \t");
if (*spec != '\0' && *spec != ',' && *spec != '=') {
argErr('S', "Unexpected '%c' after region name \"%.*s\"",
regionNamePrintLen, regionName);
// Skip to next ',' or '=' (or NUL) and keep parsing
spec += 1 + strcspn(&spec[1], ",=");
}
// Now, determine which region type this is
enum ScrambledRegion region = 0;
while (region < SCRAMBLE_UNK) {
// If the strings match (case-insensitively), we got it!
// It's OK not to use `strncasecmp` because `regionName` is still
// NUL-terminated, since the encompassing spec is.
if (!strcasecmp(scrambleSpecs[region].name, regionName))
break;
region++;
}
if (region == SCRAMBLE_UNK)
argErr('S', "Unknown region \"%.*s\"", regionNamePrintLen, regionName);
if (*spec == '=') {
spec++; // `strtoul` will skip the whitespace on its own
unsigned long limit;
char *endptr;
if (*spec == '\0' || *spec == ',') {
argErr('S', "Empty limit for region \"%.*s\"",
regionNamePrintLen, regionName);
goto next;
}
limit = strtoul(spec, &endptr, 10);
endptr += strspn(endptr, " \t");
if (*endptr != '\0' && *endptr != ',') {
argErr('S', "Invalid non-numeric limit for region \"%.*s\"",
regionNamePrintLen, regionName);
endptr = strchr(endptr, ',');
}
spec = endptr;
if (region != SCRAMBLE_UNK && limit >= scrambleSpecs[region].max) {
argErr('S', "Limit for region \"%.*s\" may not exceed %" PRIu16,
regionNamePrintLen, regionName, scrambleSpecs[region].max);
limit = scrambleSpecs[region].max;
}
switch (region) {
case SCRAMBLE_ROMX:
scrambleROMX = limit;
break;
case SCRAMBLE_SRAM:
scrambleSRAM = limit;
break;
case SCRAMBLE_WRAMX:
scrambleWRAMX = limit;
break;
case SCRAMBLE_UNK: // The error has already been reported, do nothing
break;
}
} else if (region == SCRAMBLE_WRAMX) {
// Only WRAMX can be implied, since ROMX and SRAM size may vary
scrambleWRAMX = 7;
} else {
argErr('S', "Cannot imply limit for region \"%.*s\"",
regionNamePrintLen, regionName);
}
next:
if (spec) {
assert(*spec == ',' || *spec == '\0');
if (*spec == ',')
spec += 1 + strspn(&spec[1], " \t");
if (*spec == '\0')
break;
}
}
}
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 */
@@ -234,15 +382,18 @@ int main(int argc, char *argv[])
case 'p':
value = strtoul(musl_optarg, &endptr, 0);
if (musl_optarg[0] == '\0' || *endptr != '\0') {
error(NULL, 0, "Invalid argument for option 'p'");
argErr('p', "");
value = 0xFF;
}
if (value > 0xFF) {
error(NULL, 0, "Argument for 'p' must be a byte (between 0 and 0xFF)");
argErr('p', "Argument for 'p' must be a byte (between 0 and 0xFF)");
value = 0xFF;
}
padValue = value;
break;
case 'S':
parseScrambleSpec(musl_optarg);
break;
case 's':
/* FIXME: nobody knows what this does, figure it out */
(void)musl_optarg;
@@ -275,7 +426,7 @@ int main(int argc, char *argv[])
/* If no input files were specified, the user must have screwed up */
if (curArgIndex == argc) {
fputs("fatal: no input files\n", stderr);
fputs("FATAL: no input files\n", stderr);
printUsage();
exit(1);
}
@@ -304,7 +455,7 @@ int main(int argc, char *argv[])
patch_ApplyPatches();
if (nbErrors) {
fprintf(stderr, "Linking failed with %" PRIu32 " error%s\n",
nbErrors, nbErrors != 1 ? "s" : "");
nbErrors, nbErrors == 1 ? "" : "s");
exit(1);
}
out_WriteFiles();

View File

@@ -21,7 +21,7 @@
#include "link/section.h"
#include "link/symbol.h"
#include "extern/err.h"
#include "error.h"
#include "helpers.h"
#include "linkdefs.h"
@@ -48,8 +48,9 @@ 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) \
errx(__VA_ARGS__, feof(tmpFile) \
? "Unexpected end of file" \
: strerror(errno)); \
} \
@@ -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
@@ -292,13 +289,13 @@ static void readPatch(FILE *file, struct Patch *patch, char const *fileName, cha
patch->rpnExpression = malloc(sizeof(*patch->rpnExpression) * patch->rpnSize);
if (!patch->rpnExpression)
err(1, "%s: Failed to alloc \"%s\"'s patch #%" PRIu32 "'s RPN expression",
err("%s: Failed to alloc \"%s\"'s patch #%" PRIu32 "'s RPN expression",
fileName, sectName, i);
size_t nbElementsRead = fread(patch->rpnExpression, sizeof(*patch->rpnExpression),
patch->rpnSize, file);
if (nbElementsRead != patch->rpnSize)
errx(1, "%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s RPN expression: %s",
errx("%s: Cannot read \"%s\"'s patch #%" PRIu32 "'s RPN expression: %s",
fileName, sectName, i,
feof(file) ? "Unexpected end of file" : strerror(errno));
}
@@ -330,7 +327,7 @@ static void readSection(FILE *file, struct Section *section, char const *fileNam
tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s' size: %s",
fileName, section->name);
if (tmp < 0 || tmp > UINT16_MAX)
errx(1, "\"%s\"'s section size (%" PRId32 ") is invalid",
errx("\"%s\"'s section size (%" PRId32 ") is invalid",
section->name, tmp);
section->size = tmp;
section->offset = 0;
@@ -376,13 +373,13 @@ static void readSection(FILE *file, struct Section *section, char const *fileNam
uint8_t *data = malloc(sizeof(*data) * section->size + 1);
if (!data)
err(1, "%s: Unable to read \"%s\"'s data", fileName,
err("%s: Unable to read \"%s\"'s data", fileName,
section->name);
if (section->size) {
size_t nbElementsRead = fread(data, sizeof(*data),
section->size, file);
if (nbElementsRead != section->size)
errx(1, "%s: Cannot read \"%s\"'s data: %s",
errx("%s: Cannot read \"%s\"'s data: %s",
fileName, section->name,
feof(file) ? "Unexpected end of file"
: strerror(errno));
@@ -397,7 +394,7 @@ 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);
err("%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;
@@ -452,7 +449,7 @@ static void readAssertion(FILE *file, struct Assertion *assert,
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);
@@ -465,7 +462,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
FILE *file = strcmp("-", fileName) ? fopen(fileName, "rb") : stdin;
if (!file)
err(1, "Could not open file %s", fileName);
err("Could not open file %s", fileName);
/* Begin by reading the magic bytes and version number */
unsigned versionNumber;
@@ -473,13 +470,13 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
&versionNumber);
if (matchedElems != 1)
errx(1, "\"%s\" is not a RGBDS object file", fileName);
errx("\"%s\" is not a RGBDS object file", fileName);
verbosePrint("Reading object file %s, version %u\n",
fileName, versionNumber);
if (versionNumber != RGBDS_OBJECT_VERSION_NUMBER)
errx(1, "\"%s\" is an incompatible version %u object file",
errx("\"%s\" is an incompatible version %u object file",
fileName, versionNumber);
uint32_t revNum;
@@ -487,7 +484,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
tryReadlong(revNum, file, "%s: Cannot read revision number: %s",
fileName);
if (revNum != RGBDS_OBJECT_REV)
errx(1, "%s is a revision 0x%04" PRIx32 " object file; only 0x%04x is supported",
errx("%s is a revision 0x%04" PRIx32 " object file; only 0x%04x is supported",
fileName, revNum, RGBDS_OBJECT_REV);
uint32_t nbSymbols;
@@ -503,7 +500,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
tryReadlong(nodes[fileID].nbNodes, file, "%s: Cannot read number of nodes: %s", fileName);
nodes[fileID].nodes = calloc(nodes[fileID].nbNodes, sizeof(nodes[fileID].nodes[0]));
if (!nodes[fileID].nodes)
err(1, "Failed to get memory for %s's nodes", fileName);
err("Failed to get memory for %s's nodes", fileName);
verbosePrint("Reading %u nodes...\n", nodes[fileID].nbNodes);
for (uint32_t i = nodes[fileID].nbNodes; i--; )
readFileStackNode(file, nodes[fileID].nodes, i, fileName);
@@ -513,12 +510,12 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
malloc(sizeof(*fileSymbols) * nbSymbols + 1);
if (!fileSymbols)
err(1, "Failed to get memory for %s's symbols", fileName);
err("Failed to get memory for %s's symbols", fileName);
struct SymbolList *symbolList = malloc(sizeof(*symbolList));
if (!symbolList)
err(1, "Failed to register %s's symbol list", fileName);
err("Failed to register %s's symbol list", fileName);
symbolList->symbolList = fileSymbols;
symbolList->nbSymbols = nbSymbols;
symbolList->next = symbolLists;
@@ -533,7 +530,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
struct Symbol *symbol = malloc(sizeof(*symbol));
if (!symbol)
err(1, "%s: Couldn't create new symbol", fileName);
err("%s: Couldn't create new symbol", fileName);
readSymbol(file, symbol, fileName, nodes[fileID].nodes);
fileSymbols[i] = symbol;
@@ -552,7 +549,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
/* Read section */
fileSections[i] = malloc(sizeof(*fileSections[i]));
if (!fileSections[i])
err(1, "%s: Couldn't create new section", fileName);
err("%s: Couldn't create new section", fileName);
fileSections[i]->nextu = NULL;
readSection(file, fileSections[i], fileName, nodes[fileID].nodes);
@@ -561,7 +558,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
fileSections[i]->symbols = malloc(nbSymPerSect[i]
* sizeof(*fileSections[i]->symbols));
if (!fileSections[i]->symbols)
err(1, "%s: Couldn't link to symbols",
err("%s: Couldn't link to symbols",
fileName);
} else {
fileSections[i]->symbols = NULL;
@@ -612,7 +609,7 @@ void obj_ReadFile(char const *fileName, unsigned int fileID)
struct Assertion *assertion = malloc(sizeof(*assertion));
if (!assertion)
err(1, "%s: Couldn't create new assertion", fileName);
err("%s: Couldn't create new assertion", fileName);
readAssertion(file, assertion, fileName, i, nodes[fileID].nodes);
linkPatchToPCSect(&assertion->patch, fileSections);
assertion->fileSymbols = fileSymbols;
@@ -643,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 (uint32_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)
@@ -668,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);
}
@@ -680,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>
@@ -15,10 +16,8 @@
#include "link/section.h"
#include "link/symbol.h"
#include "extern/err.h"
#include "error.h"
#include "linkdefs.h"
#include "platform.h" // MIN_NB_ELMS
#define BANK_SIZE 0x4000
@@ -33,6 +32,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 {
@@ -70,7 +75,7 @@ void out_AddSection(struct Section const *section)
uint32_t minNbBanks = targetBank + 1;
if (minNbBanks > maxNbBanks[section->type])
errx(1, "Section \"%s\" has an invalid bank range (%" PRIu32 " > %" PRIu32 ")",
errx("Section \"%s\" has an invalid bank range (%" PRIu32 " > %" PRIu32 ")",
section->name, section->bank,
maxNbBanks[section->type] - 1);
@@ -85,7 +90,7 @@ void out_AddSection(struct Section const *section)
sections[section->type].nbBanks = minNbBanks;
}
if (!sections[section->type].banks)
err(1, "Failed to realloc banks");
err("Failed to realloc banks");
struct SortedSection *newSection = malloc(sizeof(*newSection));
struct SortedSection **ptr = section->size
@@ -93,7 +98,7 @@ void out_AddSection(struct Section const *section)
: &sections[section->type].banks[targetBank].zeroLenSections;
if (!newSection)
err(1, "Failed to add new section \"%s\"", section->name);
err("Failed to add new section \"%s\"", section->name);
newSection->section = section;
while (*ptr && (*ptr)->section->org < section->org)
@@ -138,15 +143,15 @@ static uint32_t checkOverlaySize(void)
fseek(overlayFile, 0, SEEK_SET);
if (overlaySize % BANK_SIZE)
errx(1, "Overlay file must have a size multiple of 0x4000");
errx("Overlay file must have a size multiple of 0x4000");
uint32_t nbOverlayBanks = overlaySize / BANK_SIZE;
if (is32kMode && nbOverlayBanks != 2)
errx(1, "Overlay must be exactly 0x8000 bytes large");
errx("Overlay must be exactly 0x8000 bytes large");
if (nbOverlayBanks < 2)
errx(1, "Overlay must be at least 0x8000 bytes large");
errx("Overlay must be at least 0x8000 bytes large");
return nbOverlayBanks;
}
@@ -171,7 +176,7 @@ static void coverOverlayBanks(uint32_t nbOverlayBanks)
realloc(sections[SECTTYPE_ROMX].banks,
sizeof(*sections[SECTTYPE_ROMX].banks) * nbUncoveredBanks);
if (!sections[SECTTYPE_ROMX].banks)
err(1, "Failed to realloc banks for overlay");
err("Failed to realloc banks for overlay");
for (uint32_t i = sections[SECTTYPE_ROMX].nbBanks; i < nbUncoveredBanks; i++) {
sections[SECTTYPE_ROMX].banks[i].sections = NULL;
sections[SECTTYPE_ROMX].banks[i].zeroLenSections = NULL;
@@ -268,69 +273,86 @@ 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("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);
}
/**
@@ -360,7 +382,8 @@ static uint16_t writeMapBank(struct SortedSections const *sectList,
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);
@@ -443,7 +466,7 @@ static void writeSymAndMap(void)
for (uint32_t bank = 0; bank < sections[type].nbBanks; bank++) {
struct SortedSections const *sect = &sections[type].banks[bank];
writeSymBank(sect);
writeSymBank(sect, type, bank);
usedMap[type] += writeMapBank(sect, type, bank);
}
}

View File

@@ -17,11 +17,10 @@
#include "link/section.h"
#include "link/symbol.h"
#include "error.h"
#include "linkdefs.h"
#include "opmath.h"
#include "extern/err.h"
/*
* This is an "empty"-type stack. Apart from the actual values, we also remember
* whether the value is a placeholder inserted for error recovery. This allows
@@ -37,16 +36,16 @@ 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);
stack.errorFlags = malloc(sizeof(*stack.errorFlags) * stack.capacity);
if (!stack.values || !stack.errorFlags)
err(1, "Failed to init RPN stack");
err("Failed to init RPN stack");
}
static inline void clearRPNStack(void)
static void clearRPNStack(void)
{
stack.size = 0;
}
@@ -57,7 +56,7 @@ static void pushRPN(int32_t value, bool comesFromError)
static const size_t increase_factor = 2;
if (stack.capacity > SIZE_MAX / increase_factor)
errx(1, "Overflow in RPN stack resize");
errx("Overflow in RPN stack resize");
stack.capacity *= increase_factor;
stack.values =
@@ -70,7 +69,7 @@ static void pushRPN(int32_t value, bool comesFromError)
* the overflow check above. Hence the stringent check below.
*/
if (!stack.values || !stack.errorFlags || !stack.capacity)
err(1, "Failed to resize RPN stack");
err("Failed to resize RPN stack");
}
stack.values[stack.size] = value;
@@ -92,7 +91,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 +291,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 +324,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
@@ -435,6 +477,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

@@ -20,6 +20,7 @@
.Op Fl O Ar overlay_file
.Op Fl o Ar out_file
.Op Fl p Ar pad_value
.Op Fl S Ar spec
.Op Fl s Ar symbol
.Ar
.Sh DESCRIPTION
@@ -89,6 +90,14 @@ Has no effect if
.Fl O
is specified.
The default is 0.
.It Fl S Ar spec , Fl Fl scramble Ar spec
Enables a different
.Dq scrambling
algorithm for placing sections.
See
.Sx Scrambling algorithm
below for an explanation and a description of
.Ar spec .
.It Fl s Ar symbol , Fl Fl smart Ar symbol
This option is ignored.
It was supposed to perform smart linking but fell into disrepair, and so has been removed.
@@ -113,6 +122,56 @@ When making a ROM, be careful that not using this is not a replacement for
.Xr rgbfix 1 Ap s Fl p
option!
.El
.Ss Scrambling algorithm
The default section placement algorithm tries to minimize the number of banks used;
.Dq scrambling
instead places sections into a given pool of banks, trying to minimize the number of sections sharing a given bank.
This is useful to catch broken bank assumptions, such as expecting two different sections to land in the same bank (that is not guaranteed unless both are manually assigned the same bank number).
.Pp
A scrambling spec is a comma-separated list of region specs.
A trailing comma is allowed, as well as whitespace between all specs and their components.
Each region spec has the following form:
.D1 Ar region Ns Op = Ns Ar size
.Ar region
must be one of the following (case-insensitive), while
.Ar size
must be a positive decimal integer between 1 and the corresponding maximum.
Certain regions allow omitting the size, in which case it defaults to its max value.
.Bl -column "Region name" "Max value" "Size optional"
Region name Ta Max size Ta Size optional
.Cm romx Ta 65535 Ta \&No
.Cm sram Ta 255 Ta \&No
.Cm wramx Ta 7 Ta Yes
.El
.Pp
A
.Ar size
of 0 disables scrambling for that region.
.Pp
For example,
.Ql romx=64,wramx=4
will scramble
.Ic ROMX
sections among ROM banks 1 to 64,
.Ic WRAMX
sections among RAM banks 1 to 4, and will not scramble
.Ic SRAM
sections.
.Pp
Later region specs override earlier ones; for example,
.Ql romx=42, Romx=0
disables scrambling for
.Cm romx .
.Pp
.Cm wramx
scrambling is silently ignored if
.Fl w
is passed (including if implied by
.Fl d ) ,
as
.Ic WRAMX
sections will be treated as
.Ic WRAM0 .
.Sh EXAMPLES
All you need for a basic ROM is an object file, which can be made into a ROM image like so:
.Pp

View File

@@ -17,9 +17,9 @@
#include "link/script.h"
#include "link/section.h"
#include "extern/err.h"
#include "error.h"
FILE * linkerScript;
FILE *linkerScript;
char *includeFileName;
static uint32_t lineNo;
@@ -36,17 +36,16 @@ static uint32_t fileStackIndex;
static void pushFile(char *newFileName)
{
if (fileStackIndex == UINT32_MAX)
errx(1, "%s(%" PRIu32 "): INCLUDE recursion limit reached",
errx("%s(%" PRIu32 "): INCLUDE recursion limit reached",
linkerScriptName, lineNo);
if (fileStackIndex == fileStackSize) {
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",
err("%s(%" PRIu32 "): Internal INCLUDE error",
linkerScriptName, lineNo);
}
@@ -57,7 +56,7 @@ static void pushFile(char *newFileName)
linkerScript = fopen(newFileName, "r");
if (!linkerScript)
err(1, "%s(%" PRIu32 "): Could not open \"%s\"",
err("%s(%" PRIu32 "): Could not open \"%s\"",
linkerScriptName, lineNo, newFileName);
lineNo = 1;
linkerScriptName = newFileName;
@@ -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,12 +172,12 @@ 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))
err(1, "%s(%" PRIu32 "): Unexpected error in %s",
if (curchar == EOF && ferror(linkerScript))
err("%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,12 @@ 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);
}
} else if (curchar == '"') {
/* If we have a string start, this is a string */
token.type = TOKEN_STRING;
@@ -222,20 +224,35 @@ static struct LinkerScriptToken *nextToken(void)
size_t capacity = 16; /* Half of the default capacity */
do {
curchar = readChar(linkerScript);
if (curchar == EOF || isNewline(curchar))
errx(1, "%s(%" PRIu32 "): Unterminated string",
curchar = nextChar();
if (curchar == EOF || isNewline(curchar)) {
errx("%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("%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("%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",
err("%s: Failed to allocate memory for string",
__func__);
}
token.attr.string[size++] = curchar;
@@ -251,7 +268,7 @@ static struct LinkerScriptToken *nextToken(void)
capacity *= 2;
str = realloc(str, capacity);
if (!str)
err(1, "%s: Failed to allocate memory for token",
err("%s: Failed to allocate memory for token",
__func__);
}
str[size] = toupper(curchar);
@@ -260,10 +277,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 +298,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;
@@ -303,7 +318,7 @@ static struct LinkerScriptToken *nextToken(void)
if (tryParseNumber(str, &token.attr.number))
token.type = TOKEN_NUMBER;
else
errx(1, "%s(%" PRIu32 "): Unknown token \"%s\"",
errx("%s(%" PRIu32 "): Unknown token \"%s\"",
linkerScriptName, lineNo, str);
}
@@ -313,8 +328,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:
@@ -331,7 +345,7 @@ static void processCommand(enum LinkerScriptCommand command, uint16_t arg,
}
if (arg < *pc)
errx(1, "%s(%" PRIu32 "): `%s` cannot be used to go backwards (currently at $%x)",
errx("%s(%" PRIu32 "): `%s` cannot be used to go backwards (currently at $%x)",
linkerScriptName, lineNo, commands[command], *pc);
*pc = arg;
}
@@ -380,11 +394,11 @@ struct SectionPlacement *script_NextSection(void)
if (type != SECTTYPE_INVALID) {
if (curaddr[type][bankID] > endaddr(type) + 1)
errx(1, "%s(%" PRIu32 "): Sections would extend past the end of %s ($%04" PRIx16 " > $%04" PRIx16 ")",
errx("%s(%" PRIu32 "): Sections would extend past the end of %s ($%04" PRIx16 " > $%04" PRIx16 ")",
linkerScriptName, lineNo, typeNames[type],
curaddr[type][bankID], endaddr(type));
if (curaddr[type][bankID] < startaddr[type])
errx(1, "%s(%" PRIu32 "): PC underflowed ($%04" PRIx16 " < $%04" PRIx16 ")",
errx("%s(%" PRIu32 "): PC underflowed ($%04" PRIx16 " < $%04" PRIx16 ")",
linkerScriptName, lineNo,
curaddr[type][bankID], startaddr[type]);
}
@@ -405,7 +419,7 @@ struct SectionPlacement *script_NextSection(void)
break;
case TOKEN_NUMBER:
errx(1, "%s(%" PRIu32 "): stray number \"%" PRIu32 "\"",
errx("%s(%" PRIu32 "): stray number \"%" PRIu32 "\"",
linkerScriptName, lineNo,
token->attr.number);
@@ -418,13 +432,13 @@ struct SectionPlacement *script_NextSection(void)
parserState = PARSER_LINEEND;
if (type == SECTTYPE_INVALID)
errx(1, "%s(%" PRIu32 "): Didn't specify a location before the section",
errx("%s(%" PRIu32 "): Didn't specify a location before the section",
linkerScriptName, lineNo);
section.section =
sect_GetSection(token->attr.string);
if (!section.section)
errx(1, "%s(%" PRIu32 "): Unknown section \"%s\"",
errx("%s(%" PRIu32 "): Unknown section \"%s\"",
linkerScriptName, lineNo,
token->attr.string);
section.org = curaddr[type][bankID];
@@ -453,14 +467,13 @@ struct SectionPlacement *script_NextSection(void)
if (tokType == TOKEN_COMMAND) {
if (type == SECTTYPE_INVALID)
errx(1, "%s(%" PRIu32 "): Didn't specify a location before the command",
errx("%s(%" PRIu32 "): Didn't specify a location before the command",
linkerScriptName, lineNo);
if (!hasArg)
errx(1, "%s(%" PRIu32 "): Command specified without an argument",
errx("%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;
/*
@@ -468,16 +481,16 @@ struct SectionPlacement *script_NextSection(void)
* specifying the number is optional.
*/
if (!hasArg && nbbanks(type) != 1)
errx(1, "%s(%" PRIu32 "): Didn't specify a bank number",
errx("%s(%" PRIu32 "): Didn't specify a bank number",
linkerScriptName, lineNo);
else if (!hasArg)
arg = bankranges[type][0];
else if (arg < bankranges[type][0])
errx(1, "%s(%" PRIu32 "): specified bank number is too low (%" PRIu32 " < %" PRIu32 ")",
errx("%s(%" PRIu32 "): specified bank number is too low (%" PRIu32 " < %" PRIu32 ")",
linkerScriptName, lineNo,
arg, bankranges[type][0]);
else if (arg > bankranges[type][1])
errx(1, "%s(%" PRIu32 "): specified bank number is too high (%" PRIu32 " > %" PRIu32 ")",
errx("%s(%" PRIu32 "): specified bank number is too high (%" PRIu32 " > %" PRIu32 ")",
linkerScriptName, lineNo,
arg, bankranges[type][1]);
bank = arg;
@@ -497,7 +510,7 @@ struct SectionPlacement *script_NextSection(void)
case PARSER_INCLUDE:
if (token->type != TOKEN_STRING)
errx(1, "%s(%" PRIu32 "): Expected a file name after INCLUDE",
errx("%s(%" PRIu32 "): Expected a file name after INCLUDE",
linkerScriptName, lineNo);
/* Switch to that file */
@@ -517,7 +530,7 @@ lineend:
return NULL;
parserState = PARSER_LINEEND;
} else if (token->type != TOKEN_NEWLINE)
errx(1, "%s(%" PRIu32 "): Unexpected %s at the end of the line",
errx("%s(%" PRIu32 "): Unexpected %s at the end of the line",
linkerScriptName, lineNo,
tokenTypes[token->type]);
break;

View File

@@ -14,8 +14,7 @@
#include "link/main.h"
#include "link/section.h"
#include "extern/err.h"
#include "error.h"
#include "hashmap.h"
HashMap sections;
@@ -44,13 +43,13 @@ static void checkSectUnionCompat(struct Section *target, struct Section *other)
if (other->isAddressFixed) {
if (target->isAddressFixed) {
if (target->org != other->org)
errx(1, "Section \"%s\" is defined with conflicting addresses $%04"
errx("Section \"%s\" is defined with conflicting addresses $%04"
PRIx16 " and $%04" PRIx16,
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("Section \"%s\" is defined with conflicting %d-byte alignment (offset %"
PRIu16 ") and address $%04" PRIx16,
other->name, target->alignMask + 1,
target->alignOfs, other->org);
}
@@ -60,16 +59,15 @@ static void checkSectUnionCompat(struct Section *target, struct Section *other)
} else if (other->isAlignFixed) {
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 ")",
errx("Section \"%s\" is defined with conflicting address $%04"
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("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)) {
@@ -86,14 +84,14 @@ static void checkFragmentCompat(struct Section *target, struct Section *other)
if (target->isAddressFixed) {
if (target->org != org)
errx(1, "Section \"%s\" is defined with conflicting addresses $%04"
errx("Section \"%s\" is defined with conflicting addresses $%04"
PRIx16 " and $%04" PRIx16,
other->name, target->org, other->org);
} else if (target->isAlignFixed) {
if ((org - target->alignOfs) & target->alignMask)
errx(1, "Section \"%s\" is defined with conflicting %" PRIu16
"-byte alignment (offset %" PRIu16 ") and address $%04" PRIx16,
errx("Section \"%s\" is defined with conflicting %d-byte alignment (offset %"
PRIu16 ") and address $%04" PRIx16,
other->name, target->alignMask + 1,
target->alignOfs, other->org);
}
@@ -108,16 +106,15 @@ 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 ")",
errx("Section \"%s\" is defined with conflicting address $%04"
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("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);
@@ -134,7 +131,7 @@ static void mergeSections(struct Section *target, struct Section *other, enum Se
// Common checks
if (target->type != other->type)
errx(1, "Section \"%s\" is defined with conflicting types %s and %s",
errx("Section \"%s\" is defined with conflicting types %s and %s",
other->name, typeNames[target->type], typeNames[other->type]);
if (other->isBankFixed) {
@@ -142,7 +139,7 @@ static void mergeSections(struct Section *target, struct Section *other, enum Se
target->isBankFixed = true;
target->bank = other->bank;
} else if (target->bank != other->bank) {
errx(1, "Section \"%s\" is defined with conflicting banks %" PRIu32 " and %"
errx("Section \"%s\" is defined with conflicting banks %" PRIu32 " and %"
PRIu32, other->name, target->bank, other->bank);
}
}
@@ -163,8 +160,11 @@ static void mergeSections(struct Section *target, struct Section *other, enum Se
target->data = realloc(target->data,
sizeof(*target->data) * target->size + 1);
if (!target->data)
errx(1, "Failed to concatenate \"%s\"'s fragments", target->name);
errx("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;
@@ -183,14 +183,14 @@ void sect_AddSection(struct Section *section)
if (other) {
if (section->modifier != other->modifier)
errx(1, "Section \"%s\" defined as %s and %s", section->name,
errx("Section \"%s\" defined as %s and %s", section->name,
sectionModNames[section->modifier], sectionModNames[other->modifier]);
else if (section->modifier == SECTION_NORMAL)
errx(1, "Section name \"%s\" is already in use", section->name);
errx("Section name \"%s\" is already in use", section->name);
else
mergeSections(other, section, section->modifier);
} else if (section->modifier == SECTION_UNION && sect_HasData(section->type)) {
errx(1, "Section \"%s\" is of type %s, which cannot be unionized",
errx("Section \"%s\" is of type %s, which cannot be unionized",
section->name, typeNames[section->type]);
} else {
/* If not, add it */
@@ -249,7 +249,7 @@ static void doSanityChecks(struct Section *section, void *ptr)
/* 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];
@@ -289,9 +289,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
@@ -301,5 +301,5 @@ void sect_DoSanityChecks(void)
{
sect_ForEach(doSanityChecks, NULL);
if (sanityChecksFailed)
errx(1, "Sanity checks failed");
errx("Sanity checks failed");
}

View File

@@ -14,7 +14,7 @@
#include "link/symbol.h"
#include "link/main.h"
#include "extern/err.h"
#include "error.h"
#include "hashmap.h"
HashMap symbols;
@@ -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

@@ -226,13 +226,13 @@ 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 ~
.It Li $21 Ta Li && comparison
.It Li $22 Ta Li || comparison
.It Li $23 Ta Li unary\ !
.It Li $23 Ta Li unary \&!
.It Li $30 Ta Li == comparison
.It Li $31 Ta Li != comparison
.It Li $32 Ta Li > comparison
@@ -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

@@ -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

@@ -12,7 +12,7 @@
#include "helpers.h"
#include "version.h"
const char *get_package_version_string(void)
char const *get_package_version_string(void)
{
// The following conditional should be simplified by the compiler.
if (strlen(BUILD_VERSION_STRING) == 0) {

3
test/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/pokecrystal/
/pokered/
/ucity/

View File

@@ -1,3 +1,3 @@
ERROR: align-large-ofs.asm(2):
error: align-large-ofs.asm(2):
Alignment offset (2) must be smaller than alignment size (2)
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)!

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