Compare commits

..

158 Commits

Author SHA1 Message Date
ISSOtm
b61a187b19 Add forgotten __RGBDS_RC__ symbol 2021-03-10 01:09:18 +01:00
ISSOtm
6382f13647 Release 0.5.0-rc1 2021-03-10 01:04:23 +01:00
ISSOtm
714d39c86f Make some INCBIN errors non-fatal 2021-03-10 01:02:45 +01:00
ISSOtm
f11241c2ae Add INCBIN tests 2021-03-10 01:02:45 +01:00
daid
cb47ac8b97 Change the start position check on INCBIN
Currently INCBIN gives an error if your start position == file size.
This means you cannot include an empty file.
It also means the text of the error message is incorrect
(as the start is not greater, but equal to the length of the file)
2021-03-10 01:02:45 +01:00
ISSOtm
028e7af7d1 Prepare release candidates
We'll use "-rcX" instead of "-pre" to allow multiple ones, jic
Additionally, they will be able to be detected using __RGBDS_RC__
Finally, adapt everything version-related to this new system
2021-03-10 00:06:32 +01:00
ISSOtm
e141da1d94 Compute fallback version string at compile time 2021-03-09 23:59:34 +01:00
ISSOtm
35367282e4 Add workflow to auto-prepare a release
Just push a tag beginning with "v" and a number to trigger it
2021-03-09 22:58:42 +01:00
ISSOtm
4c0fa6732e Fix release doc creation workflow 2021-03-09 22:58:42 +01:00
ISSOtm
e9d9a44687 Make release doc update also register docs 2021-03-09 22:58:42 +01:00
ISSOtm
611bd46e0b Ignore CRLF vs LF for syntax error test check 2021-03-09 22:58:32 +01:00
ISSOtm
e67254093e Delete version test if stale 2021-03-09 22:58:00 +01:00
ISSOtm
3ce3d2f662 Explicitly force tested projects to use tested bins 2021-03-09 22:56:59 +01:00
ISSOtm
3da9d81071 Ignore DLLs too
Since you may want to put them next to the EXEs here
2021-03-09 22:46:17 +01:00
Rangi
88646093ca Swap the Name and Type columns in Predeclared Symbols 2021-03-05 17:00:06 -05:00
Rangi
0a0427afbb windows-xbuild CI uses ubuntu-18.04 for its gcc 7.3.0 (#784)
GitHub's ubuntu-18.04 environment installs gcc 7.3.0.
The latest ubuntu-20.04 environment installs gcc 9.3.0.
Version 9.3.0 is incompatible with building libpng 1.6.37,
as of 2021-03-04.

Fixes #783
2021-03-05 11:27:12 -05:00
Rangi
88048cdcd7 Clarify the character encoding example in rgbasm.5 2021-03-05 10:08:43 -05:00
Rangi
c878ff8775 Report slack totals at the end of the .map file (#781)
Fixes #777
2021-03-05 08:53:27 -05:00
dannye
a4049a3f1b rgbasm(5): Clarify charmap behavior (#558) 2021-03-05 13:16:49 +01:00
Rangi
8a75cc5051 Test an indented anonymous label 2021-03-03 12:26:35 -05:00
Rangi
b674c5fcaa Comment refers to "built-in" symbols 2021-03-03 10:35:47 -05:00
daid
a6d3df7ac9 Put all local symbols in object/sym/map files (#774)
* Store all the local symbols in object files, not only the ones that need linker patches.
  This generates better map/sym files, and thus allows for better debugging as well.

* Add comments explaining the ->src and the (void)arg;
2021-03-03 10:59:51 +01:00
Rangi
365484ef18 Factor out handleCRLF()
This logic repeats ten times
2021-03-03 10:03:52 +01:00
Rangi
5e2bd35239 Factor out a 'plain_directive' parser rule similar to the reverted 'last_line'
Also expand on the comment explaining how the EOF-newline hack affects the parser
2021-03-02 20:46:17 -05:00
Rangi
6655e04ef0 Restore the "EOF-newline" lexer hack
This was removed in b3c0db218d
(along with two unrelated changes).

Removing this hack introduced issue #742, whereby INCLUDing
a file without a trailing newline can cause a syntax error.

A more proper fix would involve Bison's tracking locations,
but for now the EOF-newline hack fixes the issue while only
affecting some reported errors (expecting "newline"
instead of "end of file").

Fixes #742
2021-03-02 16:53:56 -08:00
ISSOtm
40c6b840f8 Add tests for certain directives at EOF without a newline 2021-03-02 16:53:56 -08:00
daid
5d6e0677d9 Fix error-related issues (#773)
* Mark `error` as a `format` function, to properly scan its format

* Fix the call to error() from parser.y:
  - Use '%s' to avoid passing an arbitrary format
  - Simplify yyerror overall

* Fix size parameter of %.*s format being an int... bonkers standard.

* Report the number of arguments required and provided on a STRFMT mismatch

* Add an assert to check for a very unlikely bug
2021-03-02 16:34:19 +01:00
Rangi
56071599e7 Allow trailing commas in bare lists
This applies to macro arguments, DB, DW, DL, DS,
PRINT, PRINTLN, EXPORT, PURGE, and OPT.

It also removes support for empty entries in DB/DW/DL.
(Deprecating it would require keeping parser support,
which is ambiguous with trailing commas.)

Fixes #753
2021-03-02 11:48:20 +01:00
Rangi
c637447d5d Make the "db/dw/dl directive without data in ROM" warning more specific
Also use uppercase for DB/DW/DL to be consistent
2021-03-02 11:48:20 +01:00
Rangi
ac2cefdd87 Refactor some math functions into a shared file for rgbasm and rgblink
Fixes #769

Fixes #770
2021-03-02 11:40:03 +01:00
Rangi
0774f5eb9d Rename math.c/mymath.h to fixpoint.c/.h
This also changes the functions' prefix
from "math_" to "fix_".
2021-03-02 11:40:03 +01:00
Rangi
76d8862900 Refactor part of getSection into createSection
getSection validates its parameters and calls
either mergeSections or createSection.
2021-02-28 16:12:03 -05:00
Rangi
1dafc1c762 Allow empty macro arguments, with a warning
Fixes #739
2021-02-28 10:38:49 -08:00
Rangi
63d15ac8c9 append_yylval_tzString should always evaluate its argument
Fixes #762
2021-02-28 10:38:49 -08:00
Rangi
1f579deaff Trim right whitespace from macro args before warning about length 2021-02-28 10:38:49 -08:00
Rangi
fad48326f8 Support /* block comments */ in macro arguments
Fixes #746
2021-02-28 10:38:49 -08:00
Rangi
e7d6ddf593 Fix linking tiny overlay files (#755)
* Fix compatibility of rgblink -O and -t

The -t "tiny mode" option makes ROM0 cover 0x8000 bytes,
not 0x4000. The -O "overlay" option fills areas uncovered
by sections with data from an overlay file. These needed
to cooperate so that the calculated uncovered overlay size
does not exceed the actual size of the ROM.

Fixes #754

* Print link test names like asm tests do

* Make the three test.sh scripts more similar
2021-02-24 23:04:51 -05:00
Rangi
953f79c0d9 Support 'MACRO mac' as well as 'mac: MACRO' for defining macros
The new syntax is used in documentation, but
the old syntax is not yet deprecated.
2021-02-25 04:42:35 +01:00
Rangi
3c5e1caa7c Disallow "." as a local label
Fixes #760
2021-02-25 04:40:42 +01:00
Rangi
d4028fff10 Prevent ELIF or ELSE after an ELSE
Fixes #749
2021-02-25 04:39:49 +01:00
Rangi
dd892d61d8 Correct rgbasm(5) about whitespace before labels
Also rephrase some more label-related documentation
2021-02-23 15:31:29 -05:00
Rangi
a09f2d4115 Test an indented macro label 2021-02-23 14:48:27 -05:00
Eldred Habert
dafef5a953 Remove column 1 restriction for labels with colons (#635)
Partial fix for #457
2021-02-23 14:42:24 -05:00
dannye
929e2a4490 rgbasm: Report conflicting file/line number for duplicate sections 2021-02-23 20:08:59 +01:00
dannye
cc1129093d Add duplicate-section test 2021-02-23 20:08:59 +01:00
ISSOtm
72911ada2d Prevent non-numeric symbol from overriding refs
Fixes #751
2021-02-22 13:00:25 +01:00
Rangi
037bc7abb3 Add an rgblink test case for an $8000-byte ROM0 section with -t 2021-02-21 16:30:02 -05:00
Rangi
5fd636ac4b Implement floored / and divisor-sign % operators (#745)
Fixes #703
2021-02-21 16:14:44 -05:00
Rangi
a6d844a9a5 Add more test cases for IF, REPT, and MACRO (#748) 2021-02-19 19:34:21 -05:00
ISSOtm
bee62076c6 Allow ENDC at EOF without a newline 2021-02-20 00:51:33 +01:00
ISSOtm
cd072d5e6a Add assertion check for potential UAF trigger
It actually currently triggers if an INCLUDE directive is given at EOF,
but #742 will fix that.
2021-02-19 16:53:30 +01:00
ISSOtm
6ef3ee1391 Give void* ptr to %p formatter
Silences a format type mismatch warning
2021-02-19 16:53:26 +01:00
ISSOtm
c29b616f93 Fix forgotten initialization of lexerState->isReferenced 2021-02-18 16:33:06 +01:00
Rangi
39c179aa32 Fix missing newline in a dbgPrint 2021-02-17 13:14:02 -05:00
Rangi
5c1ae4ce22 Shorter quine test cases 2021-02-17 11:51:21 -05:00
Rangi
efbfeca292 Avoid two peek(1) calls when lexing """multi-line strings""" 2021-02-17 10:36:36 -05:00
Rangi
7dd34f1572 Format INT32_MIN as '-2147483648', not '--2147483648' 2021-02-17 09:48:40 -05:00
Rangi
748e7dd4c7 Fix calculation of 2**30
In exponent(), 'base *= base;' should not run
when base is 65536, since it overflows an int32_t.

This also optimizes exponent() based on
gcc and clang -O3 test cases in godbolt.org.
2021-02-17 09:25:02 -05:00
Rangi
d049ffc0f0 Handle string literals within macro arguments (#685)
Fixes #683 and #691

The lexer's raw mode for reading macro args already attempted
to handle semicolons inside string literals, versus outside ones
which start comments. This change reuses the same function for
reading string literals in normal and raw modes, also handling:

- Commas in strings versus between macro args
- Character escapes
- {Interpolations} and \1-\9 args inside vs. outside strings
- Multi-line string literals

Macro args now allow escaping '\', '"', and '\\'.

A consistent model for expanding macro args and interpolations,
within macro args, string literals, and normal context:

- "{S}" should always equal the contents of S
- "\1" should always act like quoting the value of \1
2021-02-16 22:44:25 -05:00
Rangi
8c0275480c Allow ds to take multiple values to repeat for a count (#725)
Fixes #722
2021-02-16 22:01:23 -05:00
Rangi
76d6ef8695 Implement LOAD UNION and LOAD FRAGMENT
Fix #632
2021-02-17 03:42:06 +01:00
Rangi
c67a696a87 Use macros to convert between radians and fixed-point 65536ths 2021-02-16 18:07:05 -05:00
Rangi
ee20d9010e Make @ relative to the start of a ds even at link time
Fix #737
2021-02-16 22:47:07 +01:00
Rangi
2bc12447e2 Update rgbds(5) object file format documentation (#740) 2021-02-16 16:36:37 -05:00
Rangi
cb61da8842 -Wmacro-shift warns about shifting macro arguments too far (#741)
Fixes #735
2021-02-16 16:31:01 -05:00
ISSOtm
122ba6eba5 Update deprecated feature examples
Those have been removed, lol
2021-02-16 22:09:31 +01:00
Rangi
dddff0f450 Cannot start a new section that's already on the stack
This is only relevant for FRAGMENT or UNION sections, since
normal ones would fail with "Section already defined previously".

Fixes #730
2021-02-16 21:51:48 +01:00
Antonio Vivace
a919f922a1 Add FUNDING.yml to show the Sponsor button on the repository 2021-02-16 21:23:27 +01:00
Rangi
8f20620c16 Allow shifting macro arguments by a negative amount
Fixes #733
2021-02-14 16:21:00 +01:00
Rangi
96bce05be2 Newlines in multi-line strings update the line number
This affects error and warning messages, and dbgPrint
2021-02-14 16:16:52 +01:00
Rangi
fc2bf3d11d Prefer sizeof($$) to MAXSTRLEN + 1
This makes `strsubUTF8` similar to `strrpl` and `strfmt`
2021-02-14 16:16:52 +01:00
Rangi
8415ce3ed0 Correct the keywordDict size
The stale keywords XDEF and GLOBAL were removed,
so there can be fewer keyword nodes.
2021-02-14 16:16:52 +01:00
Rangi
ebb5aab6db Correct some comments
nPCOffset no longer exists, and the lexer only
returns T_NEWLINE for EOF in raw mode.
2021-02-14 16:16:52 +01:00
Rangi
464a3a4892 Separate extern getopt implementation from the unistd.h one
Fixes #710
2021-02-12 00:29:21 +01:00
ISSOtm
88e1cc7302 Make EOF token name consistent across Bison versions
The "end of file" name apparently only became a default recently
2021-02-11 13:04:43 +01:00
ISSOtm
b3c0db218d Remove "EOF-newline" lexer hack
In preparation for an upcoming change
Makes for nicer error messages, complaining about EOF instead of newlines
The hack had to be kept for the lexer raw mode to avoid a bug;
see the relevant code comment for more info.
2021-02-11 12:48:37 +01:00
ISSOtm
76446e6d00 Change behavior of merging FRAGMENTs to constrain each fragment individually
Additionally, remove the deprecated merging of non-fragment SECTIONs
2021-02-10 10:19:16 +01:00
ISSOtm
6623b1dc45 Fix CI on macOS
Apple supplies version 2.3 (from 2006!!), which doesn't support `%empty`.
2021-01-23 13:45:12 +01:00
ISSOtm
70bbb098d3 Remove stale keywords
They were removed from the grammar, but not the lexer
2021-01-23 00:05:56 +01:00
ISSOtm
1926065377 Enable Bison warnings
-Wall should be old enough.
Also use %empty instead of comments
2021-01-23 00:05:56 +01:00
Rangi
e3d355d976 Attempt to recover from syntax errors with bison
Fixes #595
2021-01-22 15:54:24 +01:00
ISSOtm
a679e02246 Get rid of asm.h and localasm.h
No need to make them global
2021-01-22 13:34:16 +01:00
ISSOtm
592e9b3725 Reorganize and comment better main() 2021-01-22 11:09:27 +01:00
ISSOtm
effc58241d Rework defining variables on command-line
Avoids allocating memory
Detects CLI errors even when failing to open file
Reports symbols as defined on command-line instead of input line 0 (!?)
2021-01-22 11:03:43 +01:00
ISSOtm
3697065afc Add asm/opt.c to CMakeLists
Fixes compiling with CMake
2021-01-22 11:01:33 +01:00
ISSOtm
fa0fa4d5ac Significantly overhaul OPT code
Simplify the mess that was option setting (2 redundant variables !?)
Move options to a separate file
Have "modules" own their options, and OPT only access them (less redundancy)
Simplify code, respect naming conventions better
2021-01-22 10:41:09 +01:00
ISSOtm
5acc48fa54 Error out when given several input files
Also rename tzMainFile
2021-01-22 09:44:41 +01:00
ISSOtm
1487eebe79 Remove time counter
Nobody uses that, and it requires running extra code for each line
2021-01-22 08:51:57 +01:00
ISSOtm
993c034039 Avoid using EXPAND_AND_STR with external defines
There is no guarantee that they are purely numeric, use the values instead
2021-01-22 08:51:35 +01:00
ISSOtm
09f16bda4a Use putc instead of fputc
Also rename some functions for consistency
2021-01-22 08:32:28 +01:00
ISSOtm
41d544a4eb Rewrite RGBFIX
- Make it work inside pipelines
- Add RGBFIX tests to the suite
- Be more flexible in accepted MBC names
- Add warnings for dangerous or nonsensical input params
- Improve man page
2021-01-20 21:22:55 +01:00
Rangi
f28b4abafc Fix a potential buffer overflow in strrpl
This caused an error using clang with -O3 -flto
2021-01-20 10:21:53 +01:00
ISSOtm
ca1c934629 Have CMake use specified compiler in CI, not system default
Otherwise it's just GCC on Linux and Clang on macOS...
2021-01-19 16:35:06 +01:00
Rangi
d5a00cf634 Comment cites the string hash algorithm (djb2)
"If you just want to have a good hash function,
and cannot wait, djb2 is one of the best string
hash functions I know."
2021-01-19 15:03:22 +01:00
Rangi
fb39c3a70e Consistently refer to "directives", not "pseudo-ops"
Some docs and warnings already referred to SECTION and
db/dw/dl "directives", but others used "pseudo-ops".
2021-01-19 15:03:22 +01:00
ISSOtm
15ec6efc28 Fix missing newline in charmap override warning 2021-01-17 20:13:51 +01:00
Rangi
93d83e17dc Don't override bison's internal 'yytnamerr' function
This is not used in all bison versions, which causes a
"defined but not used" warning for the 'rgbasm_yytnamerr'
replacement, treated as an error by 'make develop'.
2021-01-15 11:51:31 +01:00
Rangi
df16e64fc6 Handle MACRO and REPT/FOR bodies differently
Fixes #697
2021-01-15 02:16:37 +01:00
Rangi
a4ebb87858 Add Rangi to contributors 2021-01-14 18:36:10 +01:00
Rangi
5ef8e0a1f6 Use an IELR parser if available 2021-01-14 18:36:10 +01:00
Rangi
eb4952c188 Use more verbose syntax error messages
Fixes #385
2021-01-14 18:36:10 +01:00
ISSOtm
57b734a7df Reinstate RL into the _RS family
Removal of colon-less labels lifted the grammar ambiguity that
prevented `RL` from being usable as a variable declarator.
Thus, reinstate its functionality.
2021-01-11 01:38:03 +01:00
ISSOtm
5be1c0da62 Fix intra-section ALIGN not computing offset correctly 2021-01-09 23:29:08 +01:00
ISSOtm
b598911e96 Enable LTO in release builds
Fixes #693
2021-01-09 20:31:15 +01:00
Rangi
cab9cb06a3 Store IF depth relative to each fstack context
This disallows starting/ending an IF inside an
INCLUDEd file or a macro expansion
2021-01-09 20:12:14 +01:00
Rangi
62bea23c49 Implement BREAK to exit REPT and FOR loops
Fixes #684
2021-01-08 21:13:23 +01:00
Rangi
7ce5cf1595 Convert floating to fixed point by rounding, not truncation
Fixes #678
2021-01-04 21:23:43 +01:00
Rangi
7e3fc1db03 Fix Actions CI for MSVC
Fixes #616
2021-01-04 02:01:25 +01:00
Rangi
77279984a5 Implement STRRPL
Fixes #660

STRRPL(str, "", new) does nothing
(warn about it with -Wempty-strrpl)
2021-01-04 00:20:35 +01:00
Rangi
669a392fcd Revise the rgbasm(5) docs 2021-01-02 08:29:57 +01:00
ISSOtm
51ce0b038a Remove removed features from documentation 2021-01-02 02:47:13 +01:00
ISSOtm
bd244e6865 Remove deprecated features
Trimming off the fat!
- GLOBAL and XDEF keywords
- Colon-less global labels
- *-comments
2021-01-02 02:42:44 +01:00
Rangi
a70ecba06f Implement PRINT and PRINTLN (#672)
Fixes #669
Closes #368
Closes #624

Deprecate PRINTT, PRINTV, PRINTI, and PRINTF

Default STRFMT("%f") to 5 fractional digits like "{f:}"
Any use of string formatting will share this default
2021-01-02 02:37:32 +01:00
Rangi
9d2d5cfcfe Implement REDEF to allow redefining EQUS string equates
Fixes #677
2021-01-02 01:49:00 +01:00
ISSOtm
18f3c8ff9a Un-document deprecated _PI 2021-01-02 01:43:49 +01:00
Rangi
895ec5564d Update mathematical functions (#675)
Document the existing `ROUND`, `CEIL`, and `FLOOR` functions
Also update the trig function docs for searchability

Implement `POW` and `LOG`
Addresses part of #675

Implement ** for integer exponents
** has higher precedence than -, like Python, so -3**4 == -(3**4) == 81
2021-01-02 01:39:20 +01:00
Rangi
7bb6f71f0b Change FOREACH to FOR (#680) 2021-01-02 00:46:26 +01:00
Rangi
10e3f1a02b Deprecate built-in _PI
Fixes #670
2021-01-01 19:18:17 +01:00
Rangi
2a9d52871b Make dbgPrint in lexer.c report the correct colNo (#676)
Fixes #656
2021-01-01 18:44:47 +01:00
ISSOtm
c4fb5591ee Fix size of unterminated REPT/FOREACH blocks
Do not "uncapture" ENDR if it was not read in the first place
2020-12-30 15:52:24 +01:00
Rangi
c0ce1da4c3 Implement STRFMT and more printf-like format specifiers for string interpolation (#646)
Fixes #570
Fixes #178

Use errors for inapplicable format spec flags instead of -Wstring-format
2020-12-29 22:53:15 +01:00
ISSOtm
aa27e714d4 Make Bison version detection more portable
- POSIX sh actually does NOT support `+=`,
  but Bash does even in compatibility mode
- `mawk` does not fully support POSIX EREs (regexes),
  so work around its lack of brace support
Fixes building on Debian, apparently.
2020-12-29 22:31:15 +01:00
Rangi
6874f694e5 Implement FOREACH (#658)
This acts like `REPT` with a variable automatically
incremented across a range of values

Fixes #432
2020-12-29 21:30:42 +01:00
Rangi
3690546795 make checkpatch and make checkcodebase check the same files
Only check src and include (not test), and
exclude src/extern and include/extern.
2020-12-29 20:27:00 +01:00
ISSOtm
7bc42d468b Clean up temp test files even if interrupted
Avoids "tmp.*" piling up in /tmp
2020-12-26 14:38:04 +01:00
ISSOtm
097999cad3 Prevent tests from running if RGBDS hasn't been built
Prevents a *lot* of spurious errors due to files not generating
2020-12-26 14:26:50 +01:00
ISSOtm
f82edaa7ec Make gbdiff.bash handle CRLF sym files gracefully
Additionally, reduce the amount of lines piped through `cut`,
improving performance
2020-12-26 02:47:04 +01:00
ISSOtm
d8e8b796e7 Update tested projects to latest commits
Compatibility fixes, etc.
2020-12-26 02:02:38 +01:00
ISSOtm
900fd8cabc Improve gbdiff script
Use better constructs where possible
- <<< instead of echo|
- Parameter expansions instead of `cut`
- Etc.
Improves performance and reliability somewhat

Also, accept "d" between diff line info parts
2020-12-26 01:44:45 +01:00
ISSOtm
c20ac35074 Refactor readString
Much more readable, and avoids `peek(nz)`
2020-12-19 13:02:05 +01:00
Rangi
255b8bf9ba Implement """triple-quoted""" multi-line strings
Fixes #589
2020-12-19 12:34:32 +01:00
Rangi
ad6f17cd93 Support SOURCE_DATE_EPOCH for reproducible builds
See https://reproducible-builds.org/docs/source-date-epoch/

Fixes #286
2020-12-19 01:09:45 +01:00
Rangi
063a22ddf9 LOAD blocks cannot create a ROM section
Fixes #576
2020-12-19 00:58:54 +01:00
Rangi
1d9cc01ae1 Macro arguments within a string literal are read into the string, not expanded
Fixes #643
2020-12-15 21:28:15 +01:00
Rangi
f31deb5010 Fix STRUPR and STRLWR after 5aabb915ec
Fixes #647
2020-12-15 20:18:45 +01:00
Rangi
0956d300c4 Document \# 2020-12-14 10:48:03 +01:00
ISSOtm
4ef490f3cb Avoid interpreting Liquid in doc pages
They are auto-generated, so they wouldn't normally contain Liquid
(The post-processor may insert some, but it has control over the `raw` tags)
Some code introduced as examples contains `{{` due to nested brace expansions,
which was interpreted as (invalid) Liquid. This fixes such problems.
Additionally, Jekyll generates warnings about excerpts being part of a Liquid
block (the `{% raw %}`). This is fine, since doc pages don't use excerpts.
2020-12-14 10:35:30 +01:00
ISSOtm
8f2a894b88 Add anonymous labels
Fix #497
2020-12-14 10:14:40 +01:00
Rangi
0e40543757 Implement \# to expand to all unshifted macro arguments
Fixes #596
2020-12-14 00:12:36 +01:00
Rangi
ce58f6d6be Allow {symbol} interpolation outside of strings
Fixes #629

Closes #631
2020-12-13 23:53:16 +01:00
ISSOtm
5aabb915ec Allow STRCAT to take any number of args
Fixes bullet point 1 of #625
2020-12-12 23:46:32 +01:00
ISSOtm
0d9de01f9d Make charmap-converting a non-UTF8 string non-fatal 2020-12-12 14:16:50 +01:00
ISSOtm
f5b0eae9cd Remove custom action code when equivalent to default
Enables Bison to better reason about it, and should improve performance
2020-12-12 12:22:36 +01:00
Rangi
e6552064bf Specify rgbfix --mbc-type by name
This supports the names listed in Pan Docs:
https://gbdev.io/pandocs/#_0147-cartridge-type
Spaces may be replaced with underscores.

It also supports "ROM" for "ROM ONLY".
2020-12-12 01:53:42 +01:00
Rangi
861cb552c4 discardBlockComment sets lexerState->disableMacroArgs = true, like discardComment 2020-12-12 01:34:01 +01:00
ISSOtm
417cceb0de Document dw and dl with strings 2020-12-11 11:02:20 +01:00
Rangi
165bd8cb71 Allow 'dw' and 'dl' to apply to characters of strings
Fixes #568

The old behavior of `dw "string"` can be replicated with `dw ("string")`; likewise for dl
2020-12-11 11:02:20 +01:00
ISSOtm
e54e02dc77 Correct underscore-in-number documentation
No radix actually allows underscores at the beginning of a literal
2020-12-10 16:01:18 +01:00
ISSOtm
2bf3b08d76 Document underscores in numeric literals 2020-12-10 15:40:59 +01:00
ISSOtm
21b58b08b8 Fix not shifting CRLF at end of raw lines
Fixes line reporting errors on Windows
2020-12-10 15:37:50 +01:00
Rangi
af530859f0 Allow underscores in numeric literals
Fixes #539

Changes \@'s output to start with "_u", not "_", so it will be valid within labels but not numerics
2020-12-10 15:34:21 +01:00
Rangi
58739b0bf2 Implement STRRIN, like STRIN but searching from the right 2020-12-10 15:32:17 +01:00
ISSOtm
3e4c2fe712 Avoid error with old Bison versions
`api.token.raw` is only defined starting with Bison 3.5
Since it's not essential, define it on the command-line iff the Bison
version is sufficient.
2020-12-10 15:13:45 +01:00
ISSOtm
bdfce25db0 Avoid running version test when git describe fails
Can happen when not enough history has been fetched, notably in our CI
2020-12-10 13:43:22 +01:00
ISSOtm
2b6d9cd1e0 Avoid using yytoken_kind_t
Apparently it was added in a fairly recent Bison version...
2020-12-10 13:32:18 +01:00
ISSOtm
bf789dd7b3 Add automated test for version consistency
Automatically check that the version number constants
(__RGBDS_MAJOR__ etc.) match `rgbasm -V`
Should avoid the problem with 0.4.2's release...
2020-12-10 12:22:29 +01:00
ISSOtm
9b6f01047c Enable raw token types
Removes one layer of indirection for the parser, and helps remove all literals from the grammar

The latter preparing the next change
2020-12-09 21:22:05 +01:00
ISSOtm
3fe2fa43bb Switch to GNU Bison as a dependency
First step for #595
2020-12-09 20:30:31 +01:00
608 changed files with 8080 additions and 3171 deletions

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
github: avivace
patreon: gbdev01
open_collective: gbdev

View File

@@ -7,6 +7,14 @@
sub(/b><\/td/, "th"); sub(/b><\/td/, "th");
} }
# The whole page is being generated, so it's not meant to contain any Liquid
BEGIN {
print "{% raw %}"
}
END {
print "{% endraw %}"
}
BEGIN { BEGIN {
in_synopsis = 0 in_synopsis = 0
} }

View File

@@ -5,20 +5,21 @@ usage() {
Usage: $0 [-h] [-r] <rgbds-www> <version> Usage: $0 [-h] [-r] <rgbds-www> <version>
Copy renders from RGBDS repository to rgbds-www documentation Copy renders from RGBDS repository to rgbds-www documentation
Execute from the root folder of the RGBDS repo, checked out at the desired tag Execute from the root folder of the RGBDS repo, checked out at the desired tag
<rgbds-www> : Path to the '_documentation' folder in the rgbds-www repository <rgbds-www> : Path to the rgbds-www repository
<version> : Version to be copied, such as 'v0.4.1' or 'master' <version> : Version to be copied, such as 'v0.4.1' or 'master'
-h Display this help message -h Display this help message
-r Update "latest stable" redirection pages (use for releases, not master) -r Update "latest stable" redirection pages and add a new entry to the index
(use for releases, not master)
EOF EOF
} }
update_redirects=0 is_release=0
bad_usage=0 bad_usage=0
while getopts ":hr" opt; do while getopts ":hr" opt; do
case $opt in case $opt in
r) r)
update_redirects=1 is_release=1
;; ;;
h) h)
usage usage
@@ -50,7 +51,7 @@ PAGES=(
[gbz80.7.html]=src/gbz80.7 [gbz80.7.html]=src/gbz80.7
) )
WWWPATH="/docs" WWWPATH="/docs"
mkdir -p "$1/$2" mkdir -p "$1/_documentation/$2"
# `mandoc` uses a different format for referring to man pages present in the **current** directory # `mandoc` uses a different format for referring to man pages present in the **current** directory
# We want that format for RGBDS man pages, and the other one for the t=rest; # We want that format for RGBDS man pages, and the other one for the t=rest;
@@ -64,7 +65,7 @@ stem="${page%.html}"
manpage="${stem%.?}(${stem#*.})" manpage="${stem%.?}(${stem#*.})"
descr="$(awk -v 'FS=.Nd ' '/.Nd/ { print $2; }' "${PAGES[$page]}")" descr="$(awk -v 'FS=.Nd ' '/.Nd/ { print $2; }' "${PAGES[$page]}")"
cat - >"$1/$2/$page" <<EOF cat >"$1/_documentation/$2/$page" <<EOF
--- ---
layout: doc layout: doc
title: $manpage [$2] title: $manpage [$2]
@@ -75,9 +76,9 @@ EOF
if [ $stem = rgbasm.5 ]; then if [ $stem = rgbasm.5 ]; then
options+=,toc options+=,toc
fi fi
mandoc -Thtml -I os=Linux -O$options "${PAGES[$page]##*/}" | .github/actions/doc_postproc.awk >> "$1/$2/$page" mandoc -Thtml -I os=Linux -O$options "${PAGES[$page]##*/}" | .github/actions/doc_postproc.awk >> "$1/_documentation/$2/$page"
if [ $update_redirects -ne 0 ]; then if [ $is_release -ne 0 ]; then
cat - >"$1/$page" <<EOF cat - >"$1/_documentation/$page" <<EOF
--- ---
redirect_to: $WWWPATH/$2/${page%.html} redirect_to: $WWWPATH/$2/${page%.html}
permalink: $WWWPATH/${page%.html}/ permalink: $WWWPATH/${page%.html}/
@@ -87,7 +88,7 @@ description: RGBDS latest stable — $descr
EOF EOF
fi fi
done done
cat - >"$1/$2/index.html" <<EOF cat - >"$1/_documentation/$2/index.html" <<EOF
--- ---
layout: doc_index layout: doc_index
permalink: /docs/$2/ permalink: /docs/$2/
@@ -96,5 +97,13 @@ description: RGBDS $2 - Online manual
--- ---
EOF EOF
# If making a release, add a new entry right after `master`
if [ $is_release -ne 0 ]; then
awk '{ print }
/"name": "master"/ { print "\t\t{\"name\": \"'$2'\", \"text\": \"'$2'\" }," }
' "$1/_data/doc.json" >"$1/_data/doc.json.tmp"
mv "$1/_data/doc.json"{.tmp,}
fi
# Clean up # Clean up
rm "${PAGES[@]##*/}" rm "${PAGES[@]##*/}"

View File

@@ -4,11 +4,15 @@ case `echo $1 | cut -d '-' -f 1` in
sudo apt-get install -yq bison libpng-dev pkg-config sudo apt-get install -yq bison libpng-dev pkg-config
;; ;;
macos) macos)
brew install libpng pkg-config md5sha1sum brew install bison libpng pkg-config md5sha1sum
# For the version check below exclusively, re-do this before building
export PATH="/usr/local/opt/bison/bin:$PATH"
;; ;;
*) *)
echo "WARNING: Cannot install deps for OS '$1'" echo "WARNING: Cannot install deps for OS '$1'"
;; ;;
esac esac
yacc --version bison --version
make --version
cmake --version

View File

@@ -0,0 +1,96 @@
name: "Create release artifacts"
on:
push:
tags:
- v[0-9]*
jobs:
windows:
runs-on: windows-2019
steps:
- uses: actions/checkout@v2
- name: Get version from tag
shell: bash
run: | # Turn "refs/tags/vX.Y.Z" into "X.Y.Z"
VERSION="${{ github.ref }}"
echo "version=${VERSION##*/v}" >> $GITHUB_ENV
- name: Get zlib, libpng and bison
run: | # TODO: use an array
$wc = New-Object System.Net.WebClient
$wc.DownloadFile('https://www.zlib.net/zlib1211.zip', 'zlib.zip')
$hash = (Get-FileHash "zlib.zip" -Algorithm SHA256).Hash
if ($hash -ne 'd7510a8ee1918b7d0cad197a089c0a2cd4d6df05fee22389f67f115e738b178d') {
Write-Host "zlib SHA256 mismatch! ($hash)"
exit 1
}
$wc.DownloadFile('https://download.sourceforge.net/libpng/lpng1637.zip', 'libpng.zip')
$hash = (Get-FileHash "libpng.zip" -Algorithm SHA256).Hash
if ($hash -ne '3b4b1cbd0bae6822f749d39b1ccadd6297f05e2b85a83dd2ce6ecd7d09eabdf2') {
Write-Host "libpng SHA256 mismatch! ($hash)"
exit 1
}
$wc.DownloadFile('https://github.com/lexxmark/winflexbison/releases/download/v2.5.23/win_flex_bison-2.5.23.zip', 'winflexbison.zip')
$hash = (Get-FileHash "winflexbison.zip" -Algorithm SHA256).Hash
if ($hash -ne '6AA5C8EA662DA1550020A5804C28BE63FFAA53486DA9F6842E24C379EC422DFC') {
Write-Host "bison SHA256 mismatch! ($hash)"
}
Expand-Archive -DestinationPath . "zlib.zip"
Expand-Archive -DestinationPath . "libpng.zip"
Expand-Archive -DestinationPath install_dir "winflexbison.zip"
Move-Item zlib-1.2.11 zlib
Move-Item lpng1637 libpng
- name: Build 32-bit zlib
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
cmake -S zlib -B zbuild32 -A Win32 -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
cmake --build zbuild32 --config Release
cmake --install zbuild32
- name: Build 32-bit libpng
run: |
cmake -S libpng -B pngbuild32 -A Win32 -DCMAKE_INSTALL_PREFIX=install_dir -DPNG_SHARED=ON -DPNG_STATIC=ON -DPNG_TESTS=OFF
cmake --build pngbuild32 --config Release
cmake --install pngbuild32
- name: Build 32-bit Windows binaries
run: |
cmake -S . -B build32 -A Win32 -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release
cmake --build build32 --config Release
cmake --install build32
- name: Package 32-bit binaries
run: |
Compress-Archive -LiteralPath @("install_dir/bin/rgbasm.exe", "install_dir/bin/rgblink.exe", "install_dir/bin/rgbfix.exe", "install_dir/bin/rgbgfx.exe", "install_dir/bin/zlib1.dll", "install_dir/bin/libpng16.dll") "rgbds-${{ env.version }}-win32.zip"
- name: Build 64-bit zlib
run: | # BUILD_SHARED_LIBS causes the output DLL to be correctly called `zlib1.dll`
cmake -S zlib -B zbuild64 -A x64 -DCMAKE_INSTALL_PREFIX=install_dir -DBUILD_SHARED_LIBS=ON
cmake --build zbuild64 --config Release
cmake --install zbuild64
- name: Build 64-bit libpng
run: |
cmake -S libpng -B pngbuild64 -A x64 -DCMAKE_INSTALL_PREFIX=install_dir -DPNG_SHARED=ON -DPNG_STATIC=ON -DPNG_TESTS=OFF
cmake --build pngbuild64 --config Release
cmake --install pngbuild64
- name: Build 64-bit Windows binaries
run: |
cmake -S . -B build64 -A x64 -DCMAKE_INSTALL_PREFIX=install_dir -DCMAKE_BUILD_TYPE=Release
cmake --build build64 --config Release
cmake --install build64
- name: Package 64-bit binaries
run: |
Compress-Archive -LiteralPath @("install_dir/bin/rgbasm.exe", "install_dir/bin/rgblink.exe", "install_dir/bin/rgbfix.exe", "install_dir/bin/rgbgfx.exe", "install_dir/bin/zlib1.dll", "install_dir/bin/libpng16.dll") "rgbds-${{ env.version }}-win64.zip"
- name: Package sources
run: |
make dist
- name: Release
uses: softprops/action-gh-release@v1
with:
body: |
Please ensure that the three assets below work properly.
Once that's done, replace this text with the changelog, un-draft the release, and update the `release` branch.
By the way, if you forgot to update `include/version.h`, RGBASM's version test is gonna fail in the tag's regression testing! (Use `git push --delete origin <tag>` to delete it)
draft: true # Don't publish the release quite yet...
prerelease: ${{ contains(github.ref, '-rc') }}
files: |
rgbds-${{ env.version }}-win32.zip
rgbds-${{ env.version }}-win64.zip
rgbds-${{ env.version }}.tar.gz
fail_on_unmatched_files: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -2,24 +2,22 @@ name: "Create release docs"
on: on:
release: release:
types: types:
- created - released # This avoids triggering on pre-releases
jobs: jobs:
build: build:
runs-on: ubuntu-18.04 runs-on: ubuntu-18.04
steps: steps:
- name: Checkout rgbds@master - name: Checkout rgbds@release
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
repository: gbdev/rgbds
ref: master
path: rgbds path: rgbds
- name: Checkout rgbds-www@master - name: Checkout rgbds-www@master
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
repository: gbdev/rgbds-www repository: ${{ github.repository_owner }}/rgbds-www
ref: master
path: rgbds-www path: rgbds-www
# `-O toc` was added in 1.14.5, but the repos only have 1.14.4
- name: Build and install mandoc - name: Build and install mandoc
run: | run: |
sudo apt-get -qq update sudo apt-get -qq update
@@ -32,23 +30,23 @@ jobs:
sudo make install sudo make install
- name: Update pages - name: Update pages
working-directory: rgbds working-directory: rgbds
run: | run: | # The ref appears to be in the format "refs/tags/<version>", so strip that
./.github/actions/get-pages.sh ../rgbds-www/_documentation ${GITHUB_REF} ./.github/actions/get-pages.sh -r ../rgbds-www ${GITHUB_REF##*/}
- name: Push new pages - name: Push new pages
working-directory: rgbds-www working-directory: rgbds-www
run: | run: |
mkdir -p -m 700 ~/.ssh mkdir -p -m 700 ~/.ssh
echo "${{ secrets.SSH_KEY_SECRET }}" > ~/.ssh/id_ed25519 cat > ~/.ssh/id_ed25519 <<<"${{ secrets.SSH_KEY_SECRET }}"
chmod 0600 ~/.ssh/id_ed25519 chmod 0600 ~/.ssh/id_ed25519
eval $(ssh-agent -s) eval $(ssh-agent -s)
ssh-add ~/.ssh/id_ed25519 ssh-add ~/.ssh/id_ed25519
git config --global user.name "GitHub Action" git config --global user.name "GitHub Action"
git config --global user.email "community@gbdev.io" git config --global user.email "community@gbdev.io"
git add . git add -A
git commit -m "Create RGBDS ${GITHUB_REF} documentation" git commit -m "Create RGBDS ${GITHUB_REF##*/} documentation"
if git remote | grep -q origin; then if git remote | grep -q origin; then
git remote set-url origin git@github.com:gbdev/rgbds-www.git git remote set-url origin git@github.com:${{ github.repository_owner }}/rgbds-www.git
else else
git remote add origin git@github.com:gbdev/rgbds-www.git git remote add origin git@github.com:${{ github.repository_owner }}/rgbds-www.git
fi fi
git push origin master git push origin master

View File

@@ -27,14 +27,19 @@ jobs:
shell: bash shell: bash
run: | run: |
./.github/actions/install_deps.sh ${{ matrix.os }} ./.github/actions/install_deps.sh ${{ matrix.os }}
# The `export` lines are to allow working on macOS...
# Apple's base version is severely outdated, not even supporting -Wall,
# but it overrides Homebrew's version nonetheless...
- name: Build & install using Make - name: Build & install using Make
run: | run: |
export PATH="/usr/local/opt/bison/bin:$PATH"
make ${{ matrix.target }} -j Q= CC=${{ matrix.cc }} make ${{ matrix.target }} -j Q= CC=${{ matrix.cc }}
sudo make install -j Q= sudo make install -j Q=
if: matrix.buildsys == 'make' if: matrix.buildsys == 'make'
- name: Build & install using CMake - name: Build & install using CMake
run: | run: |
cmake -S . -B build -DCMAKE_VERBOSE_MAKEFILE=ON ${{ matrix.cmakevars }} export PATH="/usr/local/opt/bison/bin:$PATH"
cmake -S . -B build -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=${{ matrix.cc }} ${{ matrix.cmakevars }}
cmake --build build cmake --build build
cp build/src/rgb{asm,link,fix,gfx} . cp build/src/rgb{asm,link,fix,gfx} .
sudo cmake --install build sudo cmake --install build
@@ -128,7 +133,7 @@ jobs:
strategy: strategy:
matrix: matrix:
bits: [32, 64] bits: [32, 64]
os: [ubuntu-latest] os: [ubuntu-18.04]
include: include:
- bits: 32 - bits: 32
arch: i686 arch: i686

1
.gitignore vendored
View File

@@ -5,6 +5,7 @@ rgbgfx
rgbshim.sh rgbshim.sh
*.o *.o
*.exe *.exe
*.dll
.checkpatch-camelcase.* .checkpatch-camelcase.*
CMakeCache.txt CMakeCache.txt
CMakeFiles CMakeFiles

View File

@@ -6,8 +6,8 @@
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
# #
cmake_minimum_required(VERSION 3.0) # 3.9 required for LTO checks
cmake_policy(VERSION 3.0) cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
project(rgbds project(rgbds
LANGUAGES C) LANGUAGES C)
@@ -23,15 +23,15 @@ if(srcdir STREQUAL bindir)
message(FATAL_ERROR "Terminating configuration") message(FATAL_ERROR "Terminating configuration")
endif() endif()
include_directories("${PROJECT_SOURCE_DIR}/include") option(SANITIZERS "Build with sanitizers enabled" OFF) # Ignored on MSVC
option(MORE_WARNINGS "Turn on more warnings" OFF) # Ignored on MSVC
option(SANITIZERS "Build with sanitizers enabled" OFF)
option(MORE_WARNINGS "Turn on more warnings" OFF)
option(TRACE_PARSER "Trace parser execution" OFF) option(TRACE_PARSER "Trace parser execution" OFF)
option(TRACE_LEXER "Trace lexer execution" OFF) option(TRACE_LEXER "Trace lexer execution" OFF)
if(MSVC) if(MSVC)
add_compile_options(/W1 /MP) # MSVC's standard library triggers warning C5105,
# "macro expansion producing 'defined' has undefined behavior"
add_compile_options(/std:c11 /W1 /MP /wd5105)
add_definitions(/D_CRT_SECURE_NO_WARNINGS) add_definitions(/D_CRT_SECURE_NO_WARNINGS)
else() else()
add_compile_options(-Wall -pedantic) add_compile_options(-Wall -pedantic)
@@ -65,6 +65,8 @@ execute_process(COMMAND git describe --tags --dirty --always
ERROR_QUIET) ERROR_QUIET)
string(STRIP "${GIT_REV}" GIT_REV) string(STRIP "${GIT_REV}" GIT_REV)
include_directories("${PROJECT_SOURCE_DIR}/include")
add_definitions(-DBUILD_VERSION_STRING="${GIT_REV}") add_definitions(-DBUILD_VERSION_STRING="${GIT_REV}")
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD 11)
@@ -72,6 +74,20 @@ set(CMAKE_C_STANDARD_REQUIRED True)
add_subdirectory(src) add_subdirectory(src)
# By default, build in Release mode; Debug mode must be explicitly requested
# (You may want to augment it with the options above)
if(CMAKE_BUILD_TYPE STREQUAL "Release")
message(CHECK_START "Checking if LTO is supported")
include(CheckIPOSupported)
check_ipo_supported(RESULT enable_lto)
if(enable_lto)
message(CHECK_PASS "yes")
set_property(TARGET rgbasm rgblink rgbfix rgbgfx PROPERTY INTERPROCEDURAL_OPTIMIZATION ON)
else()
message(CHECK_FAIL "no")
endif()
endif()
if(TRACE_PARSER) if(TRACE_PARSER)
target_compile_definitions(rgbasm PRIVATE -DYYDEBUG) target_compile_definitions(rgbasm PRIVATE -DYYDEBUG)
endif() endif()

View File

@@ -40,6 +40,8 @@ Other contributors
- Quint Guvernator <quint@guvernator.net> - Quint Guvernator <quint@guvernator.net>
- Rangi <http://github.com/Rangi42>
- Sanqui <gsanky@gmail.com> - Sanqui <gsanky@gmail.com>
- YamaArashi <shadow962@live.com> - YamaArashi <shadow962@live.com>

View File

@@ -7,7 +7,7 @@
FROM alpine:latest FROM alpine:latest
RUN apk add --update \ RUN apk add --update \
build-base \ build-base \
byacc \ bison \
libpng-dev libpng-dev
COPY . /rgbds COPY . /rgbds
WORKDIR /rgbds WORKDIR /rgbds

View File

@@ -33,7 +33,7 @@ VERSION_STRING := `git describe --tags --dirty --always 2>/dev/null`
WARNFLAGS := -Wall WARNFLAGS := -Wall
# Overridable CFLAGS # Overridable CFLAGS
CFLAGS ?= -O3 -DNDEBUG CFLAGS ?= -O3 -flto -DNDEBUG
# Non-overridable CFLAGS # Non-overridable CFLAGS
REALCFLAGS := ${CFLAGS} ${WARNFLAGS} -std=gnu11 -D_POSIX_C_SOURCE=200809L \ REALCFLAGS := ${CFLAGS} ${WARNFLAGS} -std=gnu11 -D_POSIX_C_SOURCE=200809L \
-Iinclude -Iinclude
@@ -43,9 +43,9 @@ LDFLAGS ?=
REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} \ REALLDFLAGS := ${LDFLAGS} ${WARNFLAGS} \
-DBUILD_VERSION_STRING=\"${VERSION_STRING}\" -DBUILD_VERSION_STRING=\"${VERSION_STRING}\"
YFLAGS ?= YFLAGS ?= -Wall
YACC := yacc BISON := bison
RM := rm -rf RM := rm -rf
# Rules to build the RGBDS binaries # Rules to build the RGBDS binaries
@@ -54,12 +54,14 @@ all: rgbasm rgblink rgbfix rgbgfx
rgbasm_obj := \ rgbasm_obj := \
src/asm/charmap.o \ src/asm/charmap.o \
src/asm/fixpoint.o \
src/asm/format.o \
src/asm/fstack.o \ src/asm/fstack.o \
src/asm/lexer.o \ src/asm/lexer.o \
src/asm/macro.o \ src/asm/macro.o \
src/asm/main.o \ src/asm/main.o \
src/asm/math.o \
src/asm/parser.o \ src/asm/parser.o \
src/asm/opt.o \
src/asm/output.o \ src/asm/output.o \
src/asm/rpn.o \ src/asm/rpn.o \
src/asm/section.o \ src/asm/section.o \
@@ -70,7 +72,8 @@ rgbasm_obj := \
src/extern/getopt.o \ src/extern/getopt.o \
src/extern/utf8decoder.o \ src/extern/utf8decoder.o \
src/hashmap.o \ src/hashmap.o \
src/linkdefs.o src/linkdefs.o \
src/opmath.o
src/asm/lexer.o src/asm/main.o: src/asm/parser.h src/asm/lexer.o src/asm/main.o: src/asm/parser.h
@@ -86,7 +89,8 @@ rgblink_obj := \
src/extern/err.o \ src/extern/err.o \
src/extern/getopt.o \ src/extern/getopt.o \
src/hashmap.o \ src/hashmap.o \
src/linkdefs.o src/linkdefs.o \
src/opmath.o
rgbfix_obj := \ rgbfix_obj := \
src/fix/main.o \ src/fix/main.o \
@@ -114,15 +118,27 @@ rgbgfx: ${rgbgfx_obj}
# Rules to process files # Rules to process files
# We want the yacc invocations to pass through our rules, not default ones # We want the Bison invocation to pass through our rules, not default ones
.y.o: .y.o:
# yacc-generated C files have an accompanying header # Bison-generated C files have an accompanying header
.c.h: src/asm/parser.h: src/asm/parser.c
$Qtouch $@ $Qtouch $@
.y.c: src/asm/parser.c: src/asm/parser.y
$Q${YACC} -d ${YFLAGS} -o $@ $< $QDEFS=; \
add_flag(){ \
if src/check_bison_ver.sh $$1 $$2; then \
DEFS="-D$$3 $$DEFS"; \
fi \
}; \
add_flag 3 5 api.token.raw=true; \
add_flag 3 6 parse.error=detailed; \
add_flag 3 0 parse.error=verbose; \
add_flag 3 0 parse.lac=full; \
add_flag 3 0 lr.type=ielr; \
echo "DEFS=$$DEFS"; \
${BISON} $$DEFS -d ${YFLAGS} -o $@ $<
.c.o: .c.o:
$Q${CC} ${REALCFLAGS} ${PNGCFLAGS} -c -o $@ $< $Q${CC} ${REALCFLAGS} ${PNGCFLAGS} -c -o $@ $<
@@ -178,6 +194,7 @@ checkpatch:
for commit in `git rev-list $$COMMON_COMMIT..HEAD`; do \ for commit in `git rev-list $$COMMON_COMMIT..HEAD`; do \
echo "[*] Analyzing commit '$$commit'"; \ echo "[*] Analyzing commit '$$commit'"; \
git format-patch --stdout "$$commit~..$$commit" \ git format-patch --stdout "$$commit~..$$commit" \
-- src include '!src/extern' '!include/extern' \
| ${CHECKPATCH} - || true; \ | ${CHECKPATCH} - || true; \
done done
@@ -189,7 +206,7 @@ develop:
$Qenv $(MAKE) -j WARNFLAGS="-Werror -Wall -Wextra -Wpedantic -Wno-type-limits \ $Qenv $(MAKE) -j WARNFLAGS="-Werror -Wall -Wextra -Wpedantic -Wno-type-limits \
-Wno-sign-compare -Wvla -Wformat -Wformat-security -Wformat-overflow=2 \ -Wno-sign-compare -Wvla -Wformat -Wformat-security -Wformat-overflow=2 \
-Wformat-truncation=1 -Wformat-y2k -Wswitch-enum -Wunused \ -Wformat-truncation=1 -Wformat-y2k -Wswitch-enum -Wunused \
-Wuninitialized -Wunknown-pragmas -Wstrict-overflow=5 \ -Wuninitialized -Wunknown-pragmas -Wstrict-overflow=4 \
-Wstringop-overflow=4 -Walloc-zero -Wduplicated-cond \ -Wstringop-overflow=4 -Walloc-zero -Wduplicated-cond \
-Wfloat-equal -Wshadow -Wcast-qual -Wcast-align -Wlogical-op \ -Wfloat-equal -Wshadow -Wcast-qual -Wcast-align -Wlogical-op \
-Wnested-externs -Wno-aggressive-loop-optimizations -Winline \ -Wnested-externs -Wno-aggressive-loop-optimizations -Winline \
@@ -206,11 +223,11 @@ develop:
# install instructions instead. # install instructions instead.
mingw32: mingw32:
$Qmake CC=i686-w64-mingw32-gcc YACC=bison \ $Qmake CC=i686-w64-mingw32-gcc BISON=bison \
PKG_CONFIG=i686-w64-mingw32-pkg-config -j PKG_CONFIG=i686-w64-mingw32-pkg-config -j
mingw64: mingw64:
$Qmake CC=x86_64-w64-mingw32-gcc YACC=bison \ $Qmake CC=x86_64-w64-mingw32-gcc BISON=bison \
PKG_CONFIG=x86_64-w64-mingw32-pkg-config -j PKG_CONFIG=x86_64-w64-mingw32-pkg-config -j
wine-shim: wine-shim:

View File

@@ -31,31 +31,33 @@ diff <(xxd $1) <(xxd $2) | while read -r LINE; do
# Separator between files switches states # Separator between files switches states
echo $LINE echo $LINE
STATE=3 STATE=3
elif echo $LINE | grep -Eq '^[0-9]+(,[0-9]+)?c[0-9]+(,[0-9]+)?'; then elif grep -Eq '^[0-9]+(,[0-9]+)?[cd][0-9]+(,[0-9]+)?' <<< "$LINE"; then
# Line info resets the whole thing # Line info resets the whole thing
STATE=1 STATE=1
elif [ $STATE -eq 1 -o $STATE -eq 3 ]; then elif [ $STATE -eq 1 -o $STATE -eq 3 ]; then
# Compute the GB address from the ROM offset # Compute the GB address from the ROM offset
OFS=$(echo $LINE | cut -d ' ' -f 2 | tr -d ':') OFS=$(cut -d ' ' -f 2 <<< "$LINE" | tr -d ':')
BANK=$((0x$OFS / 0x4000)) BANK=$((0x$OFS / 0x4000))
ADDR=$((0x$OFS % 0x4000 + ($BANK != 0) * 0x4000)) ADDR=$((0x$OFS % 0x4000 + ($BANK != 0) * 0x4000))
# Try finding the preceding symbol closest to the diff # Try finding the preceding symbol closest to the diff
if [ $STATE -eq 1 ]; then if [ $STATE -eq 1 ]; then
STATE=2 STATE=2
SYMFILE=$(echo $1 | cut -d '.' -f 1).sym SYMFILE=${1%.*}.sym
else else
STATE=4 STATE=4
SYMFILE=$(echo $2 | cut -d '.' -f 1).sym SYMFILE=${2%.*}.sym
fi fi
EXTRA=$(if [ -f "$SYMFILE" ]; then EXTRA=$(if [ -f "$SYMFILE" ]; then
# Read the sym file for such a symbol # Read the sym file for such a symbol
# Ignore comment lines, only pick matching bank # Ignore comment lines, only pick matching bank
grep -Fv ';' "$SYMFILE" | # (The bank regex ignores comments already, make `cut` and `tr` process less lines)
grep -Ei $(printf "^%02x:" $BANK) | grep -Ei $(printf "^%02x:" $BANK) "$SYMFILE" |
cut -d ';' -f 1 |
tr -d "\r" |
while read -r SYMADDR SYM; do while read -r SYMADDR SYM; do
SYMADDR=$((0x$(echo $SYMADDR | cut -d ':' -f 2))) SYMADDR=$((0x${SYMADDR#*:}))
if [ $SYMADDR -le $ADDR ]; then if [ $SYMADDR -le $ADDR ]; then
printf " (%s+%#x)\n" $SYM $(($ADDR - $SYMADDR)) printf " (%s+%#x)\n" "$SYM" $(($ADDR - $SYMADDR))
fi fi
# TODO: assumes sorted sym files # TODO: assumes sorted sym files
done | tail -n 1 done | tail -n 1
@@ -63,9 +65,9 @@ diff <(xxd $1) <(xxd $2) | while read -r LINE; do
printf "%02x:%04x %s\n" $BANK $ADDR $EXTRA printf "%02x:%04x %s\n" $BANK $ADDR $EXTRA
fi fi
if [ $STATE -eq 2 -o $STATE -eq 4 ]; then if [ $STATE -eq 2 -o $STATE -eq 4 ]; then
OFS=$(echo $LINE | cut -d ' ' -f 2 | tr -d ':') OFS=$(cut -d ' ' -f 2 <<< "$LINE" | tr -d ':')
BANK=$((0x$OFS / 0x4000)) BANK=$((0x$OFS / 0x4000))
ADDR=$((0x$OFS % 0x4000 + ($BANK != 0) * 0x4000)) ADDR=$((0x$OFS % 0x4000 + ($BANK != 0) * 0x4000))
printf "%s %02x:%04x: %s\n" "$(echo $LINE | cut -d ' ' -f 1)" $BANK $ADDR "$(echo $LINE | cut -d ' ' -f 3-)" printf "%s %02x:%04x: %s\n" "${LINE:0:1}" $BANK $ADDR "${LINE#*: }"
fi fi
done done

View File

@@ -1,31 +0,0 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
/*
* Contains some assembler-wide defines and externs
*/
#ifndef RGBDS_ASM_ASM_H
#define RGBDS_ASM_ASM_H
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "asm/localasm.h"
#include "asm/symbol.h"
#define MAXMACROARGS 99999
#define MAXINCPATHS 128
extern uint32_t nTotalLines;
extern uint32_t nIFDepth;
extern struct Section *pCurrentSection;
#endif /* RGBDS_ASM_ASM_H */

31
include/asm/fixpoint.h Normal file
View File

@@ -0,0 +1,31 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2021, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef RGBDS_ASM_FIXPOINT_H
#define RGBDS_ASM_FIXPOINT_H
#include <stdint.h>
int32_t fix_Callback_PI(void);
void fix_Print(int32_t i);
int32_t fix_Sin(int32_t i);
int32_t fix_Cos(int32_t i);
int32_t fix_Tan(int32_t i);
int32_t fix_ASin(int32_t i);
int32_t fix_ACos(int32_t i);
int32_t fix_ATan(int32_t i);
int32_t fix_ATan2(int32_t i, int32_t j);
int32_t fix_Mul(int32_t i, int32_t j);
int32_t fix_Div(int32_t i, int32_t j);
int32_t fix_Pow(int32_t i, int32_t j);
int32_t fix_Log(int32_t i, int32_t j);
int32_t fix_Round(int32_t i);
int32_t fix_Ceil(int32_t i);
int32_t fix_Floor(int32_t i);
#endif /* RGBDS_ASM_FIXPOINT_H */

63
include/asm/format.h Normal file
View File

@@ -0,0 +1,63 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2020, RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef RGBDS_FORMAT_SPEC_H
#define RGBDS_FORMAT_SPEC_H
#include <stdint.h>
#include <stdbool.h>
enum FormatState {
FORMAT_SIGN, // expects '+' or ' ' (optional)
FORMAT_PREFIX, // expects '#' (optional)
FORMAT_ALIGN, // expects '-' (optional)
FORMAT_WIDTH, // expects '0'-'9', max 255 (optional) (leading '0' indicates pad)
FORMAT_FRAC, // got '.', expects '0'-'9', max 255 (optional)
FORMAT_DONE, // got [duXxbofs] (required)
FORMAT_INVALID, // got unexpected character
};
struct FormatSpec {
enum FormatState state;
int sign;
bool prefix;
bool alignLeft;
bool padZero;
uint8_t width;
bool hasFrac;
uint8_t fracWidth;
int type;
bool valid;
};
struct StrFmtArg {
union {
uint32_t number;
char *string;
};
bool isNumeric;
};
#define INITIAL_STRFMT_ARG_SIZE 4
struct StrFmtArgList {
char *format;
size_t nbArgs;
size_t capacity;
struct StrFmtArg *args;
};
struct FormatSpec fmt_NewSpec(void);
bool fmt_IsEmpty(struct FormatSpec const *fmt);
bool fmt_IsValid(struct FormatSpec const *fmt);
bool fmt_IsFinished(struct FormatSpec const *fmt);
void fmt_UseCharacter(struct FormatSpec *fmt, int c);
void fmt_FinishCharacters(struct FormatSpec *fmt);
void fmt_PrintString(char *buf, size_t bufLen, struct FormatSpec const *fmt, char const *value);
void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uint32_t value);
#endif /* RGBDS_FORMAT_SPEC_H */

View File

@@ -13,10 +13,10 @@
#ifndef RGBDS_ASM_FSTACK_H #ifndef RGBDS_ASM_FSTACK_H
#define RGBDS_ASM_FSTACK_H #define RGBDS_ASM_FSTACK_H
#include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include "asm/asm.h"
#include "asm/lexer.h" #include "asm/lexer.h"
#include "types.h" #include "types.h"
@@ -73,6 +73,10 @@ bool yywrap(void);
void fstk_RunInclude(char const *path); void fstk_RunInclude(char const *path);
void fstk_RunMacro(char const *macroName, struct MacroArgs *args); void fstk_RunMacro(char const *macroName, struct MacroArgs *args);
void fstk_RunRept(uint32_t count, int32_t nReptLineNo, char *body, size_t size); void fstk_RunRept(uint32_t count, int32_t nReptLineNo, char *body, size_t size);
void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step,
int32_t reptLineNo, char *body, size_t size);
void fstk_StopRept(void);
bool fstk_Break(void);
void fstk_Init(char const *mainPath, size_t maxRecursionDepth); void fstk_Init(char const *mainPath, size_t maxRecursionDepth);

View File

@@ -9,6 +9,8 @@
#ifndef RGBDS_ASM_LEXER_H #ifndef RGBDS_ASM_LEXER_H
#define RGBDS_ASM_LEXER_H #define RGBDS_ASM_LEXER_H
#include <stdbool.h>
#define MAXSTRLEN 255 #define MAXSTRLEN 255
struct LexerState; struct LexerState;
@@ -30,17 +32,21 @@ static inline void lexer_SetStateAtEOL(struct LexerState *state)
lexerStateEOL = state; lexerStateEOL = state;
} }
extern char const *binDigits; extern char binDigits[2];
extern char const *gfxDigits; extern char gfxDigits[4];
static inline void lexer_SetBinDigits(char const *digits) static inline void lexer_SetBinDigits(char const digits[2])
{ {
binDigits = digits; binDigits[0] = digits[0];
binDigits[1] = digits[1];
} }
static inline void lexer_SetGfxDigits(char const *digits) static inline void lexer_SetGfxDigits(char const digits[4])
{ {
gfxDigits = digits; gfxDigits[0] = digits[0];
gfxDigits[1] = digits[1];
gfxDigits[2] = digits[2];
gfxDigits[3] = digits[3];
} }
/* /*
@@ -56,18 +62,40 @@ enum LexerMode {
LEXER_NORMAL, LEXER_NORMAL,
LEXER_RAW, LEXER_RAW,
LEXER_SKIP_TO_ELIF, LEXER_SKIP_TO_ELIF,
LEXER_SKIP_TO_ENDC LEXER_SKIP_TO_ENDC,
LEXER_SKIP_TO_ENDR
}; };
void lexer_SetMode(enum LexerMode mode); void lexer_SetMode(enum LexerMode mode);
void lexer_ToggleStringExpansion(bool enable); void lexer_ToggleStringExpansion(bool enable);
uint32_t lexer_GetIFDepth(void);
void lexer_IncIFDepth(void);
void lexer_DecIFDepth(void);
bool lexer_RanIFBlock(void);
bool lexer_ReachedELSEBlock(void);
void lexer_RunIFBlock(void);
void lexer_ReachELSEBlock(void);
struct CaptureBody {
uint32_t lineNo;
char *body;
size_t size;
};
char const *lexer_GetFileName(void); char const *lexer_GetFileName(void);
uint32_t lexer_GetLineNo(void); uint32_t lexer_GetLineNo(void);
uint32_t lexer_GetColNo(void); uint32_t lexer_GetColNo(void);
void lexer_DumpStringExpansions(void); void lexer_DumpStringExpansions(void);
int yylex(void); int yylex(void);
void lexer_CaptureRept(char **capture, size_t *size); void lexer_CaptureRept(struct CaptureBody *capture);
void lexer_CaptureMacroBody(char **capture, size_t *size); void lexer_CaptureMacroBody(struct CaptureBody *capture);
#define INITIAL_DS_ARG_SIZE 2
struct DsArgList {
size_t nbArgs;
size_t capacity;
struct Expression *args;
};
#endif /* RGBDS_ASM_LEXER_H */ #endif /* RGBDS_ASM_LEXER_H */

View File

@@ -1,131 +0,0 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef RGBDS_ASM_LOCALASM_H
#define RGBDS_ASM_LOCALASM_H
/*
* GB Z80 instruction groups
*
* n3 = 3-bit
* n = 8-bit
* nn = 16-bit
*
* ADC A,n : 0xCE
* ADC A,r : 0x88|r
* ADD A,n : 0xC6
* ADD A,r : 0x80|r
* ADD HL,ss : 0x09|(ss<<4)
* ADD SP,n : 0xE8
* AND A,n : 0xE6
* AND A,r : 0xA0|r
* BIT n3,r : 0xCB 0x40|(n3<<3)|r
* CALL cc,nn : 0xC4|(cc<<3)
* CALL nn : 0xCD
* CCF : 0x3F
* CP A,n : 0xFE
* CP A,r : 0xB8|r
* CPL : 0x2F
* DAA : 0x27
* DEC r : 0x05|(r<<3)
* DEC ss : 0x0B|(ss<<4)
* DI : 0xF3
* EI : 0xFB
* HALT : 0x76
* INC r : 0x04|(r<<3)
* INC ss : 0x03|(ss<<4)
* JP HL : 0xE9
* JP cc,nn : 0xC2|(cc<<3)
* JP nn : 0xC3|(cc<<3)
* JR n : 0x18
* JR cc,n : 0x20|(cc<<3)
* LD (nn),SP : 0x08
* LD ($FF00+C),A : 0xE2
* LD ($FF00+n),A : 0xE0
* LD (nn),A : 0xEA
* LD (rr),A : 0x02|(rr<<4) // HL+ and HL- included
* LD A,($FF00+C) : 0xF2
* LD A,($FF00+n) : 0xF0
* LD A,(nn) : 0xFA
* LD A,(rr) : 0x0A|(rr<<4) // HL+ and HL- included
* LD HL,SP+n : 0xF8
* LD SP,HL : 0xF9
* LD r,n : 0x06|(r<<3)
* LD r,r' : 0x40|(r<<3)|r' // NOTE: LD (HL),(HL) not allowed
* LD ss,nn : 0x01|(ss<<4)
* NOP : 0x00
* OR A,n : 0xF6
* OR A,r : 0xB0|r
* POP tt : 0xC1|(tt<<4)
* PUSH tt : 0xC5|(tt<<4)
* RES n3,r : 0xCB 0x80|(n3<<3)|r
* RET : 0xC9
* RET cc : 0xC0|(cc<<3)
* RETI : 0xD9
* RL r : 0xCB 0x10|r
* RLA : 0x17
* RLC r : 0xCB 0x00|r
* RLCA : 0x07
* RR r : 0xCB 0x18|r
* RRA : 0x1F
* RRC r : 0xCB 0x08|r
* RRCA : 0x0F
* RST n : 0xC7|n
* SBC A,n : 0xDE
* SBC A,r : 0x98|r
* SCF : 0x37
* SET n3,r : 0xCB 0xC0|(n8<<3)|r
* SLA r : 0xCB 0x20|r
* SRA r : 0xCB 0x28|r
* SRL r : 0xCB 0x38|r
* STOP : 0x10 0x00
* SUB A,n : 0xD6
* SUB A,r : 0x90|r
* SWAP r : 0xCB 0x30|r
* XOR A,n : 0xEE
* XOR A,r : 0xA8|r
*/
/* "r" defs */
enum {
REG_B = 0,
REG_C,
REG_D,
REG_E,
REG_H,
REG_L,
REG_HL_IND,
REG_A
};
/* "rr" defs */
enum {
REG_BC_IND = 0,
REG_DE_IND,
REG_HL_INDINC,
REG_HL_INDDEC,
};
/* "ss" defs (SP) and "tt" defs (AF) */
enum {
REG_BC = 0,
REG_DE = 1,
REG_HL = 2,
REG_SP = 3,
REG_AF = 3
};
/* "cc" defs */
enum {
CC_NZ = 0,
CC_Z,
CC_NC,
CC_C
};
#endif /* RGBDS_ASM_LOCALASM_H */

View File

@@ -10,6 +10,7 @@
#define RGBDS_MACRO_H #define RGBDS_MACRO_H
#include <stdint.h> #include <stdint.h>
#include <stdbool.h>
#include <stdlib.h> #include <stdlib.h>
#include "asm/warning.h" #include "asm/warning.h"
@@ -24,6 +25,7 @@ void macro_AppendArg(struct MacroArgs **args, char *s);
void macro_UseNewArgs(struct MacroArgs *args); void macro_UseNewArgs(struct MacroArgs *args);
void macro_FreeArgs(struct MacroArgs *args); void macro_FreeArgs(struct MacroArgs *args);
char const *macro_GetArg(uint32_t i); char const *macro_GetArg(uint32_t i);
char *macro_GetAllArgs(void);
uint32_t macro_GetUniqueID(void); uint32_t macro_GetUniqueID(void);
char const *macro_GetUniqueIDStr(void); char const *macro_GetUniqueIDStr(void);

View File

@@ -1,7 +1,7 @@
/* /*
* This file is part of RGBDS. * This file is part of RGBDS.
* *
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors. * Copyright (c) 1997-2021, Carsten Sorensen and RGBDS contributors.
* *
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
@@ -15,19 +15,6 @@
#include "helpers.h" #include "helpers.h"
struct sOptions {
char binary[2];
char gbgfx[4];
int32_t fillchar;
};
extern char *tzNewMacro;
extern uint32_t ulNewMacroSize;
extern int32_t nGBGfxID;
extern int32_t nBinaryID;
extern struct sOptions DefaultOptions;
extern struct sOptions CurrentOptions;
extern bool haltnop; extern bool haltnop;
extern bool optimizeloads; extern bool optimizeloads;
extern bool verbose; extern bool verbose;
@@ -39,13 +26,6 @@ extern bool oGeneratedMissingIncludes;
extern bool oFailedOnMissingInclude; extern bool oFailedOnMissingInclude;
extern bool oGeneratePhonyDeps; extern bool oGeneratePhonyDeps;
void opt_Push(void);
void opt_Pop(void);
void opt_Parse(char *s);
void upperstring(char *s);
void lowerstring(char *s);
/* TODO: are these really needed? */ /* TODO: are these really needed? */
#define YY_FATAL_ERROR fatalerror #define YY_FATAL_ERROR fatalerror

View File

@@ -1,29 +0,0 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef RGBDS_ASM_MATH_H
#define RGBDS_ASM_MATH_H
#include <stdint.h>
void math_DefinePI(void);
void math_Print(int32_t i);
int32_t math_Sin(int32_t i);
int32_t math_Cos(int32_t i);
int32_t math_Tan(int32_t i);
int32_t math_ASin(int32_t i);
int32_t math_ACos(int32_t i);
int32_t math_ATan(int32_t i);
int32_t math_ATan2(int32_t i, int32_t j);
int32_t math_Mul(int32_t i, int32_t j);
int32_t math_Div(int32_t i, int32_t j);
int32_t math_Round(int32_t i);
int32_t math_Ceil(int32_t i);
int32_t math_Floor(int32_t i);
#endif /* RGBDS_ASM_MATH_H */

22
include/asm/opt.h Normal file
View File

@@ -0,0 +1,22 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2021, Eldred Habert and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef RGBDS_OPT_H
#define RGBDS_OPT_H
void opt_B(char chars[2]);
void opt_G(char chars[4]);
void opt_P(uint8_t fill);
void opt_Parse(char const *option);
void opt_Push(void);
void opt_Pop(void);
#endif

View File

@@ -14,6 +14,7 @@
#include "linkdefs.h" #include "linkdefs.h"
struct Expression; struct Expression;
struct FileStackNode;
extern char *tzObjectname; extern char *tzObjectname;
extern struct Section *pSectionList, *pCurrentSection; extern struct Section *pSectionList, *pCurrentSection;
@@ -21,8 +22,7 @@ extern struct Section *pSectionList, *pCurrentSection;
void out_RegisterNode(struct FileStackNode *node); void out_RegisterNode(struct FileStackNode *node);
void out_ReplaceNode(struct FileStackNode *node); void out_ReplaceNode(struct FileStackNode *node);
void out_SetFileName(char *s); void out_SetFileName(char *s);
void out_CreatePatch(uint32_t type, struct Expression const *expr, void out_CreatePatch(uint32_t type, struct Expression const *expr, uint32_t ofs, uint32_t pcShift);
uint32_t ofs);
bool out_CreateAssert(enum AssertionType type, struct Expression const *expr, bool out_CreateAssert(enum AssertionType type, struct Expression const *expr,
char const *message, uint32_t ofs); char const *message, uint32_t ofs);
void out_WriteObject(void); void out_WriteObject(void);

View File

@@ -27,9 +27,6 @@ struct Expression {
uint32_t nRPNPatchSize; // Size the expression will take in the obj file uint32_t nRPNPatchSize; // Size the expression will take in the obj file
}; };
/* FIXME: Should be defined in `parser.h`, but impossible with POSIX Yacc */
extern int32_t nPCOffset;
/* /*
* Determines if an expression is known at assembly time * Determines if an expression is known at assembly time
*/ */

View File

@@ -14,12 +14,16 @@
#include "linkdefs.h" #include "linkdefs.h"
extern uint8_t fillByte;
struct Expression; struct Expression;
struct Section { struct Section {
char *name; char *name;
enum SectionType type; enum SectionType type;
enum SectionModifier modifier; enum SectionModifier modifier;
struct FileStackNode *src; /* Where the section was defined */
uint32_t fileLine; /* Line where the section was defined */
uint32_t size; uint32_t size;
uint32_t org; uint32_t org;
uint32_t bank; uint32_t bank;
@@ -41,7 +45,8 @@ void out_NewSection(char const *name, uint32_t secttype, uint32_t org,
struct SectionSpec const *attributes, struct SectionSpec const *attributes,
enum SectionModifier mod); enum SectionModifier mod);
void out_SetLoadSection(char const *name, uint32_t secttype, uint32_t org, void out_SetLoadSection(char const *name, uint32_t secttype, uint32_t org,
struct SectionSpec const *attributes); struct SectionSpec const *attributes,
enum SectionModifier mod);
void out_EndLoadSection(void); void out_EndLoadSection(void);
struct Section *sect_GetSymbolSection(void); struct Section *sect_GetSymbolSection(void);
@@ -56,13 +61,15 @@ void sect_CheckUnionClosed(void);
void out_AbsByte(uint8_t b); void out_AbsByte(uint8_t b);
void out_AbsByteGroup(uint8_t const *s, int32_t length); void out_AbsByteGroup(uint8_t const *s, int32_t length);
void out_AbsWordGroup(uint8_t const *s, int32_t length);
void out_AbsLongGroup(uint8_t const *s, int32_t length);
void out_Skip(int32_t skip, bool ds); void out_Skip(int32_t skip, bool ds);
void out_String(char const *s); void out_String(char const *s);
void out_RelByte(struct Expression *expr); void out_RelByte(struct Expression *expr, uint32_t pcShift);
void out_RelBytes(struct Expression *expr, uint32_t n); void out_RelBytes(uint32_t n, struct Expression *exprs, size_t size);
void out_RelWord(struct Expression *expr); void out_RelWord(struct Expression *expr, uint32_t pcShift);
void out_RelLong(struct Expression *expr); void out_RelLong(struct Expression *expr, uint32_t pcShift);
void out_PCRelByte(struct Expression *expr); void out_PCRelByte(struct Expression *expr, uint32_t pcShift);
void out_BinaryFile(char const *s, int32_t startPos); void out_BinaryFile(char const *s, int32_t startPos);
void out_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length); void out_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length);

View File

@@ -12,9 +12,11 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <string.h> #include <string.h>
#include <time.h>
#include "asm/section.h" #include "asm/section.h"
#include "platform.h" // MIN_NB_ELMS
#include "types.h" #include "types.h"
#define HASHSIZE (1 << 16) #define HASHSIZE (1 << 16)
@@ -115,6 +117,8 @@ int32_t sym_GetValue(struct Symbol const *sym);
void sym_SetExportAll(bool set); void sym_SetExportAll(bool set);
struct Symbol *sym_AddLocalLabel(char const *symName); struct Symbol *sym_AddLocalLabel(char const *symName);
struct Symbol *sym_AddLabel(char const *symName); struct Symbol *sym_AddLabel(char const *symName);
struct Symbol *sym_AddAnonLabel(void);
void sym_WriteAnonLabelName(char buf[MIN_NB_ELMS(MAXSYMLEN + 1)], uint32_t ofs, bool neg);
void sym_Export(char const *symName); void sym_Export(char const *symName);
struct Symbol *sym_AddEqu(char const *symName, int32_t value); struct Symbol *sym_AddEqu(char const *symName, int32_t value);
struct Symbol *sym_AddSet(char const *symName, int32_t value); struct Symbol *sym_AddSet(char const *symName, int32_t value);
@@ -137,8 +141,9 @@ struct Symbol const *sym_GetPC(void);
struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body, size_t size); struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body, size_t size);
struct Symbol *sym_Ref(char const *symName); struct Symbol *sym_Ref(char const *symName);
struct Symbol *sym_AddString(char const *symName, char const *value); struct Symbol *sym_AddString(char const *symName, char const *value);
struct Symbol *sym_RedefString(char const *symName, char const *value);
void sym_Purge(char const *symName); void sym_Purge(char const *symName);
void sym_Init(void); void sym_Init(time_t now);
/* Functions to save and restore the current symbol scope. */ /* Functions to save and restore the current symbol scope. */
char const *sym_GetCurrentSymbolScope(void); char const *sym_GetCurrentSymbolScope(void);

View File

@@ -13,6 +13,9 @@
uint32_t calchash(const char *s); uint32_t calchash(const char *s);
char const *print(int c); char const *print(int c);
/*
* @return The number of bytes read, or 0 if invalid data was found
*/
size_t readUTF8Char(uint8_t *dest, char const *src); size_t readUTF8Char(uint8_t *dest, char const *src);
#endif /* RGBDS_UTIL_H */ #endif /* RGBDS_UTIL_H */

View File

@@ -18,11 +18,13 @@ enum WarningID {
WARNING_BUILTIN_ARG, /* Invalid args to builtins */ WARNING_BUILTIN_ARG, /* Invalid args to builtins */
WARNING_CHARMAP_REDEF, /* Charmap entry re-definition */ WARNING_CHARMAP_REDEF, /* Charmap entry re-definition */
WARNING_DIV, /* Division undefined behavior */ WARNING_DIV, /* Division undefined behavior */
WARNING_EMPTY_DATA_DIRECTIVE, /* `db`, `dw` or `dl` with no directive in ROM */ WARNING_EMPTY_DATA_DIRECTIVE, /* `db`, `dw` or `dl` directive without data in ROM */
WARNING_EMPTY_ENTRY, /* Empty entry in `db`, `dw` or `dl` */ WARNING_EMPTY_MACRO_ARG, /* Empty macro argument */
WARNING_EMPTY_STRRPL, /* Empty second argument in `STRRPL` */
WARNING_LARGE_CONSTANT, /* Constants too large */ WARNING_LARGE_CONSTANT, /* Constants too large */
WARNING_LONG_STR, /* String too long for internal buffers */ WARNING_LONG_STR, /* String too long for internal buffers */
WARNING_NESTED_COMMENT, /* Comment-start delimeter in a block comment */ WARNING_MACRO_SHIFT, /* Shift past available arguments in macro */
WARNING_NESTED_COMMENT, /* Comment-start delimiter in a block comment */
WARNING_OBSOLETE, /* Obsolete things */ WARNING_OBSOLETE, /* Obsolete things */
WARNING_SHIFT, /* Shifting undefined behavior */ WARNING_SHIFT, /* Shifting undefined behavior */
WARNING_SHIFT_AMOUNT, /* Strange shift amount */ WARNING_SHIFT_AMOUNT, /* Strange shift amount */
@@ -46,7 +48,7 @@ void processWarningFlag(char const *flag);
* Used to warn the user about problems that don't prevent the generation of * Used to warn the user about problems that don't prevent the generation of
* valid code. * valid code.
*/ */
void warning(enum WarningID id, const char *fmt, ...); void warning(enum WarningID id, const char *fmt, ...) format_(printf, 2, 3);
/* /*
* Used for errors that compromise the whole assembly process by affecting the * Used for errors that compromise the whole assembly process by affecting the
@@ -55,7 +57,7 @@ void warning(enum WarningID id, const char *fmt, ...);
* It is also used when the assembler goes into an invalid state (for example, * It is also used when the assembler goes into an invalid state (for example,
* when it fails to allocate memory). * when it fails to allocate memory).
*/ */
_Noreturn void fatalerror(const char *fmt, ...); _Noreturn void fatalerror(const char *fmt, ...) format_(printf, 1, 2);
/* /*
* Used for errors that make it impossible to assemble correctly, but don't * Used for errors that make it impossible to assemble correctly, but don't
@@ -63,6 +65,6 @@ _Noreturn void fatalerror(const char *fmt, ...);
* get a list of all errors at the end, making it easier to fix all of them at * get a list of all errors at the end, making it easier to fix all of them at
* once. * once.
*/ */
void error(const char *fmt, ...); void error(const char *fmt, ...) format_(printf, 1, 2);
#endif #endif

View File

@@ -26,8 +26,8 @@
#ifndef RGBDS_EXTERN_GETOPT_H #ifndef RGBDS_EXTERN_GETOPT_H
#define RGBDS_EXTERN_GETOPT_H #define RGBDS_EXTERN_GETOPT_H
extern char *optarg; extern char *musl_optarg;
extern int optind, opterr, optopt, optreset; extern int musl_optind, musl_opterr, musl_optopt, musl_optreset;
struct option { struct option {
const char *name; const char *name;

View File

@@ -32,6 +32,58 @@
static inline _Noreturn unreachable_(void) {} static inline _Noreturn unreachable_(void) {}
#endif #endif
// Use builtins whenever possible, and shim them otherwise
#ifdef __GNUC__ // GCC or compatible
#define ctz __builtin_ctz
#define clz __builtin_clz
#elif defined(_MSC_VER)
#include <assert.h>
#include <intrin.h>
#pragma intrinsic(_BitScanReverse, _BitScanForward)
static inline int ctz(unsigned int x)
{
unsigned long cnt;
assert(x != 0);
_BitScanForward(&cnt, x);
return cnt;
}
static inline int clz(unsigned int x)
{
unsigned long cnt;
assert(x != 0);
_BitScanReverse(&cnt, x);
return 31 - cnt;
}
#else
// FIXME: these are rarely used, and need testing...
#include <limits.h>
static inline int ctz(unsigned int x)
{
int cnt = 0;
while (!(x & 1)) {
x >>= 1;
cnt++;
}
return cnt;
}
static inline int clz(unsigned int x)
{
int cnt = 0;
while (x <= UINT_MAX / 2) {
x <<= 1;
cnt++;
}
return cnt;
}
#endif
// Macros for stringification // Macros for stringification
#define STR(x) #x #define STR(x) #x
#define EXPAND_AND_STR(x) STR(x) #define EXPAND_AND_STR(x) STR(x)

View File

@@ -14,7 +14,7 @@
#define RGBDS_OBJECT_VERSION_STRING "RGB%1u" #define RGBDS_OBJECT_VERSION_STRING "RGB%1u"
#define RGBDS_OBJECT_VERSION_NUMBER 9U #define RGBDS_OBJECT_VERSION_NUMBER 9U
#define RGBDS_OBJECT_REV 6U #define RGBDS_OBJECT_REV 7U
enum AssertionType { enum AssertionType {
ASSERT_WARN, ASSERT_WARN,
@@ -29,6 +29,7 @@ enum RPNCommand {
RPN_DIV = 0x03, RPN_DIV = 0x03,
RPN_MOD = 0x04, RPN_MOD = 0x04,
RPN_UNSUB = 0x05, RPN_UNSUB = 0x05,
RPN_EXP = 0x06,
RPN_OR = 0x10, RPN_OR = 0x10,
RPN_AND = 0x11, RPN_AND = 0x11,

20
include/opmath.h Normal file
View File

@@ -0,0 +1,20 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2021, RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#ifndef RGBDS_OP_MATH_H
#define RGBDS_OP_MATH_H
#include <stdint.h>
int32_t op_divide(int32_t dividend, int32_t divisor);
int32_t op_modulo(int32_t dividend, int32_t divisor);
int32_t op_exponent(int32_t base, uint32_t power);
int32_t op_shift_left(int32_t value, int32_t amount);
int32_t op_shift_right(int32_t value, int32_t amount);
#endif /* RGBDS_OP_MATH_H */

View File

@@ -34,9 +34,40 @@
/* MSVC doesn't use POSIX types or defines for `read` */ /* MSVC doesn't use POSIX types or defines for `read` */
#ifdef _MSC_VER #ifdef _MSC_VER
# include <io.h>
# define STDIN_FILENO 0 # define STDIN_FILENO 0
# define STDOUT_FILENO 1
# define STDERR_FILENO 2
# define ssize_t int # define ssize_t int
# define SSIZE_MAX INT_MAX # define SSIZE_MAX INT_MAX
#else
# include <fcntl.h>
# include <unistd.h>
#endif
/* MSVC doesn't support `[static N]` for array arguments from C99 */
#ifdef _MSC_VER
# define MIN_NB_ELMS(N)
#else
# define MIN_NB_ELMS(N) static (N)
#endif
// MSVC uses a different name for O_RDWR, and needs an additional _O_BINARY flag
#ifdef _MSC_VER
# include <fcntl.h>
# define O_RDWR _O_RDWR
# define S_ISREG(field) ((field) & _S_IFREG)
# define O_BINARY _O_BINARY
#elif !defined(O_BINARY) // Cross-compilers define O_BINARY
# define O_BINARY 0 // POSIX says we shouldn't care!
#endif // _MSC_VER
// Windows has stdin and stdout open as text by default, which we may not want
#if defined(_MSC_VER) || defined(__MINGW32__)
# include <io.h>
# define setmode(fd, mode) _setmode(fd, mode)
#else
# define setmode(fd, mode) ((void)0)
#endif #endif
#endif /* RGBDS_PLATFORM_H */ #endif /* RGBDS_PLATFORM_H */

View File

@@ -9,9 +9,10 @@
#ifndef EXTERN_VERSION_H #ifndef EXTERN_VERSION_H
#define EXTERN_VERSION_H #define EXTERN_VERSION_H
#define PACKAGE_VERSION_MAJOR (0) #define PACKAGE_VERSION_MAJOR 0
#define PACKAGE_VERSION_MINOR (4) #define PACKAGE_VERSION_MINOR 5
#define PACKAGE_VERSION_PATCH (2) #define PACKAGE_VERSION_PATCH 0
#define PACKAGE_VERSION_RC 1
const char *get_package_version_string(void); const char *get_package_version_string(void);

View File

@@ -12,29 +12,45 @@ set(common_src
"version.c" "version.c"
) )
find_package(BISON REQUIRED)
find_package(PkgConfig) find_package(PkgConfig)
if(MSVC OR NOT PKG_CONFIG_FOUND)
if(NOT PKG_CONFIG_FOUND)
# fallback to find_package # fallback to find_package
find_package(PNG REQUIRED) find_package(PNG REQUIRED)
else() else()
pkg_check_modules(LIBPNG REQUIRED libpng) pkg_check_modules(LIBPNG REQUIRED libpng)
endif() endif()
find_package(BISON REQUIRED)
set(BISON_FLAGS "-Wall")
# Set sompe optimization flags on versions that support them
if(BISON_VERSION VERSION_GREATER_EQUAL "3.5")
set(BISON_FLAGS "${BISON_FLAGS} -Dapi.token.raw=true")
endif()
if(BISON_VERSION VERSION_GREATER_EQUAL "3.6")
set(BISON_FLAGS "${BISON_FLAGS} -Dparse.error=detailed")
elseif(BISON_VERSION VERSION_GREATER_EQUAL "3.0")
set(BISON_FLAGS "${BISON_FLAGS} -Dparse.error=verbose")
endif()
if(BISON_VERSION VERSION_GREATER_EQUAL "3.0")
set(BISON_FLAGS "${BISON_FLAGS} -Dparse.lac=full")
set(BISON_FLAGS "${BISON_FLAGS} -Dlr.type=ielr")
endif()
BISON_TARGET(PARSER "asm/parser.y" BISON_TARGET(PARSER "asm/parser.y"
"${PROJECT_SOURCE_DIR}/src/asm/parser.c" "${PROJECT_SOURCE_DIR}/src/asm/parser.c"
COMPILE_FLAGS "${BISON_FLAGS}"
DEFINES_FILE "${PROJECT_SOURCE_DIR}/src/asm/parser.h" DEFINES_FILE "${PROJECT_SOURCE_DIR}/src/asm/parser.h"
) )
set(rgbasm_src set(rgbasm_src
"${BISON_PARSER_OUTPUT_SOURCE}" "${BISON_PARSER_OUTPUT_SOURCE}"
"asm/charmap.c" "asm/charmap.c"
"asm/fixpoint.c"
"asm/format.c"
"asm/fstack.c" "asm/fstack.c"
"asm/lexer.c" "asm/lexer.c"
"asm/macro.c" "asm/macro.c"
"asm/main.c" "asm/main.c"
"asm/math.c" "asm/opt.c"
"asm/output.c" "asm/output.c"
"asm/rpn.c" "asm/rpn.c"
"asm/section.c" "asm/section.c"
@@ -44,6 +60,7 @@ set(rgbasm_src
"extern/utf8decoder.c" "extern/utf8decoder.c"
"hashmap.c" "hashmap.c"
"linkdefs.c" "linkdefs.c"
"opmath.c"
) )
set(rgbfix_src set(rgbfix_src
@@ -67,6 +84,7 @@ set(rgblink_src
"link/symbol.c" "link/symbol.c"
"hashmap.c" "hashmap.c"
"linkdefs.c" "linkdefs.c"
"opmath.c"
) )
foreach(PROG "asm" "fix" "gfx" "link") foreach(PROG "asm" "fix" "gfx" "link")

View File

@@ -13,7 +13,6 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include "asm/asm.h"
#include "asm/charmap.h" #include "asm/charmap.h"
#include "asm/main.h" #include "asm/main.h"
#include "asm/output.h" #include "asm/output.h"
@@ -183,7 +182,7 @@ void charmap_Add(char *mapping, uint8_t value)
} }
if (node->isTerminal) if (node->isTerminal)
warning(WARNING_CHARMAP_REDEF, "Overriding charmap mapping"); warning(WARNING_CHARMAP_REDEF, "Overriding charmap mapping\n");
node->isTerminal = true; node->isTerminal = true;
node->value = value; node->value = value;
@@ -229,6 +228,10 @@ size_t charmap_Convert(char const *input, uint8_t *output)
} else if (*input) { /* No match found */ } else if (*input) { /* No match found */
size_t codepointLen = readUTF8Char(output, input); size_t codepointLen = readUTF8Char(output, input);
if (codepointLen == 0) {
error("Input string is not valid UTF-8!\n");
break;
}
input += codepointLen; /* OK because UTF-8 has no NUL in multi-byte chars */ input += codepointLen; /* OK because UTF-8 has no NUL in multi-byte chars */
output += codepointLen; output += codepointLen;
outputLen += codepointLen; outputLen += codepointLen;

170
src/asm/fixpoint.c Normal file
View File

@@ -0,0 +1,170 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2021, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
/*
* Fixed-point math routines
*/
#include <inttypes.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include "asm/fixpoint.h"
#include "asm/symbol.h"
#include "asm/warning.h"
#define fix2double(i) ((double)((i) / 65536.0))
#define double2fix(d) ((int32_t)round((d) * 65536.0))
// pi radians == 32768 fixed-point "degrees"
#define fdeg2rad(f) ((f) * (M_PI / 32768.0))
#define rad2fdeg(r) ((r) * (32768.0 / M_PI))
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
/*
* Return the _PI symbol value
*/
int32_t fix_Callback_PI(void)
{
warning(WARNING_OBSOLETE, "`_PI` is deprecated; use 3.14159\n");
return double2fix(M_PI);
}
/*
* Print a fixed point value
*/
void fix_Print(int32_t i)
{
uint32_t u = i;
const char *sign = "";
if (i < 0) {
u = -u;
sign = "-";
}
printf("%s%" PRIu32 ".%05" PRIu32, sign, u >> 16,
((uint32_t)(fix2double(u) * 100000 + 0.5)) % 100000);
}
/*
* Calculate sine
*/
int32_t fix_Sin(int32_t i)
{
return double2fix(sin(fdeg2rad(fix2double(i))));
}
/*
* Calculate cosine
*/
int32_t fix_Cos(int32_t i)
{
return double2fix(cos(fdeg2rad(fix2double(i))));
}
/*
* Calculate tangent
*/
int32_t fix_Tan(int32_t i)
{
return double2fix(tan(fdeg2rad(fix2double(i))));
}
/*
* Calculate arcsine
*/
int32_t fix_ASin(int32_t i)
{
return double2fix(rad2fdeg(asin(fix2double(i))));
}
/*
* Calculate arccosine
*/
int32_t fix_ACos(int32_t i)
{
return double2fix(rad2fdeg(acos(fix2double(i))));
}
/*
* Calculate arctangent
*/
int32_t fix_ATan(int32_t i)
{
return double2fix(rad2fdeg(atan(fix2double(i))));
}
/*
* Calculate atan2
*/
int32_t fix_ATan2(int32_t i, int32_t j)
{
return double2fix(rad2fdeg(atan2(fix2double(i), fix2double(j))));
}
/*
* Multiplication
*/
int32_t fix_Mul(int32_t i, int32_t j)
{
return double2fix(fix2double(i) * fix2double(j));
}
/*
* Division
*/
int32_t fix_Div(int32_t i, int32_t j)
{
return double2fix(fix2double(i) / fix2double(j));
}
/*
* Power
*/
int32_t fix_Pow(int32_t i, int32_t j)
{
return double2fix(pow(fix2double(i), fix2double(j)));
}
/*
* Logarithm
*/
int32_t fix_Log(int32_t i, int32_t j)
{
return double2fix(log(fix2double(i)) / log(fix2double(j)));
}
/*
* Round
*/
int32_t fix_Round(int32_t i)
{
return double2fix(round(fix2double(i)));
}
/*
* Ceil
*/
int32_t fix_Ceil(int32_t i)
{
return double2fix(ceil(fix2double(i)));
}
/*
* Floor
*/
int32_t fix_Floor(int32_t i)
{
return double2fix(floor(fix2double(i)));
}

299
src/asm/format.c Normal file
View File

@@ -0,0 +1,299 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 2020, RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
#include <inttypes.h>
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "asm/format.h"
#include "asm/warning.h"
struct FormatSpec fmt_NewSpec(void)
{
struct FormatSpec fmt = {0};
return fmt;
}
bool fmt_IsEmpty(struct FormatSpec const *fmt)
{
return !fmt->state;
}
bool fmt_IsValid(struct FormatSpec const *fmt)
{
return fmt->valid || fmt->state == FORMAT_DONE;
}
bool fmt_IsFinished(struct FormatSpec const *fmt)
{
return fmt->state >= FORMAT_DONE;
}
void fmt_UseCharacter(struct FormatSpec *fmt, int c)
{
if (fmt->state == FORMAT_INVALID)
return;
switch (c) {
/* sign */
case ' ':
case '+':
if (fmt->state > FORMAT_SIGN)
goto invalid;
fmt->state = FORMAT_PREFIX;
fmt->sign = c;
break;
/* prefix */
case '#':
if (fmt->state > FORMAT_PREFIX)
goto invalid;
fmt->state = FORMAT_ALIGN;
fmt->prefix = true;
break;
/* align */
case '-':
if (fmt->state > FORMAT_ALIGN)
goto invalid;
fmt->state = FORMAT_WIDTH;
fmt->alignLeft = true;
break;
/* pad and width */
case '0':
if (fmt->state < FORMAT_WIDTH)
fmt->padZero = true;
/* fallthrough */
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if (fmt->state < FORMAT_WIDTH) {
fmt->state = FORMAT_WIDTH;
fmt->width = c - '0';
} else if (fmt->state == FORMAT_WIDTH) {
fmt->width = fmt->width * 10 + (c - '0');
} else if (fmt->state == FORMAT_FRAC) {
fmt->fracWidth = fmt->fracWidth * 10 + (c - '0');
} else {
goto invalid;
}
break;
case '.':
if (fmt->state > FORMAT_WIDTH)
goto invalid;
fmt->state = FORMAT_FRAC;
fmt->hasFrac = true;
break;
/* type */
case 'd':
case 'u':
case 'X':
case 'x':
case 'b':
case 'o':
case 'f':
case 's':
if (fmt->state >= FORMAT_DONE)
goto invalid;
fmt->state = FORMAT_DONE;
fmt->valid = true;
fmt->type = c;
break;
default:
invalid:
fmt->state = FORMAT_INVALID;
fmt->valid = false;
}
}
void fmt_FinishCharacters(struct FormatSpec *fmt)
{
if (!fmt_IsValid(fmt))
fmt->state = FORMAT_INVALID;
}
void fmt_PrintString(char *buf, size_t bufLen, struct FormatSpec const *fmt, char const *value)
{
if (fmt->sign)
error("Formatting string with sign flag '%c'\n", fmt->sign);
if (fmt->prefix)
error("Formatting string with prefix flag '#'\n");
if (fmt->padZero)
error("Formatting string with padding flag '0'\n");
if (fmt->hasFrac)
error("Formatting string with fractional width\n");
if (fmt->type != 's')
error("Formatting string as type '%c'\n", fmt->type);
size_t len = strlen(value);
size_t totalLen = fmt->width > len ? fmt->width : len;
if (totalLen + 1 > bufLen) /* bufLen includes terminator */
error("Formatted string value too long\n");
size_t padLen = fmt->width > len ? fmt->width - len : 0;
if (fmt->alignLeft) {
strncpy(buf, value, len < bufLen ? len : bufLen);
for (size_t i = 0; i < totalLen && len + i < bufLen; i++)
buf[len + i] = ' ';
} else {
for (size_t i = 0; i < padLen && i < bufLen; i++)
buf[i] = ' ';
if (bufLen > padLen)
strncpy(buf + padLen, value, bufLen - padLen - 1);
}
buf[totalLen] = '\0';
}
void fmt_PrintNumber(char *buf, size_t bufLen, struct FormatSpec const *fmt, uint32_t value)
{
if (fmt->type != 'X' && fmt->type != 'x' && fmt->type != 'b' && fmt->type != 'o'
&& fmt->prefix)
error("Formatting type '%c' with prefix flag '#'\n", fmt->type);
if (fmt->type != 'f' && fmt->hasFrac)
error("Formatting type '%c' with fractional width\n", fmt->type);
if (fmt->type == 's')
error("Formatting number as type 's'\n");
char sign = fmt->sign; /* 0 or ' ' or '+' */
if (fmt->type == 'd' || fmt->type == 'f') {
int32_t v = value;
if (v < 0 && v != INT32_MIN) {
sign = '-';
value = -v;
}
}
char prefix = !fmt->prefix ? 0
: fmt->type == 'X' ? '$'
: fmt->type == 'x' ? '$'
: fmt->type == 'b' ? '%'
: fmt->type == 'o' ? '&'
: 0;
char valueBuf[262]; /* Max 5 digits + decimal + 255 fraction digits + terminator */
if (fmt->type == 'b') {
/* Special case for binary */
char *ptr = valueBuf;
do {
*ptr++ = (value & 1) + '0';
value >>= 1;
} while (value);
*ptr = '\0';
/* Reverse the digits */
size_t valueLen = ptr - valueBuf;
for (size_t i = 0, j = valueLen - 1; i < j; i++, j--) {
char c = valueBuf[i];
valueBuf[i] = valueBuf[j];
valueBuf[j] = c;
}
} else if (fmt->type == 'f') {
/* Special case for fixed-point */
/* Default fractional width (C's is 6 for "%f"; here 5 is enough) */
uint8_t fracWidth = fmt->hasFrac ? fmt->fracWidth : 5;
if (fracWidth) {
char spec[16]; /* Max "%" + 5-char PRIu32 + ".%0255.f" + terminator */
snprintf(spec, sizeof(spec), "%%" PRIu32 ".%%0%d.f", fracWidth);
snprintf(valueBuf, sizeof(valueBuf), spec, value >> 16,
(value % 65536) / 65536.0 * pow(10, fracWidth) + 0.5);
} else {
snprintf(valueBuf, sizeof(valueBuf), "%" PRIu32, value >> 16);
}
} else {
char const *spec = fmt->type == 'd' ? "%" PRId32
: fmt->type == 'u' ? "%" PRIu32
: fmt->type == 'X' ? "%" PRIX32
: fmt->type == 'x' ? "%" PRIx32
: fmt->type == 'o' ? "%" PRIo32
: "%" PRId32;
snprintf(valueBuf, sizeof(valueBuf), spec, value);
}
size_t len = strlen(valueBuf);
size_t numLen = len;
if (sign)
numLen++;
if (prefix)
numLen++;
size_t totalLen = fmt->width > numLen ? fmt->width : numLen;
if (totalLen + 1 > bufLen) /* bufLen includes terminator */
error("Formatted numeric value too long\n");
size_t padLen = fmt->width > numLen ? fmt->width - numLen : 0;
if (fmt->alignLeft) {
size_t pos = 0;
if (sign && pos < bufLen)
buf[pos++] = sign;
if (prefix && pos < bufLen)
buf[pos++] = prefix;
strcpy(buf + pos, valueBuf);
pos += len;
for (size_t i = 0; i < totalLen && pos + i < bufLen; i++)
buf[pos + i] = ' ';
} else {
size_t pos = 0;
if (fmt->padZero) {
/* sign, then prefix, then zero padding */
if (sign && pos < bufLen)
buf[pos++] = sign;
if (prefix && pos < bufLen)
buf[pos++] = prefix;
for (size_t i = 0; i < padLen && pos < bufLen; i++)
buf[pos++] = '0';
} else {
/* space padding, then sign, then prefix */
for (size_t i = 0; i < padLen && pos < bufLen; i++)
buf[pos++] = ' ';
if (sign && pos < bufLen)
buf[pos++] = sign;
if (prefix && pos < bufLen)
buf[pos++] = prefix;
}
if (bufLen > pos)
strcpy(buf + pos, valueBuf);
}
buf[totalLen] = '\0';
}

View File

@@ -21,8 +21,10 @@
#include "asm/warning.h" #include "asm/warning.h"
#include "platform.h" /* S_ISDIR (stat macro) */ #include "platform.h" /* S_ISDIR (stat macro) */
#define MAXINCPATHS 128
#ifdef LEXER_DEBUG #ifdef LEXER_DEBUG
#define dbgPrint(...) fprintf(stderr, "[lexer] " __VA_ARGS__) #define dbgPrint(...) fprintf(stderr, "[fstack] " __VA_ARGS__)
#else #else
#define dbgPrint(...) #define dbgPrint(...)
#endif #endif
@@ -34,6 +36,9 @@ struct Context {
uint32_t uniqueID; uint32_t uniqueID;
struct MacroArgs *macroArgs; /* Macro args are *saved* here */ struct MacroArgs *macroArgs; /* Macro args are *saved* here */
uint32_t nbReptIters; uint32_t nbReptIters;
int32_t forValue;
int32_t forStep;
char *forName;
}; };
static struct Context *contextStack; static struct Context *contextStack;
@@ -44,7 +49,7 @@ size_t nMaxRecursionDepth;
static unsigned int nbIncPaths = 0; static unsigned int nbIncPaths = 0;
static char const *includePaths[MAXINCPATHS]; static char const *includePaths[MAXINCPATHS];
char const *dumpNodeAndParents(struct FileStackNode const *node) static const char *dumpNodeAndParents(struct FileStackNode const *node)
{ {
char const *name; char const *name;
@@ -85,6 +90,9 @@ void fstk_DumpCurrent(void)
struct FileStackNode *fstk_GetFileStack(void) struct FileStackNode *fstk_GetFileStack(void)
{ {
if (!contextStack)
return NULL;
struct FileStackNode *node = contextStack->fileInfo; struct FileStackNode *node = contextStack->fileInfo;
/* Mark node and all of its parents as referenced if not already so they don't get freed */ /* Mark node and all of its parents as referenced if not already so they don't get freed */
@@ -197,6 +205,12 @@ bool fstk_FindFile(char const *path, char **fullPath, size_t *size)
bool yywrap(void) bool yywrap(void)
{ {
uint32_t nIFDepth = lexer_GetIFDepth();
if (nIFDepth != 0)
fatalerror("Ended block with %" PRIu32 " unterminated IF construct%s\n",
nIFDepth, nIFDepth == 1 ? "" : "s");
if (contextStack->fileInfo->type == NODE_REPT) { /* The context is a REPT block, which may loop */ if (contextStack->fileInfo->type == NODE_REPT) { /* The context is a REPT block, which may loop */
struct FileStackReptNode *fileInfo = (struct FileStackReptNode *)contextStack->fileInfo; struct FileStackReptNode *fileInfo = (struct FileStackReptNode *)contextStack->fileInfo;
@@ -216,6 +230,17 @@ bool yywrap(void)
contextStack->fileInfo = (struct FileStackNode *)fileInfo; contextStack->fileInfo = (struct FileStackNode *)fileInfo;
} }
/* If this is a FOR, update the symbol value */
if (contextStack->forName && fileInfo->iters[0] <= contextStack->nbReptIters) {
contextStack->forValue += contextStack->forStep;
struct Symbol *sym = sym_AddSet(contextStack->forName,
contextStack->forValue);
/* This error message will refer to the current iteration */
if (sym->type != SYM_SET)
fatalerror("Failed to update FOR symbol value\n");
}
/* Advance to the next iteration */
fileInfo->iters[0]++; fileInfo->iters[0]++;
/* If this wasn't the last iteration, wrap instead of popping */ /* If this wasn't the last iteration, wrap instead of popping */
if (fileInfo->iters[0] <= contextStack->nbReptIters) { if (fileInfo->iters[0] <= contextStack->nbReptIters) {
@@ -231,17 +256,20 @@ bool yywrap(void)
struct Context *context = contextStack; struct Context *context = contextStack;
contextStack = contextStack->parent; contextStack = contextStack->parent;
assert(contextDepth != 0); // This is never supposed to underflow
contextDepth--; contextDepth--;
lexer_DeleteState(context->lexerState); lexer_DeleteState(context->lexerState);
/* Restore args if a macro (not REPT) saved them */ /* Restore args if a macro (not REPT) saved them */
if (context->fileInfo->type == NODE_MACRO) { if (context->fileInfo->type == NODE_MACRO) {
dbgPrint("Restoring macro args %p\n", contextStack->macroArgs); dbgPrint("Restoring macro args %p\n", (void *)contextStack->macroArgs);
macro_UseNewArgs(contextStack->macroArgs); macro_UseNewArgs(contextStack->macroArgs);
} }
/* Free the file stack node */ /* Free the file stack node */
if (!context->fileInfo->referenced) if (!context->fileInfo->referenced)
free(context->fileInfo); free(context->fileInfo);
/* Free the FOR symbol name */
free(context->forName);
/* Free the entry and make its parent the current entry */ /* Free the entry and make its parent the current entry */
free(context); free(context);
@@ -253,6 +281,7 @@ bool yywrap(void)
/* /*
* Make sure not to switch the lexer state before calling this, so the saved line no is correct * Make sure not to switch the lexer state before calling this, so the saved line no is correct
* BE CAREFUL!! This modifies the file stack directly, you should have set up the file info first * BE CAREFUL!! This modifies the file stack directly, you should have set up the file info first
* Callers should set contextStack->lexerState after this so it is not NULL
*/ */
static void newContext(struct FileStackNode *fileInfo) static void newContext(struct FileStackNode *fileInfo)
{ {
@@ -267,13 +296,13 @@ static void newContext(struct FileStackNode *fileInfo)
fileInfo->referenced = false; fileInfo->referenced = false;
fileInfo->lineNo = lexer_GetLineNo(); fileInfo->lineNo = lexer_GetLineNo();
context->fileInfo = fileInfo; context->fileInfo = fileInfo;
context->forName = NULL;
/* /*
* Link new entry to its parent so it's reachable later * Link new entry to its parent so it's reachable later
* ERRORS SHOULD NOT OCCUR AFTER THIS!! * ERRORS SHOULD NOT OCCUR AFTER THIS!!
*/ */
context->parent = contextStack; context->parent = contextStack;
contextStack = context; contextStack = context;
} }
void fstk_RunInclude(char const *path) void fstk_RunInclude(char const *path)
@@ -376,9 +405,8 @@ void fstk_RunMacro(char const *macroName, struct MacroArgs *args)
memcpy(dest, macro->name, macroNameLen + 1); memcpy(dest, macro->name, macroNameLen + 1);
newContext((struct FileStackNode *)fileInfo); newContext((struct FileStackNode *)fileInfo);
/* Line minus 1 because buffer begins with a newline */
contextStack->lexerState = lexer_OpenFileView(macro->macro, macro->macroSize, contextStack->lexerState = lexer_OpenFileView(macro->macro, macro->macroSize,
macro->fileLine - 1); macro->fileLine);
if (!contextStack->lexerState) if (!contextStack->lexerState)
fatalerror("Failed to set up lexer for macro invocation\n"); fatalerror("Failed to set up lexer for macro invocation\n");
lexer_SetStateAtEOL(contextStack->lexerState); lexer_SetStateAtEOL(contextStack->lexerState);
@@ -386,12 +414,8 @@ void fstk_RunMacro(char const *macroName, struct MacroArgs *args)
macro_UseNewArgs(args); macro_UseNewArgs(args);
} }
void fstk_RunRept(uint32_t count, int32_t reptLineNo, char *body, size_t size) static bool newReptContext(int32_t reptLineNo, char *body, size_t size)
{ {
dbgPrint("Running REPT(%" PRIu32 ")\n", count);
if (count == 0)
return;
uint32_t reptDepth = contextStack->fileInfo->type == NODE_REPT uint32_t reptDepth = contextStack->fileInfo->type == NODE_REPT
? ((struct FileStackReptNode *)contextStack->fileInfo)->reptDepth ? ((struct FileStackReptNode *)contextStack->fileInfo)->reptDepth
: 0; : 0;
@@ -400,7 +424,7 @@ void fstk_RunRept(uint32_t count, int32_t reptLineNo, char *body, size_t size)
if (!fileInfo) { if (!fileInfo) {
error("Failed to alloc file info for REPT: %s\n", strerror(errno)); error("Failed to alloc file info for REPT: %s\n", strerror(errno));
return; return false;
} }
fileInfo->node.type = NODE_REPT; fileInfo->node.type = NODE_REPT;
fileInfo->reptDepth = reptDepth + 1; fileInfo->reptDepth = reptDepth + 1;
@@ -417,11 +441,75 @@ void fstk_RunRept(uint32_t count, int32_t reptLineNo, char *body, size_t size)
contextStack->lexerState = lexer_OpenFileView(body, size, reptLineNo); contextStack->lexerState = lexer_OpenFileView(body, size, reptLineNo);
if (!contextStack->lexerState) if (!contextStack->lexerState)
fatalerror("Failed to set up lexer for rept block\n"); fatalerror("Failed to set up lexer for REPT block\n");
lexer_SetStateAtEOL(contextStack->lexerState); lexer_SetStateAtEOL(contextStack->lexerState);
contextStack->uniqueID = macro_UseNewUniqueID(); contextStack->uniqueID = macro_UseNewUniqueID();
contextStack->nbReptIters = count; return true;
}
void fstk_RunRept(uint32_t count, int32_t reptLineNo, char *body, size_t size)
{
dbgPrint("Running REPT(%" PRIu32 ")\n", count);
if (count == 0)
return;
if (!newReptContext(reptLineNo, body, size))
return;
contextStack->nbReptIters = count;
contextStack->forName = NULL;
}
void fstk_RunFor(char const *symName, int32_t start, int32_t stop, int32_t step,
int32_t reptLineNo, char *body, size_t size)
{
dbgPrint("Running FOR(\"%s\", %" PRId32 ", %" PRId32 ", %" PRId32 ")\n",
symName, start, stop, step);
struct Symbol *sym = sym_AddSet(symName, start);
if (sym->type != SYM_SET)
return;
uint32_t count = 0;
if (step > 0 && start < stop)
count = (stop - start - 1) / step + 1;
else if (step < 0 && stop < start)
count = (start - stop - 1) / -step + 1;
else if (step == 0)
error("FOR cannot have a step value of 0\n");
if (count == 0)
return;
if (!newReptContext(reptLineNo, body, size))
return;
contextStack->nbReptIters = count;
contextStack->forValue = start;
contextStack->forStep = step;
contextStack->forName = strdup(symName);
if (!contextStack->forName)
fatalerror("Not enough memory for FOR symbol name: %s\n", strerror(errno));
}
void fstk_StopRept(void)
{
/* Prevent more iterations */
contextStack->nbReptIters = 0;
}
bool fstk_Break(void)
{
dbgPrint("Breaking out of REPT/FOR\n");
if (contextStack->fileInfo->type != NODE_REPT) {
error("BREAK can only be used inside a REPT/FOR block\n");
return false;
}
fstk_StopRept();
return true;
} }
void fstk_Init(char const *mainPath, size_t maxRecursionDepth) void fstk_Init(char const *mainPath, size_t maxRecursionDepth)
@@ -453,6 +541,9 @@ void fstk_Init(char const *mainPath, size_t maxRecursionDepth)
context->uniqueID = 0; context->uniqueID = 0;
macro_SetUniqueID(0); macro_SetUniqueID(0);
context->nbReptIters = 0; context->nbReptIters = 0;
context->forValue = 0;
context->forStep = 0;
context->forName = NULL;
/* Now that it's set up properly, register the context */ /* Now that it's set up properly, register the context */
contextStack = context; contextStack = context;

File diff suppressed because it is too large Load Diff

View File

@@ -2,14 +2,16 @@
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <inttypes.h> #include <inttypes.h>
#include <stdbool.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "asm/asm.h"
#include "asm/macro.h" #include "asm/macro.h"
#include "asm/warning.h" #include "asm/warning.h"
#define MAXMACROARGS 99999
/* /*
* Your average macro invocation does not go past the tens, but some go further * Your average macro invocation does not go past the tens, but some go further
* This ensures that sane and slightly insane invocations suffer no penalties, * This ensures that sane and slightly insane invocations suffer no penalties,
@@ -36,7 +38,7 @@ static uint32_t maxUniqueID = 0;
* guarantees the size of the buffer will be correct. I was unable to find a * guarantees the size of the buffer will be correct. I was unable to find a
* better solution, but if you have one, please feel free! * better solution, but if you have one, please feel free!
*/ */
static char uniqueIDBuf[] = "_" EXPAND_AND_STR(UINT32_MAX); static char uniqueIDBuf[] = "_u4294967295"; // UINT32_MAX
static char *uniqueIDPtr = NULL; static char *uniqueIDPtr = NULL;
struct MacroArgs *macro_GetCurrentArgs(void) struct MacroArgs *macro_GetCurrentArgs(void)
@@ -60,14 +62,15 @@ struct MacroArgs *macro_NewArgs(void)
void macro_AppendArg(struct MacroArgs **argPtr, char *s) void macro_AppendArg(struct MacroArgs **argPtr, char *s)
{ {
#define macArgs (*argPtr) #define macArgs (*argPtr)
if (s[0] == '\0')
warning(WARNING_EMPTY_MACRO_ARG, "Empty macro argument\n");
if (macArgs->nbArgs == MAXMACROARGS) if (macArgs->nbArgs == MAXMACROARGS)
error("A maximum of " EXPAND_AND_STR(MAXMACROARGS) error("A maximum of " EXPAND_AND_STR(MAXMACROARGS) " arguments is allowed\n");
" arguments is allowed\n");
if (macArgs->nbArgs >= macArgs->capacity) { if (macArgs->nbArgs >= macArgs->capacity) {
macArgs->capacity *= 2; macArgs->capacity *= 2;
/* Check that overflow didn't roll us back */ /* Check that overflow didn't roll us back */
if (macArgs->capacity <= macArgs->nbArgs) if (macArgs->capacity <= macArgs->nbArgs)
fatalerror("Failed to add new macro argument: possible capacity overflow\n"); fatalerror("Failed to add new macro argument: capacity overflow\n");
macArgs = realloc(macArgs, SIZEOF_ARGS(macArgs->capacity)); macArgs = realloc(macArgs, SIZEOF_ARGS(macArgs->capacity));
if (!macArgs) if (!macArgs)
fatalerror("Error adding new macro argument: %s\n", strerror(errno)); fatalerror("Error adding new macro argument: %s\n", strerror(errno));
@@ -98,6 +101,40 @@ char const *macro_GetArg(uint32_t i)
: macroArgs->args[realIndex]; : macroArgs->args[realIndex];
} }
char *macro_GetAllArgs(void)
{
if (!macroArgs)
return NULL;
if (macroArgs->shift >= macroArgs->nbArgs)
return "";
size_t len = 0;
for (uint32_t i = macroArgs->shift; i < macroArgs->nbArgs; i++)
len += strlen(macroArgs->args[i]) + 1; /* 1 for comma */
char *str = malloc(len + 1); /* 1 for '\0' */
char *ptr = str;
if (!str)
fatalerror("Failed to allocate memory for expanding '\\#': %s\n", strerror(errno));
for (uint32_t i = macroArgs->shift; i < macroArgs->nbArgs; i++) {
size_t n = strlen(macroArgs->args[i]);
memcpy(ptr, macroArgs->args[i], n);
ptr += n;
/* Commas go between args and after a last empty arg */
if (i < macroArgs->nbArgs - 1 || n == 0)
*ptr++ = ','; /* no space after comma */
}
*ptr = '\0';
return str;
}
uint32_t macro_GetUniqueID(void) uint32_t macro_GetUniqueID(void)
{ {
return uniqueID; return uniqueID;
@@ -117,7 +154,8 @@ void macro_SetUniqueID(uint32_t id)
if (uniqueID > maxUniqueID) if (uniqueID > maxUniqueID)
maxUniqueID = uniqueID; maxUniqueID = uniqueID;
/* The buffer is guaranteed to be the correct size */ /* The buffer is guaranteed to be the correct size */
sprintf(uniqueIDBuf, "_%" PRIu32, id); /* This is a valid label fragment, but not a valid numeric */
sprintf(uniqueIDBuf, "_u%" PRIu32, id);
uniqueIDPtr = uniqueIDBuf; uniqueIDPtr = uniqueIDBuf;
} }
} }
@@ -132,12 +170,17 @@ void macro_ShiftCurrentArgs(int32_t count)
{ {
if (!macroArgs) { if (!macroArgs) {
error("Cannot shift macro arguments outside of a macro\n"); error("Cannot shift macro arguments outside of a macro\n");
} else if (count < 0) { } else if (count > 0 && (count > macroArgs->nbArgs
error("Cannot shift arguments by negative amount %" PRId32 "\n", count); || macroArgs->shift > macroArgs->nbArgs - count)) {
} else if (macroArgs->shift < macroArgs->nbArgs) { warning(WARNING_MACRO_SHIFT,
"Cannot shift macro arguments past their end\n");
macroArgs->shift = macroArgs->nbArgs;
} else if (count < 0 && macroArgs->shift < -count) {
warning(WARNING_MACRO_SHIFT,
"Cannot shift macro arguments past their beginning\n");
macroArgs->shift = 0;
} else {
macroArgs->shift += count; macroArgs->shift += count;
if (macroArgs->shift > macroArgs->nbArgs)
macroArgs->shift = macroArgs->nbArgs;
} }
} }

View File

@@ -19,9 +19,11 @@
#include <time.h> #include <time.h>
#include "asm/charmap.h" #include "asm/charmap.h"
#include "asm/format.h"
#include "asm/fstack.h" #include "asm/fstack.h"
#include "asm/lexer.h" #include "asm/lexer.h"
#include "asm/main.h" #include "asm/main.h"
#include "asm/opt.h"
#include "asm/output.h" #include "asm/output.h"
#include "asm/rpn.h" #include "asm/rpn.h"
#include "asm/symbol.h" #include "asm/symbol.h"
@@ -38,15 +40,6 @@
// Unfortunately, macOS still ships 2.3, which is from 2008... // Unfortunately, macOS still ships 2.3, which is from 2008...
int yyparse(void); int yyparse(void);
size_t cldefines_index;
size_t cldefines_numindices;
size_t cldefines_bufsize;
const size_t cldefine_entrysize = 2 * sizeof(void *);
char **cldefines;
clock_t nStartClock, nEndClock;
uint32_t nTotalLines, nIFDepth;
#if defined(YYDEBUG) && YYDEBUG #if defined(YYDEBUG) && YYDEBUG
extern int yydebug; extern int yydebug;
#endif #endif
@@ -57,162 +50,11 @@ bool oFailedOnMissingInclude;
bool oGeneratePhonyDeps; bool oGeneratePhonyDeps;
char *tzTargetFileName; char *tzTargetFileName;
/*
* Option stack
*/
struct sOptions DefaultOptions;
struct sOptions CurrentOptions;
bool haltnop; bool haltnop;
bool optimizeloads; bool optimizeloads;
bool verbose; bool verbose;
bool warnings; /* True to enable warnings, false to disable them. */ bool warnings; /* True to enable warnings, false to disable them. */
struct sOptionStackEntry {
struct sOptions Options;
struct sOptionStackEntry *next;
};
struct sOptionStackEntry *pOptionStack;
void opt_SetCurrentOptions(struct sOptions *opt)
{
CurrentOptions = *opt;
lexer_SetGfxDigits(CurrentOptions.gbgfx);
lexer_SetBinDigits(CurrentOptions.binary);
}
void opt_Parse(char *s)
{
struct sOptions newopt;
newopt = CurrentOptions;
switch (s[0]) {
case 'g':
if (strlen(&s[1]) == 4) {
newopt.gbgfx[0] = s[1];
newopt.gbgfx[1] = s[2];
newopt.gbgfx[2] = s[3];
newopt.gbgfx[3] = s[4];
} else {
error("Must specify exactly 4 characters for option 'g'\n");
}
break;
case 'b':
if (strlen(&s[1]) == 2) {
newopt.binary[0] = s[1];
newopt.binary[1] = s[2];
} else {
error("Must specify exactly 2 characters for option 'b'\n");
}
break;
case 'z':
warning(WARNING_OBSOLETE, "Option 'z' is a deprecated alias for 'p'\n");
/* fallthrough */
case 'p':
if (strlen(&s[1]) <= 2) {
int result;
unsigned int fillchar;
result = sscanf(&s[1], "%x", &fillchar);
if (result != EOF && result != 1)
error("Invalid argument for option 'z'\n");
else
newopt.fillchar = fillchar;
} else {
error("Invalid argument for option 'z'\n");
}
break;
default:
error("Unknown option\n");
break;
}
opt_SetCurrentOptions(&newopt);
}
void opt_Push(void)
{
struct sOptionStackEntry *pOpt;
pOpt = malloc(sizeof(struct sOptionStackEntry));
if (pOpt == NULL)
fatalerror("No memory for option stack\n");
pOpt->Options = CurrentOptions;
pOpt->next = pOptionStack;
pOptionStack = pOpt;
}
void opt_Pop(void)
{
if (pOptionStack == NULL)
fatalerror("No entries in the option stack\n");
struct sOptionStackEntry *pOpt;
pOpt = pOptionStack;
opt_SetCurrentOptions(&(pOpt->Options));
pOptionStack = pOpt->next;
free(pOpt);
}
void opt_AddDefine(char *s)
{
char *value, *equals;
if (cldefines_index >= cldefines_numindices) {
/* Check for overflows */
if ((cldefines_numindices * 2) < cldefines_numindices)
fatalerror("No memory for command line defines\n");
if ((cldefines_bufsize * 2) < cldefines_bufsize)
fatalerror("No memory for command line defines\n");
cldefines_numindices *= 2;
cldefines_bufsize *= 2;
cldefines = realloc(cldefines, cldefines_bufsize);
if (!cldefines)
fatalerror("No memory for command line defines\n");
}
equals = strchr(s, '=');
if (equals) {
*equals = '\0';
value = equals + 1;
} else {
value = "1";
}
cldefines[cldefines_index++] = s;
cldefines[cldefines_index++] = value;
}
static void opt_ParseDefines(void)
{
uint32_t i;
for (i = 0; i < cldefines_index; i += 2)
sym_AddString(cldefines[i], cldefines[i + 1]);
}
void upperstring(char *s)
{
while (*s) {
*s = toupper(*s);
s++;
}
}
void lowerstring(char *s)
{
while (*s) {
*s = tolower(*s);
s++;
}
}
/* Escapes Make-special chars from a string */ /* Escapes Make-special chars from a string */
static char *make_escape(const char *str) static char *make_escape(const char *str)
{ {
@@ -234,7 +76,7 @@ static char *make_escape(const char *str)
} }
/* Short options */ /* Short options */
static char const *optstring = "b:D:Eg:hi:LM:o:p:r:VvW:w"; static const char *optstring = "b:D:Eg:hi:LM:o:p:r:VvW:w";
/* Variables for the long-only options */ /* Variables for the long-only options */
static int depType; /* Variants of `-M` */ static int depType; /* Variants of `-M` */
@@ -276,7 +118,7 @@ static void print_usage(void)
fputs( fputs(
"Usage: rgbasm [-EhLVvw] [-b chars] [-D name[=value]] [-g chars] [-i path]\n" "Usage: rgbasm [-EhLVvw] [-b chars] [-D name[=value]] [-g chars] [-i path]\n"
" [-M depend_file] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n" " [-M depend_file] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n"
" [-o out_file] [-p pad_value] [-r depth] [-W warning] <file> ...\n" " [-o out_file] [-p pad_value] [-r depth] [-W warning] <file>\n"
"Useful options:\n" "Useful options:\n"
" -E, --export-all export all labels\n" " -E, --export-all export all labels\n"
" -M, --dependfile <path> set the output dependency file\n" " -M, --dependfile <path> set the output dependency file\n"
@@ -295,120 +137,130 @@ int main(int argc, char *argv[])
int ch; int ch;
char *ep; char *ep;
struct sOptions newopt; time_t now = time(NULL);
char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH");
char *tzMainfile; /*
* Support SOURCE_DATE_EPOCH for reproducible builds
* https://reproducible-builds.org/docs/source-date-epoch/
*/
if (sourceDateEpoch)
now = (time_t)strtoul(sourceDateEpoch, NULL, 0);
dependfile = NULL; dependfile = NULL;
/* Initial number of allocated elements in array */
cldefines_numindices = 32;
cldefines_bufsize = cldefines_numindices * cldefine_entrysize;
cldefines = malloc(cldefines_bufsize);
if (!cldefines)
fatalerror("No memory for command line defines\n");
#if defined(YYDEBUG) && YYDEBUG #if defined(YYDEBUG) && YYDEBUG
yydebug = 1; yydebug = 1;
#endif #endif
// Perform some init for below
sym_Init(now);
// Set defaults
oGeneratePhonyDeps = false; oGeneratePhonyDeps = false;
oGeneratedMissingIncludes = false; oGeneratedMissingIncludes = false;
oFailedOnMissingInclude = false; oFailedOnMissingInclude = false;
tzTargetFileName = NULL; tzTargetFileName = NULL;
uint32_t maxRecursionDepth = 64;
size_t nTargetFileNameLen = 0;
DefaultOptions.gbgfx[0] = '0'; opt_B("01");
DefaultOptions.gbgfx[1] = '1'; opt_G("0123");
DefaultOptions.gbgfx[2] = '2'; opt_P(0);
DefaultOptions.gbgfx[3] = '3';
DefaultOptions.binary[0] = '0';
DefaultOptions.binary[1] = '1';
DefaultOptions.fillchar = 0;
optimizeloads = true; optimizeloads = true;
haltnop = true; haltnop = true;
verbose = false; verbose = false;
warnings = true; warnings = true;
bool exportall = false; sym_SetExportAll(false);
uint32_t maxRecursionDepth = 64;
size_t nTargetFileNameLen = 0;
opt_SetCurrentOptions(&DefaultOptions); while ((ch = musl_getopt_long_only(argc, argv, optstring, longopts, NULL)) != -1) {
newopt = CurrentOptions;
while ((ch = musl_getopt_long_only(argc, argv, optstring, longopts,
NULL)) != -1) {
switch (ch) { switch (ch) {
case 'b': case 'b':
if (strlen(optarg) == 2) { if (strlen(musl_optarg) == 2)
newopt.binary[0] = optarg[1]; opt_B(&musl_optarg[1]);
newopt.binary[1] = optarg[2]; else
} else {
errx(1, "Must specify exactly 2 characters for option 'b'"); errx(1, "Must specify exactly 2 characters for option 'b'");
}
break; break;
char *equals;
case 'D': case 'D':
opt_AddDefine(optarg); equals = strchr(musl_optarg, '=');
break; if (equals) {
case 'E': *equals = '\0';
exportall = true; sym_AddString(musl_optarg, equals + 1);
break;
case 'g':
if (strlen(optarg) == 4) {
newopt.gbgfx[0] = optarg[1];
newopt.gbgfx[1] = optarg[2];
newopt.gbgfx[2] = optarg[3];
newopt.gbgfx[3] = optarg[4];
} else { } else {
errx(1, "Must specify exactly 4 characters for option 'g'"); sym_AddString(musl_optarg, "1");
} }
break; break;
case 'E':
sym_SetExportAll(true);
break;
case 'g':
if (strlen(musl_optarg) == 4)
opt_G(&musl_optarg[1]);
else
errx(1, "Must specify exactly 4 characters for option 'g'");
break;
case 'h': case 'h':
haltnop = false; haltnop = false;
break; break;
case 'i': case 'i':
fstk_AddIncludePath(optarg); fstk_AddIncludePath(musl_optarg);
break; break;
case 'L': case 'L':
optimizeloads = false; optimizeloads = false;
break; break;
case 'M': case 'M':
if (!strcmp("-", optarg)) if (!strcmp("-", musl_optarg))
dependfile = stdout; dependfile = stdout;
else else
dependfile = fopen(optarg, "w"); dependfile = fopen(musl_optarg, "w");
if (dependfile == NULL) if (dependfile == NULL)
err(1, "Could not open dependfile %s", err(1, "Could not open dependfile %s", musl_optarg);
optarg);
break; break;
case 'o':
out_SetFileName(optarg);
break;
case 'p':
newopt.fillchar = strtoul(optarg, &ep, 0);
if (optarg[0] == '\0' || *ep != '\0') case 'o':
out_SetFileName(musl_optarg);
break;
unsigned long fill;
case 'p':
fill = strtoul(musl_optarg, &ep, 0);
if (musl_optarg[0] == '\0' || *ep != '\0')
errx(1, "Invalid argument for option 'p'"); errx(1, "Invalid argument for option 'p'");
if (newopt.fillchar < 0 || newopt.fillchar > 0xFF) if (fill < 0 || fill > 0xFF)
errx(1, "Argument for option 'p' must be between 0 and 0xFF"); errx(1, "Argument for option 'p' must be between 0 and 0xFF");
opt_P(fill);
break; break;
case 'r':
maxRecursionDepth = strtoul(optarg, &ep, 0);
if (optarg[0] == '\0' || *ep != '\0') case 'r':
maxRecursionDepth = strtoul(musl_optarg, &ep, 0);
if (musl_optarg[0] == '\0' || *ep != '\0')
errx(1, "Invalid argument for option 'r'"); errx(1, "Invalid argument for option 'r'");
break; break;
case 'V': case 'V':
printf("rgbasm %s\n", get_package_version_string()); printf("rgbasm %s\n", get_package_version_string());
exit(0); exit(0);
case 'v': case 'v':
verbose = true; verbose = true;
break; break;
case 'W': case 'W':
processWarningFlag(optarg); processWarningFlag(musl_optarg);
break; break;
case 'w': case 'w':
warnings = false; warnings = false;
break; break;
@@ -419,37 +271,36 @@ int main(int argc, char *argv[])
case 'G': case 'G':
oGeneratedMissingIncludes = true; oGeneratedMissingIncludes = true;
break; break;
case 'P': case 'P':
oGeneratePhonyDeps = true; oGeneratePhonyDeps = true;
break; break;
case 'Q': case 'Q':
case 'T': case 'T':
if (optind == argc) if (musl_optind == argc)
errx(1, "-M%c takes a target file name argument", errx(1, "-M%c takes a target file name argument", depType);
depType); ep = musl_optarg;
ep = optarg;
if (depType == 'Q') if (depType == 'Q')
ep = make_escape(ep); ep = make_escape(ep);
nTargetFileNameLen += strlen(ep) + 1; nTargetFileNameLen += strlen(ep) + 1;
if (!tzTargetFileName) { if (!tzTargetFileName) {
/* On first alloc, make an empty str */ /* On first alloc, make an empty str */
tzTargetFileName = tzTargetFileName = malloc(nTargetFileNameLen + 1);
malloc(nTargetFileNameLen + 1);
if (tzTargetFileName) if (tzTargetFileName)
*tzTargetFileName = '\0'; *tzTargetFileName = '\0';
} else { } else {
tzTargetFileName = tzTargetFileName = realloc(tzTargetFileName,
realloc(tzTargetFileName, nTargetFileNameLen + 1);
nTargetFileNameLen + 1);
} }
if (tzTargetFileName == NULL) if (tzTargetFileName == NULL)
err(1, "Cannot append new file to target file list"); err(1, "Cannot append new file to target file list");
strcat(tzTargetFileName, ep); strcat(tzTargetFileName, ep);
if (depType == 'Q') if (depType == 'Q')
free(ep); free(ep);
char *ptr = tzTargetFileName + char *ptr = tzTargetFileName + strlen(tzTargetFileName);
strlen(tzTargetFileName);
*ptr++ = ' '; *ptr++ = ' ';
*ptr = '\0'; *ptr = '\0';
break; break;
@@ -462,76 +313,49 @@ int main(int argc, char *argv[])
/* NOTREACHED */ /* NOTREACHED */
} }
} }
argc -= optind;
argv += optind;
if (tzTargetFileName == NULL) if (tzTargetFileName == NULL)
tzTargetFileName = tzObjectname; tzTargetFileName = tzObjectname;
opt_SetCurrentOptions(&newopt); if (argc == musl_optind) {
fputs("FATAL: No input files\n", stderr);
DefaultOptions = CurrentOptions; print_usage();
} else if (argc != musl_optind + 1) {
if (argc == 0) { fputs("FATAL: More than one input file given\n", stderr);
fputs("FATAL: no input files\n", stderr);
print_usage(); print_usage();
} }
tzMainfile = argv[argc - 1]; char const *mainFileName = argv[musl_optind];
if (verbose) if (verbose)
printf("Assembling %s\n", tzMainfile); printf("Assembling %s\n", mainFileName);
if (dependfile) { if (dependfile) {
if (!tzTargetFileName) if (!tzTargetFileName)
errx(1, "Dependency files can only be created if a target file is specified with either -o, -MQ or -MT.\n"); errx(1, "Dependency files can only be created if a target file is specified with either -o, -MQ or -MT\n");
fprintf(dependfile, "%s: %s\n", tzTargetFileName, tzMainfile); fprintf(dependfile, "%s: %s\n", tzTargetFileName, mainFileName);
} }
/* Init file stack; important to do first, since it provides the file name, line, etc */
lexer_Init();
fstk_Init(tzMainfile, maxRecursionDepth);
nStartClock = clock();
nTotalLines = 0;
nIFDepth = 0;
sym_Init();
sym_SetExportAll(exportall);
opt_ParseDefines();
charmap_New("main", NULL); charmap_New("main", NULL);
opt_SetCurrentOptions(&DefaultOptions); // Init lexer and file stack, prodiving file info
lexer_Init();
fstk_Init(mainFileName, maxRecursionDepth);
// Perform parse (yyparse is auto-generated from `parser.y`)
if (yyparse() != 0 && nbErrors == 0)
nbErrors = 1;
if (yyparse() != 0 || nbErrors != 0)
errx(1, "Assembly aborted (%u errors)!", nbErrors);
if (dependfile) if (dependfile)
fclose(dependfile); fclose(dependfile);
if (nIFDepth != 0)
errx(1, "Unterminated IF construct (%" PRIu32 " levels)!",
nIFDepth);
sect_CheckUnionClosed(); sect_CheckUnionClosed();
double timespent; if (nbErrors != 0)
errx(1, "Assembly aborted (%u errors)!", nbErrors);
nEndClock = clock();
timespent = ((double)(nEndClock - nStartClock))
/ (double)CLOCKS_PER_SEC;
if (verbose) {
printf("Success! %" PRIu32 " lines in %d.%02d seconds ",
nTotalLines, (int)timespent,
((int)(timespent * 100.0)) % 100);
if (timespent < FLT_MIN_EXP)
printf("(INFINITY lines/minute)\n");
else
printf("(%d lines/minute)\n",
(int)(60 / timespent * nTotalLines));
}
// If parse aborted due to missing an include, and `-MG` was given, exit normally
if (oFailedOnMissingInclude) if (oFailedOnMissingInclude)
return 0; return 0;

View File

@@ -1,147 +0,0 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2018, Carsten Sorensen and RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
/*
* Fixedpoint math routines
*/
#include <inttypes.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include "asm/mymath.h"
#include "asm/symbol.h"
#define fx2double(i) ((double)((i) / 65536.0))
#define double2fx(d) ((int32_t)((d) * 65536.0))
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
/*
* Define the _PI symbol
*/
void math_DefinePI(void)
{
sym_AddEqu("_PI", double2fx(M_PI));
}
/*
* Print a fixed point value
*/
void math_Print(int32_t i)
{
uint32_t u = i;
const char *sign = "";
if (i < 0) {
u = -u;
sign = "-";
}
printf("%s%" PRIu32 ".%05" PRIu32, sign, u >> 16,
((uint32_t)(fx2double(u) * 100000 + 0.5)) % 100000);
}
/*
* Calculate sine
*/
int32_t math_Sin(int32_t i)
{
return double2fx(sin(fx2double(i) * 2 * M_PI / 65536));
}
/*
* Calculate cosine
*/
int32_t math_Cos(int32_t i)
{
return double2fx(cos(fx2double(i) * 2 * M_PI / 65536));
}
/*
* Calculate tangent
*/
int32_t math_Tan(int32_t i)
{
return double2fx(tan(fx2double(i) * 2 * M_PI / 65536));
}
/*
* Calculate arcsine
*/
int32_t math_ASin(int32_t i)
{
return double2fx(asin(fx2double(i)) / 2 / M_PI * 65536);
}
/*
* Calculate arccosine
*/
int32_t math_ACos(int32_t i)
{
return double2fx(acos(fx2double(i)) / 2 / M_PI * 65536);
}
/*
* Calculate arctangent
*/
int32_t math_ATan(int32_t i)
{
return double2fx(atan(fx2double(i)) / 2 / M_PI * 65536);
}
/*
* Calculate atan2
*/
int32_t math_ATan2(int32_t i, int32_t j)
{
return double2fx(atan2(fx2double(i), fx2double(j)) / 2 / M_PI * 65536);
}
/*
* Multiplication
*/
int32_t math_Mul(int32_t i, int32_t j)
{
return double2fx(fx2double(i) * fx2double(j));
}
/*
* Division
*/
int32_t math_Div(int32_t i, int32_t j)
{
return double2fx(fx2double(i) / fx2double(j));
}
/*
* Round
*/
int32_t math_Round(int32_t i)
{
return double2fx(round(fx2double(i)));
}
/*
* Ceil
*/
int32_t math_Ceil(int32_t i)
{
return double2fx(ceil(fx2double(i)));
}
/*
* Floor
*/
int32_t math_Floor(int32_t i)
{
return double2fx(floor(fx2double(i)));
}

111
src/asm/opt.c Normal file
View File

@@ -0,0 +1,111 @@
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "asm/lexer.h"
#include "asm/section.h"
#include "asm/warning.h"
struct OptStackEntry {
char binary[2];
char gbgfx[4];
int32_t fillByte;
struct OptStackEntry *next;
};
static struct OptStackEntry *stack = NULL;
void opt_B(char chars[2])
{
lexer_SetBinDigits(chars);
}
void opt_G(char chars[4])
{
lexer_SetGfxDigits(chars);
}
void opt_P(uint8_t fill)
{
fillByte = fill;
}
void opt_Parse(char *s)
{
switch (s[0]) {
case 'b':
if (strlen(&s[1]) == 2)
opt_B(&s[1]);
else
error("Must specify exactly 2 characters for option 'b'\n");
break;
case 'g':
if (strlen(&s[1]) == 4)
opt_G(&s[1]);
else
error("Must specify exactly 4 characters for option 'g'\n");
break;
case 'p':
if (strlen(&s[1]) <= 2) {
int result;
unsigned int fillchar;
result = sscanf(&s[1], "%x", &fillchar);
if (result != EOF && result != 1)
error("Invalid argument for option 'p'\n");
else
opt_P(fillchar);
} else {
error("Invalid argument for option 'p'\n");
}
break;
default:
error("Unknown option '%c'\n", s[0]);
break;
}
}
void opt_Push(void)
{
struct OptStackEntry *entry = malloc(sizeof(*entry));
if (entry == NULL)
fatalerror("Failed to alloc option stack entry: %s\n", strerror(errno));
// Both of these pulled from lexer.h
entry->binary[0] = binDigits[0];
entry->binary[1] = binDigits[1];
entry->gbgfx[0] = gfxDigits[0];
entry->gbgfx[1] = gfxDigits[1];
entry->gbgfx[2] = gfxDigits[2];
entry->gbgfx[3] = gfxDigits[3];
entry->fillByte = fillByte; // Pulled from section.h
entry->next = stack;
stack = entry;
}
void opt_Pop(void)
{
if (stack == NULL) {
error("No entries in the option stack\n");
return;
}
struct OptStackEntry *entry = stack;
opt_B(entry->binary);
opt_G(entry->gbgfx);
opt_P(entry->fillByte);
stack = entry->next;
free(entry);
}

View File

@@ -18,7 +18,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "asm/asm.h"
#include "asm/charmap.h" #include "asm/charmap.h"
#include "asm/fstack.h" #include "asm/fstack.h"
#include "asm/main.h" #include "asm/main.h"
@@ -69,7 +68,7 @@ static struct FileStackNode *fileStackNodes = NULL;
/* /*
* Count the number of sections used in this object * Count the number of sections used in this object
*/ */
static uint32_t countsections(void) static uint32_t countSections(void)
{ {
uint32_t count = 0; uint32_t count = 0;
@@ -82,7 +81,7 @@ static uint32_t countsections(void)
/* /*
* Count the number of patches used in this object * Count the number of patches used in this object
*/ */
static uint32_t countpatches(struct Section const *sect) static uint32_t countPatches(struct Section const *sect)
{ {
uint32_t r = 0; uint32_t r = 0;
@@ -96,7 +95,7 @@ static uint32_t countpatches(struct Section const *sect)
/** /**
* Count the number of assertions used in this object * Count the number of assertions used in this object
*/ */
static uint32_t countasserts(void) static uint32_t countAsserts(void)
{ {
struct Assertion *assert = assertions; struct Assertion *assert = assertions;
uint32_t count = 0; uint32_t count = 0;
@@ -111,22 +110,22 @@ static uint32_t countasserts(void)
/* /*
* Write a long to a file (little-endian) * Write a long to a file (little-endian)
*/ */
static void fputlong(uint32_t i, FILE *f) static void putlong(uint32_t i, FILE *f)
{ {
fputc(i, f); putc(i, f);
fputc(i >> 8, f); putc(i >> 8, f);
fputc(i >> 16, f); putc(i >> 16, f);
fputc(i >> 24, f); putc(i >> 24, f);
} }
/* /*
* Write a NULL-terminated string to a file * Write a NULL-terminated string to a file
*/ */
static void fputstring(char const *s, FILE *f) static void putstring(char const *s, FILE *f)
{ {
while (*s) while (*s)
fputc(*s++, f); putc(*s++, f);
fputc(0, f); putc(0, f);
} }
static uint32_t getNbFileStackNodes(void) static uint32_t getNbFileStackNodes(void)
@@ -204,14 +203,13 @@ static uint32_t getSectIDIfAny(struct Section const *sect)
static void writepatch(struct Patch const *patch, FILE *f) static void writepatch(struct Patch const *patch, FILE *f)
{ {
assert(patch->src->ID != -1); assert(patch->src->ID != -1);
putlong(patch->src->ID, f);
fputlong(patch->src->ID, f); putlong(patch->lineNo, f);
fputlong(patch->lineNo, f); putlong(patch->nOffset, f);
fputlong(patch->nOffset, f); putlong(getSectIDIfAny(patch->pcSection), f);
fputlong(getSectIDIfAny(patch->pcSection), f); putlong(patch->pcOffset, f);
fputlong(patch->pcOffset, f); putc(patch->type, f);
fputc(patch->type, f); putlong(patch->nRPNSize, f);
fputlong(patch->nRPNSize, f);
fwrite(patch->pRPN, 1, patch->nRPNSize, f); fwrite(patch->pRPN, 1, patch->nRPNSize, f);
} }
@@ -220,23 +218,23 @@ static void writepatch(struct Patch const *patch, FILE *f)
*/ */
static void writesection(struct Section const *sect, FILE *f) static void writesection(struct Section const *sect, FILE *f)
{ {
fputstring(sect->name, f); putstring(sect->name, f);
fputlong(sect->size, f); putlong(sect->size, f);
bool isUnion = sect->modifier == SECTION_UNION; bool isUnion = sect->modifier == SECTION_UNION;
bool isFragment = sect->modifier == SECTION_FRAGMENT; bool isFragment = sect->modifier == SECTION_FRAGMENT;
fputc(sect->type | isUnion << 7 | isFragment << 6, f); putc(sect->type | isUnion << 7 | isFragment << 6, f);
fputlong(sect->org, f); putlong(sect->org, f);
fputlong(sect->bank, f); putlong(sect->bank, f);
fputc(sect->align, f); putc(sect->align, f);
fputlong(sect->alignOfs, f); putlong(sect->alignOfs, f);
if (sect_HasData(sect->type)) { if (sect_HasData(sect->type)) {
fwrite(sect->data, 1, sect->size, f); fwrite(sect->data, 1, sect->size, f);
fputlong(countpatches(sect), f); putlong(countPatches(sect), f);
for (struct Patch const *patch = sect->patches; patch != NULL; for (struct Patch const *patch = sect->patches; patch != NULL;
patch = patch->next) patch = patch->next)
@@ -249,17 +247,17 @@ static void writesection(struct Section const *sect, FILE *f)
*/ */
static void writesymbol(struct Symbol const *sym, FILE *f) static void writesymbol(struct Symbol const *sym, FILE *f)
{ {
fputstring(sym->name, f); putstring(sym->name, f);
if (!sym_IsDefined(sym)) { if (!sym_IsDefined(sym)) {
fputc(SYMTYPE_IMPORT, f); putc(SYMTYPE_IMPORT, f);
} else { } else {
assert(sym->src->ID != -1); assert(sym->src->ID != -1);
fputc(sym->isExported ? SYMTYPE_EXPORT : SYMTYPE_LOCAL, f); putc(sym->isExported ? SYMTYPE_EXPORT : SYMTYPE_LOCAL, f);
fputlong(sym->src->ID, f); putlong(sym->src->ID, f);
fputlong(sym->fileLine, f); putlong(sym->fileLine, f);
fputlong(getSectIDIfAny(sym_GetSection(sym)), f); putlong(getSectIDIfAny(sym_GetSection(sym)), f);
fputlong(sym->value, f); putlong(sym->value, f);
} }
} }
@@ -411,10 +409,15 @@ static struct Patch *allocpatch(uint32_t type, struct Expression const *expr, ui
/* /*
* Create a new patch (includes the rpn expr) * Create a new patch (includes the rpn expr)
*/ */
void out_CreatePatch(uint32_t type, struct Expression const *expr, uint32_t ofs) void out_CreatePatch(uint32_t type, struct Expression const *expr, uint32_t ofs, uint32_t pcShift)
{ {
struct Patch *patch = allocpatch(type, expr, ofs); struct Patch *patch = allocpatch(type, expr, ofs);
// If the patch had a quantity of bytes output before it,
// PC is not at the patch's location, but at the location
// before those bytes.
patch->pcOffset -= pcShift;
patch->next = pCurrentSection->patches; patch->next = pCurrentSection->patches;
pCurrentSection->patches = patch; pCurrentSection->patches = patch;
} }
@@ -446,30 +449,32 @@ bool out_CreateAssert(enum AssertionType type, struct Expression const *expr,
static void writeassert(struct Assertion *assert, FILE *f) static void writeassert(struct Assertion *assert, FILE *f)
{ {
writepatch(assert->patch, f); writepatch(assert->patch, f);
fputstring(assert->message, f); putstring(assert->message, f);
} }
static void writeFileStackNode(struct FileStackNode const *node, FILE *f) static void writeFileStackNode(struct FileStackNode const *node, FILE *f)
{ {
fputlong(node->parent ? node->parent->ID : -1, f); putlong(node->parent ? node->parent->ID : -1, f);
fputlong(node->lineNo, f); putlong(node->lineNo, f);
fputc(node->type, f); putc(node->type, f);
if (node->type != NODE_REPT) { if (node->type != NODE_REPT) {
fputstring(((struct FileStackNamedNode const *)node)->name, f); putstring(((struct FileStackNamedNode const *)node)->name, f);
} else { } else {
struct FileStackReptNode const *reptNode = (struct FileStackReptNode const *)node; struct FileStackReptNode const *reptNode = (struct FileStackReptNode const *)node;
fputlong(reptNode->reptDepth, f); putlong(reptNode->reptDepth, f);
/* Iters are stored by decreasing depth, so reverse the order for output */ /* Iters are stored by decreasing depth, so reverse the order for output */
for (uint32_t i = reptNode->reptDepth; i--; ) for (uint32_t i = reptNode->reptDepth; i--; )
fputlong(reptNode->iters[i], f); putlong(reptNode->iters[i], f);
} }
} }
static void registerExportedSymbol(struct Symbol *symbol, void *arg) static void registerUnregisteredSymbol(struct Symbol *symbol, void *arg)
{ {
(void)arg; (void)arg; // sym_ForEach requires a void* parameter, but we are not using it.
if (sym_IsExported(symbol) && symbol->ID == -1) {
// Check for symbol->src, to skip any built-in symbol from rgbasm
if (symbol->src && symbol->ID == -1) {
registerSymbol(symbol); registerSymbol(symbol);
} }
} }
@@ -488,16 +493,16 @@ void out_WriteObject(void)
if (!f) if (!f)
err(1, "Couldn't write file '%s'", tzObjectname); err(1, "Couldn't write file '%s'", tzObjectname);
/* Also write exported symbols that weren't written above */ /* Also write symbols that weren't written above */
sym_ForEach(registerExportedSymbol, NULL); sym_ForEach(registerUnregisteredSymbol, NULL);
fprintf(f, RGBDS_OBJECT_VERSION_STRING, RGBDS_OBJECT_VERSION_NUMBER); fprintf(f, RGBDS_OBJECT_VERSION_STRING, RGBDS_OBJECT_VERSION_NUMBER);
fputlong(RGBDS_OBJECT_REV, f); putlong(RGBDS_OBJECT_REV, f);
fputlong(nbSymbols, f); putlong(nbSymbols, f);
fputlong(countsections(), f); putlong(countSections(), f);
fputlong(getNbFileStackNodes(), f); putlong(getNbFileStackNodes(), f);
for (struct FileStackNode const *node = fileStackNodes; node; node = node->next) { for (struct FileStackNode const *node = fileStackNodes; node; node = node->next) {
writeFileStackNode(node, f); writeFileStackNode(node, f);
if (node->next && node->next->ID != node->ID - 1) if (node->next && node->next->ID != node->ID - 1)
@@ -512,7 +517,7 @@ void out_WriteObject(void)
for (struct Section *sect = pSectionList; sect; sect = sect->next) for (struct Section *sect = pSectionList; sect; sect = sect->next)
writesection(sect, f); writesection(sect, f);
fputlong(countasserts(), f); putlong(countAsserts(), f);
for (struct Assertion *assert = assertions; assert; for (struct Assertion *assert = assertions; assert;
assert = assert->next) assert = assert->next)
writeassert(assert, f); writeassert(assert, f);

File diff suppressed because it is too large Load Diff

View File

@@ -179,9 +179,9 @@ Enables literally every warning.
.Pp .Pp
The following warnings are actual warning flags; with each description, the corresponding warning flag is included. The following warnings are actual warning flags; with each description, the corresponding warning flag is included.
Note that each of these flag also has a negation (for example, Note that each of these flag also has a negation (for example,
.Fl Wempty-entry .Fl Wcharmap-redef
enables the warning that enables the warning that
.Fl Wno-empty-entry .Fl Wno-charmap-redef
disables). disables).
Only the non-default flag is listed here. Only the non-default flag is listed here.
Ignoring the Ignoring the
@@ -209,12 +209,16 @@ This warning is enabled by
.Fl Wall . .Fl Wall .
.It Fl Wdiv .It Fl Wdiv
Warn when dividing the smallest negative integer by -1, which yields itself due to integer overflow. Warn when dividing the smallest negative integer by -1, which yields itself due to integer overflow.
.It Fl Wempty-entry .It Fl Wempty-macro-arg
Warn when an empty entry is encountered in a Warn when a macro argument is empty.
.Ic db , dw , dl
list.
This warning is enabled by This warning is enabled by
.Fl Wextra . .Fl Wextra .
.It Fl Wempty-strrpl
Warn when
.Fn STRRPL
is called with an empty string as its second argument (the substring to replace).
This warning is enabled by
.Fl Wall .
.It Fl Wlarge-constant .It Fl Wlarge-constant
Warn when a constant too large to fit in a signed 32-bit integer is encountered. Warn when a constant too large to fit in a signed 32-bit integer is encountered.
This warning is enabled by This warning is enabled by
@@ -223,12 +227,16 @@ This warning is enabled by
Warn when a string too long to fit in internal buffers is encountered. Warn when a string too long to fit in internal buffers is encountered.
This warning is enabled by This warning is enabled by
.Fl Wall . .Fl Wall .
.It Fl Wmacro-shift
Warn when shifting macro arguments past their limits.
This warning is enabled by
.Fl Wextra .
.It Fl Wno-obsolete .It Fl Wno-obsolete
Warn when obsolete constructs such as the Warn when obsolete constructs such as the
.Ic jp [hl] .Ic _PI
instruction or constant or
.Ic HOME .Ic PRINTT
section type are encountered. directive are encountered.
.It Fl Wshift .It Fl Wshift
Warn when shifting right a negative value. Warn when shifting right a negative value.
Use a division by 2^N instead. Use a division by 2^N instead.

View File

@@ -29,7 +29,7 @@ but any program that processes RGB object files (described in
.Xr rgbds 5 ) .Xr rgbds 5 )
can be used in its place. can be used in its place.
.Sh SYNTAX .Sh SYNTAX
The syntax is linebased, just as in any other assembler, meaning that you do one instruction or pseudoop per line: The syntax is linebased, just as in any other assembler, meaning that you do one instruction or directive per line:
.Pp .Pp
.Dl Oo Ar label Oc Oo Ar instruction Oc Oo Ar ;\ comment Oc .Dl Oo Ar label Oc Oo Ar instruction Oc Oo Ar ;\ comment Oc
.Pp .Pp
@@ -38,14 +38,15 @@ Example:
John: ld a,87 ;Weee John: ld a,87 ;Weee
.Ed .Ed
.Pp .Pp
All reserved keywords (pseudoops, mnemonics, registers etc.) are caseinsensitive, all identifiers (symbol names) are case-sensitive. All reserved keywords (directives, mnemonics, registers, etc.) are caseinsensitive;
all identifiers (symbol names) are case-sensitive.
.Pp .Pp
Comments are used to give humans information about the code, such as explanations. Comments are used to give humans information about the code, such as explanations.
The assembler The assembler
.Em always .Em always
ignores comments and their contents. ignores comments and their contents.
.Pp .Pp
There are three syntaxes for comments. There are two syntaxes for comments.
The most common is that anything that follows a semicolon The most common is that anything that follows a semicolon
.Ql \&; .Ql \&;
not inside a string, is a comment until the end of the line. not inside a string, is a comment until the end of the line.
@@ -58,10 +59,6 @@ It can be split across multiple lines, or occur in the middle of an expression:
X = /* the value of x X = /* the value of x
should be 3 */ 3 should be 3 */ 3
.Ed .Ed
The third is that lines beginning with a
.Ql *
(not even spaces before it) are ignored.
This third syntax is deprecated (will be removed in a future version) and should be replaced with either of the first two.
.Pp .Pp
Sometimes lines can be too long and it may be necessary to split them. Sometimes lines can be too long and it may be necessary to split them.
To do so, put a backslash at the end of the line: To do so, put a backslash at the end of the line:
@@ -105,6 +102,8 @@ There are a number of numeric formats.
.It Gameboy graphics Ta \` Ta 0123 .It Gameboy graphics Ta \` Ta 0123
.El .El
.Pp .Pp
Underscores are also accepted in numbers, except at the beginning of one.
.Pp
The "character constant" form yields the value the character maps to in the current charmap. The "character constant" form yields the value the character maps to in the current charmap.
For example, by default For example, by default
.Pq refer to Xr ascii 7 .Pq refer to Xr ascii 7
@@ -129,6 +128,7 @@ A great number of operators you can use in expressions are available (listed fro
.It Sy Operator Ta Sy Meaning .It Sy Operator Ta Sy Meaning
.It Li \&( \&) Ta Precedence override .It Li \&( \&) Ta Precedence override
.It Li FUNC() Ta Built-in function call .It Li FUNC() Ta Built-in function call
.It Li ** Ta Exponent
.It Li ~ + - Ta Unary complement/plus/minus .It Li ~ + - Ta Unary complement/plus/minus
.It Li * / % Ta Multiply/divide/modulo .It Li * / % Ta Multiply/divide/modulo
.It Li << >> Ta Shift left/right .It Li << >> Ta Shift left/right
@@ -143,9 +143,16 @@ A great number of operators you can use in expressions are available (listed fro
complements a value by inverting all its bits. complements a value by inverting all its bits.
.Pp .Pp
.Ic % .Ic %
is used to get the remainder of the corresponding division. is used to get the remainder of the corresponding division, so that
.Sq 5 % 2 .Sq a / b * b + a % b == a
is 1. is always true.
The result has the same sign as the divisor.
This makes
.Sq a % b .
equal to
.Sq (a + b) % b
or
.Sq (a - b) % b .
.Pp .Pp
Shifting works by shifting all bits in the left operand either left Shifting works by shifting all bits in the left operand either left
.Pq Sq << .Pq Sq <<
@@ -168,7 +175,8 @@ still evaluates both operands of
and and
.Sq || . .Sq || .
.Pp .Pp
! returns 1 if the operand was 0, and 0 otherwise. .Ic \&!
returns 1 if the operand was 0, and 0 otherwise.
.Ss Fixedpoint Expressions .Ss Fixedpoint Expressions
Fixed-point numbers are basically normal (32-bit) integers, which count 65536th's instead of entire units, offering better precision than integers but limiting the range of values. Fixed-point numbers are basically normal (32-bit) integers, which count 65536th's instead of entire units, offering better precision than integers but limiting the range of values.
The upper 16 bits are used for the integer part and the lower 16 bits are used for the fraction (65536ths). The upper 16 bits are used for the integer part and the lower 16 bits are used for the fraction (65536ths).
@@ -188,30 +196,39 @@ delim $$
.It Sy Name Ta Sy Operation .It Sy Name Ta Sy Operation
.It Fn DIV x y Ta $x \[di] y$ .It Fn DIV x y Ta $x \[di] y$
.It Fn MUL x y Ta $x \[mu] y$ .It Fn MUL x y Ta $x \[mu] y$
.It Fn SIN x Ta $sin ( x )$ .It Fn POW x y Ta $x$ to the $y$ power
.It Fn COS x Ta $cos ( x )$ .It Fn LOG x y Ta Logarithm of $x$ to the base $y$
.It Fn TAN x Ta $tan ( x )$ .It Fn ROUND x Ta Round $x$ to the nearest integer
.It Fn ASIN x Ta $asin ( x )$ .It Fn CEIL x Ta Round $x$ up to an integer
.It Fn ACOS x Ta $acos ( x )$ .It Fn FLOOR x Ta Round $x$ down to an integer
.It Fn ATAN x Ta $atan ( x )$ .It Fn SIN x Ta Sine of $x$
.It Fn COS x Ta Cosine of $x$
.It Fn TAN x Ta Tangent of $x$
.It Fn ASIN x Ta Inverse sine of $x$
.It Fn ACOS x Ta Inverse cosine of $x$
.It Fn ATAN x Ta Inverse tangent of $x$
.It Fn ATAN2 x y Ta Angle between $( x , y )$ and $( 1 , 0 )$ .It Fn ATAN2 x y Ta Angle between $( x , y )$ and $( 1 , 0 )$
.El .El
.EQ .EQ
delim off delim off
.EN .EN
.Pp .Pp
The trigonometry functions (
.Ic SIN ,
.Ic COS ,
.Ic TAN ,
etc) are defined in terms of a circle divided into 65535.0 degrees.
.Pp
These functions are useful for automatic generation of various tables. These functions are useful for automatic generation of various tables.
Example: assuming a circle has 65536.0 degrees, and sine values are in range For example:
.Bq -1.0 ;\ 1.0 :
.Bd -literal -offset indent .Bd -literal -offset indent
;\ -- ; Generate a 256-byte sine table with values in the range [0, 128]
;\ -- Generate a 256-byte sine table with values between 0 and 128 ; (shifted and scaled from the range [-1.0, 1.0])
;\ --
ANGLE = 0.0 ANGLE = 0.0
REPT 256 REPT 256
db MUL(64.0, SIN(ANGLE) + 1.0) >> 16 db (MUL(64.0, SIN(ANGLE)) + 64.0) >> 16
ANGLE = ANGLE + 256.0 ; 256 = 65536 / table_len, with table_len = 256 ANGLE = ANGLE + 256.0 ; 256.0 = 65536 degrees / 256 entries
ENDR ENDR
.Ed .Ed
.Ss String Expressions .Ss String Expressions
The most basic string expression is any number of characters contained in double quotes The most basic string expression is any number of characters contained in double quotes
@@ -226,17 +243,25 @@ There are a number of escape sequences you can use within a string:
.It Sy String Ta Sy Meaning .It Sy String Ta Sy Meaning
.It Ql \[rs]\[rs] Ta Produces a backslash .It Ql \[rs]\[rs] Ta Produces a backslash
.It Ql \[rs]" Ta Produces a double quote without terminating .It Ql \[rs]" Ta Produces a double quote without terminating
.It Ql \[rs], Ta Comma
.It Ql \[rs]{ Ta Curly bracket left .It Ql \[rs]{ Ta Curly bracket left
.It Ql \[rs]} Ta Curly bracket right .It Ql \[rs]} Ta Curly bracket right
.It Ql \[rs]n Ta Newline ($0A) .It Ql \[rs]n Ta Newline ($0A)
.It Ql \[rs]r Ta Carriage return ($0D) .It Ql \[rs]r Ta Carriage return ($0D)
.It Ql \[rs]t Ta Tab ($09) .It Ql \[rs]t Ta Tab ($09)
.It Qo \[rs]1 Qc \[en] Qo \[rs]9 Qc Ta Macro argument (Only the body of a macro, see Sx Invoking macros ) .It Qo \[rs]1 Qc \[en] Qo \[rs]9 Qc Ta Macro argument (Only in the body of a macro; see Sx Invoking macros )
.It Ql \[rs]@ Ta Label name suffix (Only in the body of macros and REPTs) .It Ql \[rs]# Ta All Dv _NARG No macro arguments, separated by commas (Only in the body of a macro)
.It Ql \[rs]@ Ta Label name suffix (Only in the body of a macro or a Ic REPT No block)
.El .El
(Note that some of those can be used outside of strings, when noted further in this document.) (Note that some of those can be used outside of strings, when noted further in this document.)
.Pp .Pp
Multi-line strings are contained in triple quotes
.Pq Ql \&"\&"\&"for instance\&"\&"\&" .
Escape sequences work the same way in multi-line strings; however, literal newline
characters will be included as-is, without needing to escape them with
.Ql \[rs]r
or
.Ql \[rs]n .
.Pp
A funky feature is A funky feature is
.Ql {symbol} .Ql {symbol}
within a string, called within a string, called
@@ -249,55 +274,138 @@ If it's a numeric symbol, its value is converted to hexadecimal notation with a
.Sq $ .Sq $
prepended. prepended.
.Bd -literal -offset indent .Bd -literal -offset indent
TOPIC equs "life, the universe, and everything" TOPIC equs "life, the universe, and \[rs]"everything\[rs]""
ANSWER = 42 ANSWER = 42
;\ Prints "The answer to life, the universe, and everything is $2A" ;\ Prints "The answer to life, the universe, and "everything" is $2A"
PRINTT "The answer to {TOPIC} is {ANSWER}\[rs]n" PRINTLN "The answer to {TOPIC} is {ANSWER}"
.Ed .Ed
.Pp .Pp
Symbol interpolations can be nested, too! Symbol interpolations can be nested, too!
.Pp .Pp
It's possible to change the way numeric symbols are converted by specifying a print type like so: It's possible to change the way symbols are converted by specifying a print format like so:
.Ql {d:symbol} . .Ql {fmt:symbol} .
The
.Ql fmt
specifier consists of parts
.Ql <sign><prefix><align><pad><width><frac><type> .
These parts are:
.Bl -column "<prefix>"
.It Sy Part Ta Sy Meaning
.It Ql <sign> Ta May be
.Ql +
or
.Ql \ .
If specified, prints this character in front of non-negative numbers.
.It Ql <prefix> Ta May be
.Ql # .
If specified, prints the appropriate prefix for numbers,
.Ql $ ,
.Ql & ,
or
.Ql % .
.It Ql <align> Ta May be
.Ql - .
If specified, aligns left instead of right.
.It Ql <pad> Ta May be
.Ql 0 .
If specified, pads right-aligned numbers with zeros instead of spaces.
.It Ql <width> Ta May be one or more
.Ql 0
\[en]
.Ql 9 .
If specified, pads the value to this width, right-aligned with spaces by default.
.It Ql <frac> Ta May be
.Ql \&.
followed by one or more
.Ql 0
\[en]
.Ql 9 .
If specified, prints this many digits of a fixed-point fraction.
Defaults to 5 digits.
.It Ql <type> Ta Specifies the type of value.
.El
.Pp
All the format specifier parts are optional except the
.Ql <type> .
Valid print types are: Valid print types are:
.Bl -column -offset indent "Print type" "Lowercase hexadecimal" "Example" .Bl -column -offset indent "Print type" "Lowercase hexadecimal" "Example"
.It Sy Print type Ta Sy Format Ta Sy Example .It Sy Print type Ta Sy Format Ta Sy Example
.It Ql d Ta Decimal Ta 42 .It Ql d Ta Signed decimal Ta -42
.It Ql u Ta Unsigned decimal Ta 42
.It Ql x Ta Lowercase hexadecimal Ta 2a .It Ql x Ta Lowercase hexadecimal Ta 2a
.It Ql X Ta Uppercase hexadecimal Ta 2A .It Ql X Ta Uppercase hexadecimal Ta 2A
.It Ql b Ta Binary Ta 101010 .It Ql b Ta Binary Ta 101010
.It Ql o Ta Octal Ta 52
.It Ql f Ta Fixed-point Ta 1234.56789
.It Ql s Ta String Ta \&"example\&"
.El .El
.Pp .Pp
Note that print types should only be used with numeric values, not strings. Examples:
.Bd -literal -offset indent
; Prints "%0010 + $3 == 5"
PRINTLN STRFMT("%#05b + %#x == %d", 2, 3, 2+3)
; Prints "32% of 20 = 6.40"
PRINTLN STRFMT("%d%% of %d = %.2f", 32, 20, MUL(20.0, 0.32))
; Prints "Hello world!"
PRINTLN STRFMT("Hello %s!", STRLWR("WORLD"))
.Ed
.Pp .Pp
HINT: The HINT: The
.Ic {symbol} .Ic {symbol}
construct can also be used outside strings. construct can also be used outside strings.
The symbol's value is again inserted directly. The symbol's value is again inserted directly.
.Bd -literal -offset indent
NAME equs "ITEM"
FMT equs "d"
ZERO_NUM equ 0
ZERO_STR equs "0"
;\ Defines INDEX as 100
INDEX = 1{ZERO_STR}{{FMT}:ZERO_NUM}
;\ Defines ITEM_100 as "\[rs]"hundredth\[rs]""
{NAME}_{d:INDEX} equs "\[rs]"hundredth\[rs]""
;\ Prints "ITEM_100 is hundredth"
PRINTLN STRCAT("{NAME}_{d:INDEX} is ", {NAME}_{d:INDEX})
;\ Purges ITEM_100
PURGE {NAME}_{d:INDEX}
.Ed
.Pp .Pp
The following functions operate on string expressions. The following functions operate on string expressions.
Most of them return a string, however some of these functions actually return an integer and can be used as part of an integer expression! Most of them return a string, however some of these functions actually return an integer and can be used as part of an integer expression!
.Bl -column "STRSUB(str, pos, len)" .Bl -column "STRSUB(str, pos, len)"
.It Sy Name Ta Sy Operation .It Sy Name Ta Sy Operation
.It Fn STRLEN string Ta Returns the number of characters in Ar string . .It Fn STRLEN str Ta Returns the number of characters in Ar str .
.It Fn STRCAT str1 str2 Ta Appends Ar str2 No to Ar str1 . .It Fn STRCAT strs... Ta Concatenates Ar strs .
.It Fn STRCMP str1 str2 Ta Returns -1 if Ar str1 No is alphabetically lower than Ar str2 No , zero if they match, 1 if Ar str1 No is greater than Ar str2 . .It Fn STRCMP str1 str2 Ta Returns -1 if Ar str1 No is alphabetically lower than Ar str2 No , zero if they match, 1 if Ar str1 No is greater than Ar str2 .
.It Fn STRIN str1 str2 Ta Returns the position of Ar str2 No in Ar str1 No or zero if it's not present Pq first character is position 1 . .It Fn STRIN str1 str2 Ta Returns the first position of Ar str2 No in Ar str1 No or zero if it's not present Pq first character is position 1 .
.It Fn STRRIN str1 str2 Ta Returns the last position of Ar str2 No in Ar str1 No or zero if it's not present Pq first character is position 1 .
.It Fn STRSUB str pos len Ta Returns a substring from Ar str No starting at Ar pos Po first character is position 1 Pc and Ar len No characters long. .It Fn STRSUB str pos len Ta Returns a substring from Ar str No starting at Ar pos Po first character is position 1 Pc and Ar len No characters long.
.It Fn STRUPR str Ta Converts all characters in Ar str No to capitals and returns the new string. .It Fn STRUPR str Ta Returns Ar str No with all letters in uppercase.
.It Fn STRLWR str Ta Converts all characters in Ar str No to lower case and returns the new string. .It Fn STRLWR str Ta Returns Ar str No with all letters in lowercase.
.It Fn STRRPL str old new Ta Returns Ar str No with each non-overlapping occurrence of the substring Ar old No replaced with Ar new .
.It Fn STRFMT fmt args... Ta Returns the string Ar fmt No with each
.Ql %spec
pattern replaced by interpolating the format
.Ar spec
with its corresponding argument in
.Ar args
.Pq So %% Sc is replaced by the So % Sc character .
.El .El
.Ss Character maps .Ss Character maps
When writing text that is meant to be displayed in the Game Boy, the characters used in the source code may have a different encoding than the default of ASCII. When writing text strings that are meant to be displayed on the Game Boy, the character encoding in the ROM may need to be different than the source file encoding.
For example, the tiles used for uppercase letters may be placed starting at tile index 128, which makes it difficult to add text strings to the ROM. For example, the tiles used for uppercase letters may be placed starting at tile index 128, which differs from ASCII starting at 65.
.Pp .Pp
Character maps allow mapping strings up to 16 characters long to an abitrary 8-bit value: Character maps allow mapping strings to arbitrary 8-bit values:
.Bd -literal -offset indent .Bd -literal -offset indent
CHARMAP "<LF>", 10 CHARMAP "<LF>", 10
CHARMAP "&iacute", 20 CHARMAP "&iacute", 20
CHARMAP "A", 128 CHARMAP "A", 128
.Ed .Ed
By default, a character map contains ASCII encoding. This would result in
.Ql db \(dqAmen<LF>\(dq
being equivalent to
.Ql db 128, 109, 101, 110, 10 .
.Pp
Any characters in a string without defined mappings will be copied directly, using the source file's encoding of characters to bytes.
.Pp .Pp
It is possible to create multiple character maps and then switch between them as desired. It is possible to create multiple character maps and then switch between them as desired.
This can be used to encode debug information in ASCII and use a different encoding for other purposes, for example. This can be used to encode debug information in ASCII and use a different encoding for other purposes, for example.
@@ -307,23 +415,18 @@ and it is automatically selected as the current character map from the beginning
There is also a character map stack that can be used to save and restore which character map is currently active. There is also a character map stack that can be used to save and restore which character map is currently active.
.Bl -column "NEWCHARMAP name, basename" .Bl -column "NEWCHARMAP name, basename"
.It Sy Command Ta Sy Meaning .It Sy Command Ta Sy Meaning
.It Ic NEWCHARMAP Ar name Ta Creates a new, empty character map called Ar name . .It Ic NEWCHARMAP Ar name Ta Creates a new, empty character map called Ar name No and switches to it.
.It Ic NEWCHARMAP Ar name , basename Ta Creates a new character map called Ar name , No copied from character map Ar basename . .It Ic NEWCHARMAP Ar name , basename Ta Creates a new character map called Ar name , No copied from character map Ar basename , No and switches to it.
.It Ic SETCHARMAP Ar name Ta Switch to character map Ar name . .It Ic SETCHARMAP Ar name Ta Switch to character map Ar name .
.It Ic PUSHC Ta Push the current character map onto the stack. .It Ic PUSHC Ta Push the current character map onto the stack.
.It Ic POPC Ta Pop a character map off the stack and switch to it. .It Ic POPC Ta Pop a character map off the stack and switch to it.
.El .El
.Pp .Pp
.Sy Note: .Sy Note:
Character maps affect all strings in the file from the point in which they are defined, until switching to a different character map. Modifications to a character map take effect immediately from that point onward.
This means that any string that the code may want to print as debug information will also be affected by it.
.Pp
.Sy Note:
The output value of a mapping can be 0.
If this happens, the assembler will treat this as the end of the string and the rest of it will be trimmed.
.Ss Other functions .Ss Other functions
There are a few other functions that do various useful things: There are a few other functions that do various useful things:
.Bl -column "DEF(label)" .Bl -column "DEF(symbol)"
.It Sy Name Ta Sy Operation .It Sy Name Ta Sy Operation
.It Fn BANK arg Ta Returns a bank number. .It Fn BANK arg Ta Returns a bank number.
If If
@@ -340,8 +443,8 @@ is a label, it returns the bank number the label is in.
The result may be constant if The result may be constant if
.Nm .Nm
is able to compute it. is able to compute it.
.It Fn DEF label Ta Returns TRUE (1) if .It Fn DEF symbol Ta Returns TRUE (1) if
.Ar label .Ar symbol
has been defined, FALSE (0) otherwise. has been defined, FALSE (0) otherwise.
String symbols are not expanded within the parentheses. String symbols are not expanded within the parentheses.
.It Fn HIGH arg Ta Returns the top 8 bits of the operand if Ar arg No is a label or constant, or the top 8-bit register if it is a 16-bit register. .It Fn HIGH arg Ta Returns the top 8 bits of the operand if Ar arg No is a label or constant, or the top 8-bit register if it is a 16-bit register.
@@ -623,6 +726,13 @@ The former is situated in ROM, where the code is stored, the latter in RAM, wher
You cannot nest You cannot nest
.Ic LOAD .Ic LOAD
blocks, nor can you change the current section within them. blocks, nor can you change the current section within them.
.Pp
.Ic LOAD
blocks can use the
.Ic UNION
or
.Ic FRAGMENT
modifiers, as described below.
.Ss Unionized Sections .Ss Unionized Sections
When you're tight on RAM, you may want to define overlapping blocks of variables, as explained in the When you're tight on RAM, you may want to define overlapping blocks of variables, as explained in the
.Sx Unions .Sx Unions
@@ -729,7 +839,7 @@ A block of
.Nm .Nm
code that can be invoked later. code that can be invoked later.
.It Sy String equate .It Sy String equate
String symbol that can be evaluated, similarly to a macro. A text string that can be expanded later, similarly to a macro.
.El .El
.Pp .Pp
Symbol names can contain letters, numbers, underscores Symbol names can contain letters, numbers, underscores
@@ -738,50 +848,89 @@ hashes
.Sq # .Sq #
and at signs and at signs
.Sq @ . .Sq @ .
However, they must begin with either a letter, or an underscore. However, they must begin with either a letter or an underscore.
Periods Periods
.Sq \&. .Sq \&.
are allowed exclusively for labels, as described below. are allowed exclusively in labels, as described below.
A symbol cannot have the same name as a reserved keyword. A symbol cannot have the same name as a reserved keyword.
.Em \&In the line where a symbol is defined there must not be any whitespace before it , .Pp
Constants and string equates
.Em must not
have any whitespace before their name when they are defined;
otherwise otherwise
.Nm .Nm
will treat it as a macro invocation. will treat them as a macro invocation.
Label and macro definitions may have whitespace before them, since a leading period or a following colon distinguishes them from invoking a macro.
.Bl -tag -width indent .Bl -tag -width indent
.It Sy Label declaration .It Sy Label declaration
One of the assembler's main tasks is to keep track of addresses for you, so you can work with meaningful names instead of "magic" numbers. One of the assembler's main tasks is to keep track of addresses for you, so you can work with meaningful names instead of "magic" numbers.
.Pp .Pp
This can be done in a number of ways: This can be done in a number of ways:
.Bd -literal -offset indent .Bd -literal -offset indent
GlobalLabel ;\ This syntax is deprecated, GlobalLabel:
AnotherGlobal: ;\ please use this instead AnotherGlobal:
\&.locallabel \&.locallabel
\&.yet_a_local: \&.another_local:
AnotherGlobal.with_another_local: AnotherGlobal.with_another_local:
ThisWillBeExported:: ;\ Note the two colons ThisWillBeExported:: ;\ Note the two colons
ThisWillBeExported.too:: ThisWillBeExported.too::
.Ed .Ed
.Pp .Pp
Declaring a label (global or local) with Any label whose name does not contain a period is a global label.
Declaring a global label sets it as the current scoped label, until the next global one.
Global labels must be followed by one or two colons.
.Pp
Any label whose name contains a single period is a local label.
Label names cannot contain more than one period.
If the period is the first character, it will have the current scoped label's name implicitly prepended.
Local labels may optionally be followed by one or two colons.
Local labels can be declared as
.Ql scoped.local
or simply as
.Ql .local .
If the former notation is used, then
.Ql scoped
must actually be the current scoped label.
.Pp
Declaring a label (global or local) with two colons
.Ql :: .Ql ::
does an will
.Ic EXPORT .Ic EXPORT
at the same time. and define it at the same time.
(See (See
.Sx Exporting and importing symbols .Sx Exporting and importing symbols
below). below).
.Pp .Pp
Any label whose name does not contain a period is a global label, others are locals. .Sy Anonymous labels
Declaring a global label sets it as the current label scope until the next one; any local label whose first character is a period will have the global label's name implicitly prepended. are useful for short blocks of code.
Local labels can be declared as They are defined like normal labels, but without a name before the colon.
.Ql scope.local: Anonymous labels are independent of label scoping, so defining one does not change the scoped label, and referencing one is not affected by the current scoped label.
or simply as as
.Ql .local: .
If the former notation is used, then
.Ql scope
must be the actual current scope.
.Pp .Pp
Local labels may have whitespace before their declaration as the only exception to the rule. Anonymous labels are referenced using a colon
.Ql \&:
followed by pluses
.Ql +
or minuses
.Ql - .
Thus
.Ic :+
references the next one after the expression,
.Ic :++
the one after that;
.Ic :-
references the one before the expression;
and so on.
.Bd -literal -offset indent
ld hl, :++
: ld a, [hli] ; referenced by "jr nz"
ldh [c], a
dec c
jr nz, :-
ret
: ; referenced by "ld hl"
dw $7FFF, $1061, $03E0, $58A5
.Ed
.Pp .Pp
A label's location (and thus value) is usually not determined until the linking stage, so labels usually cannot be used as constants. A label's location (and thus value) is usually not determined until the linking stage, so labels usually cannot be used as constants.
However, if the section in which the label is declared has a fixed base address, its value is known at assembly time. However, if the section in which the label is declared has a fixed base address, its value is known at assembly time.
@@ -809,7 +958,7 @@ or its synonym
.Ic = , .Ic = ,
defines constant symbols like defines constant symbols like
.Ic EQU , .Ic EQU ,
but those constants can be re-defined. but those constants can be redefined.
This is useful for variables in macros, for counters, etc. This is useful for variables in macros, for counters, etc.
.Bd -literal -offset indent .Bd -literal -offset indent
ARRAY_SIZE EQU 4 ARRAY_SIZE EQU 4
@@ -848,7 +997,6 @@ There are five commands in the RS group of commands:
.It Ic RB Ar constexpr Ta Sets the preceding symbol to Ic _RS No and adds Ar constexpr No to Ic _RS . .It Ic RB Ar constexpr Ta Sets the preceding symbol to Ic _RS No and adds Ar constexpr No to Ic _RS .
.It Ic RW Ar constexpr Ta Sets the preceding symbol to Ic _RS No and adds Ar constexpr No * 2 to Ic _RS . .It Ic RW Ar constexpr Ta Sets the preceding symbol to Ic _RS No and adds Ar constexpr No * 2 to Ic _RS .
.It Ic RL Ar constexpr Ta Sets the preceding symbol to Ic _RS No and adds Ar constexpr No * 4 to Ic _RS . .It Ic RL Ar constexpr Ta Sets the preceding symbol to Ic _RS No and adds Ar constexpr No * 4 to Ic _RS .
(In practice, this one cannot be used due to a bug).
.El .El
.Pp .Pp
If the argument to If the argument to
@@ -888,8 +1036,23 @@ pusha EQUS "push af\[rs]npush bc\[rs]npush de\[rs]npush hl\[rs]n"
Note that colons Note that colons
.Ql \&: .Ql \&:
following the name are not allowed. following the name are not allowed.
.Pp
String equates can't be exported or imported. String equates can't be exported or imported.
.Pp .Pp
String equates, like
.Ic EQU
constants, cannot be redefined.
However, the
.Ic REDEF
keyword will define or redefine a string symbol.
For example:
.Bd -literal -offset indent
s EQUS "Hello, "
REDEF s EQUS "{s}world!"
; prints "Hello, world!"
PRINTT "{s}\n"
.Ed
.Pp
.Sy Important note : .Sy Important note :
An An
.Ic EQUS .Ic EQUS
@@ -913,37 +1076,48 @@ Macros can be called with arguments, and can react depending on input using
.Ic IF .Ic IF
constructs. constructs.
.Bd -literal -offset indent .Bd -literal -offset indent
MyMacro: MACRO MACRO MyMacro
ld a,80 ld a, 80
call MyFunc call MyFunc
ENDM ENDM
.Ed .Ed
.Pp .Pp
Note that a single colon The example above defines
.Ql MyMacro
as a new macro.
You may use the older syntax
.Ql MyMacro: MACRO
instead of
.Ql MACRO MyMacro ,
with a single colon
.Ql \&: .Ql \&:
following the macro's name is required. following the macro's name.
Macros can't be exported or imported. Macros can't be exported or imported.
.Pp .Pp
Plainly nesting macro definitions is not allowed, but this can be worked around using Plainly nesting macro definitions is not allowed, but this can be worked around using
.Ic EQUS . .Ic EQUS .
This won't work: So this won't work:
.Bd -literal -offset indent .Bd -literal -offset indent
outer: MACRO MACRO outer
inner: MACRO MACRO inner
PRINTT "Hello!\[rs]n" PRINTLN "Hello!"
ENDM ENDM
ENDM ENDM
.Ed .Ed
.Pp .Pp
But this will: But this will:
.Bd -literal -offset indent .Bd -literal -offset indent
outer: MACRO MACRO outer
definition equs "inner: MACRO\[rs]nPRINTT \[rs]"Hello!\[rs]\[rs]n\[rs]"\[rs]nENDM" definition EQUS "MACRO inner\[rs]nPRINTLN \[rs]"Hello!\[rs]"\[rs]nENDM"
definition definition
PURGE definition PURGE definition
ENDM ENDM
.Ed .Ed
.El .El
.Pp
Macro arguments support all the escape sequences of strings, as well as
.Ql \[rs],
to escape commas, since those otherwise separate arguments.
.Ss Exporting and importing symbols .Ss Exporting and importing symbols
Importing and exporting of symbols is a feature that is very useful when your project spans many source files and, for example, you need to jump to a routine defined in another file. Importing and exporting of symbols is a feature that is very useful when your project spans many source files and, for example, you need to jump to a routine defined in another file.
.Pp .Pp
@@ -1001,10 +1175,6 @@ Linking failed with 1 error
Note also that only exported symbols will appear in symbol and map files produced by Note also that only exported symbols will appear in symbol and map files produced by
.Xr rgblink 1 . .Xr rgblink 1 .
.Pp .Pp
.Ic GLOBAL
is a deprecated synonym for
.Ic EXPORT ,
do not use it.
.Ss Purging symbols .Ss Purging symbols
.Ic PURGE .Ic PURGE
allows you to completely remove a symbol from the symbol table as if it had never existed. allows you to completely remove a symbol from the symbol table as if it had never existed.
@@ -1026,27 +1196,33 @@ command
.Ss Predeclared Symbols .Ss Predeclared Symbols
The following symbols are defined by the assembler: The following symbols are defined by the assembler:
.Bl -column -offset indent "EQUS" "__ISO_8601_LOCAL__" .Bl -column -offset indent "EQUS" "__ISO_8601_LOCAL__"
.It Sy Type Ta Sy Name Ta Sy Contents .It Sy Name Ta Sy Type Ta Sy Contents
.It Ic EQU Ta Dv @ Ta PC value (essentially, the current memory address) .It Dv @ Ta Ic EQU Ta PC value (essentially, the current memory address)
.It Ic EQU Ta Dv _PI Ta Fixed point \[*p] .It Dv _RS Ta Ic SET Ta _RS Counter
.It Ic SET Ta Dv _RS Ta _RS Counter .It Dv _NARG Ta Ic EQU Ta Number of arguments passed to macro, updated by Ic SHIFT
.It Ic EQU Ta Dv _NARG Ta Number of arguments passed to macro, updated by Ic SHIFT .It Dv __LINE__ Ta Ic EQU Ta The current line number
.It Ic EQU Ta Dv __LINE__ Ta The current line number .It Dv __FILE__ Ta Ic EQUS Ta The current filename
.It Ic EQUS Ta Dv __FILE__ Ta The current filename .It Dv __DATE__ Ta Ic EQUS Ta Today's date
.It Ic EQUS Ta Dv __DATE__ Ta Today's date .It Dv __TIME__ Ta Ic EQUS Ta The current time
.It Ic EQUS Ta Dv __TIME__ Ta The current time .It Dv __ISO_8601_LOCAL__ Ta Ic EQUS Ta ISO 8601 timestamp (local)
.It Ic EQUS Ta Dv __ISO_8601_LOCAL__ Ta ISO 8601 timestamp (local) .It Dv __ISO_8601_UTC__ Ta Ic EQUS Ta ISO 8601 timestamp (UTC)
.It Ic EQUS Ta Dv __ISO_8601_UTC__ Ta ISO 8601 timestamp (UTC) .It Dv __UTC_YEAR__ Ta Ic EQU Ta Today's year
.It Ic EQU Ta Dv __UTC_YEAR__ Ta Today's year .It Dv __UTC_MONTH__ Ta Ic EQU Ta Today's month number, 1\[en]12
.It Ic EQU Ta Dv __UTC_MONTH__ Ta Today's month number, 1\[en]12 .It Dv __UTC_DAY__ Ta Ic EQU Ta Today's day of the month, 1\[en]31
.It Ic EQU Ta Dv __UTC_DAY__ Ta Today's day of the month, 1\[en]31 .It Dv __UTC_HOUR__ Ta Ic EQU Ta Current hour, 0\[en]23
.It Ic EQU Ta Dv __UTC_HOUR__ Ta Current hour, 0\[en]23 .It Dv __UTC_MINUTE__ Ta Ic EQU Ta Current minute, 0\[en]59
.It Ic EQU Ta Dv __UTC_MINUTE__ Ta Current minute, 0\[en]59 .It Dv __UTC_SECOND__ Ta Ic EQU Ta Current second, 0\[en]59
.It Ic EQU Ta Dv __UTC_SECOND__ Ta Current second, 0\[en]59 .It Dv __RGBDS_MAJOR__ Ta Ic EQU Ta Major version number of RGBDS
.It Ic EQU Ta Dv __RGBDS_MAJOR__ Ta Major version number of RGBDS .It Dv __RGBDS_MINOR__ Ta Ic EQU Ta Minor version number of RGBDS
.It Ic EQU Ta Dv __RGBDS_MINOR__ Ta Minor version number of RGBDS .It Dv __RGBDS_PATCH__ Ta Ic EQU Ta Patch version number of RGBDS
.It Ic EQU Ta Dv __RGBDS_PATCH__ Ta Patch version number of RGBDS .It Dv __RGBDS_RC__ Ta Ic EQU Ta Release candidate ID of RGBDS, not defined for final releases
.El .El
.Pp
The current time values will be taken from the
.Dv SOURCE_DATE_EPOCH
environment variable if that is defined as a UNIX timestamp.
Refer to the spec at
.Lk https://reproducible-builds.org/docs/source-date-epoch/ .
.Sh DEFINING DATA .Sh DEFINING DATA
.Ss Declaring variables in a RAM section .Ss Declaring variables in a RAM section
.Ic DS .Ic DS
@@ -1072,33 +1248,42 @@ command-line option, except when using overlays with
.Ic DB .Ic DB
defines a list of bytes that will be stored in the final image. defines a list of bytes that will be stored in the final image.
Ideal for tables and text. Ideal for tables and text.
Note that strings are not zero-terminated!
.Bd -literal -offset indent .Bd -literal -offset indent
DB 1,2,3,4,"This is a string" DB 1,2,3,4,"This is a string"
.Ed .Ed
.Pp .Pp
.Ic DS
can also be used to fill a region of memory with some value.
The following produces 42 times the byte $FF:
.Bd -literal -offset indent
DS 42, $FF
.Ed
.Pp
Alternatively, you can use Alternatively, you can use
.Ic DW .Ic DW
to store a list of words (16-bit) or to store a list of words (16-bit) or
.Ic DL .Ic DL
to store a list of double-words/longs (32-bit). to store a list of double-words/longs (32-bit).
Strings are not allowed as arguments to .Pp
.Ic DW Strings are handled a little specially: they first undergo charmap conversion (see
and .Sx Character maps ) ,
.Ic DL . then each resulting character is output individually.
For example, under the default charmap, the following two lines are identical:
.Bd -literal -offset indent
DW "Hello!"
DW "H", "e", "l", "l", "o", "!"
.Ed
.Pp
If you do not want this special handling, enclose the string in parentheses.
.Pp
.Ic DS
can also be used to fill a region of memory with some repeated values.
For example:
.Bd -literal -offset indent
; outputs 3 bytes: $AA, $AA, $AA
DS 3, $AA
; outputs 7 bytes: $BB, $CC, $BB, $CC, $BB, $CC, $BB
DS 7, $BB, $CC
.Ed
.Pp .Pp
You can also use You can also use
.Ic DB , DW .Ic DB , DW
and and
.Ic DL .Ic DL
without arguments, or leaving empty elements at any point in the list. without arguments.
This works exactly like This works exactly like
.Ic DS 1 , DS 2 .Ic DS 1 , DS 2
and and
@@ -1216,7 +1401,7 @@ it will insert the macro definition (the code enclosed in
.Pp .Pp
Suppose your macro contains a loop. Suppose your macro contains a loop.
.Bd -literal -offset indent .Bd -literal -offset indent
LoopyMacro: MACRO MACRO LoopyMacro
xor a,a xor a,a
\&.loop ld [hl+],a \&.loop ld [hl+],a
dec c dec c
@@ -1234,7 +1419,7 @@ also works in
.Ic REPT .Ic REPT
blocks. blocks.
.Bd -literal -offset indent .Bd -literal -offset indent
LoopyMacro: MACRO MACRO LoopyMacro
xor a,a xor a,a
\&.loop\[rs]@ ld [hl+],a \&.loop\[rs]@ ld [hl+],a
dec c dec c
@@ -1263,7 +1448,7 @@ through
.Ic \[rs]9 , \[rs]1 .Ic \[rs]9 , \[rs]1
being the first argument specified on the macro invocation. being the first argument specified on the macro invocation.
.Bd -literal -offset indent .Bd -literal -offset indent
LoopyMacro: MACRO MACRO LoopyMacro
ld hl,\[rs]1 ld hl,\[rs]1
ld c,\[rs]2 ld c,\[rs]2
xor a,a xor a,a
@@ -1288,36 +1473,33 @@ to
if you perform further calculations on them. if you perform further calculations on them.
For instance, consider the following: For instance, consider the following:
.Bd -literal -offset indent .Bd -literal -offset indent
print_double: MACRO MACRO print_double
PRINTV \[rs]1 * 2 PRINTLN \[rs]1 * 2
ENDM ENDM
print_double 1 + 2 print_double 1 + 2
.Ed .Ed
.Pp .Pp
The The
.Ic PRINTV .Ic PRINTLN
statement will expand to statement will expand to
.Ql PRINTV 1 + 2 * 2 , .Ql PRINTLN 1 + 2 * 2 ,
which will print 5 and not 6 as you might have expected. which will print 5 and not 6 as you might have expected.
.Pp .Pp
Line continuations work as usual inside macros or lists of macro arguments. Line continuations work as usual inside macros or lists of macro arguments.
However, some characters need to be escaped, as in the following example: However, some characters need to be escaped, as in the following example:
.Bd -literal -offset indent .Bd -literal -offset indent
PrintMacro: MACRO MACRO PrintMacro
PRINTT \[rs]1 PRINT \[rs]1
ENDM ENDM
PrintMacro STRCAT("Hello "\[rs], \[rs] PrintMacro STRCAT("Hello "\[rs], \[rs]
"world\[rs]\[rs]n") "world\[rs]n")
.Ed .Ed
.Pp .Pp
The comma needs to be escaped to avoid it being treated as separating the macro's arguments. The comma needs to be escaped to avoid it being treated as separating the macro's arguments.
The backslash The backslash in
.Sq \[rs] .Ql \[rs]n
.Pq from Sq \[rs]n does not need to be escaped because string literals also work as usual inside macro arguments.
also needs to be escaped because of the way
.Nm
processes macro arguments.
.Pp .Pp
In reality, up to 256 arguments can be passed to a macro, but you can only use the first 9 like this. In reality, up to 256 arguments can be passed to a macro, but you can only use the first 9 like this.
If you want to use the rest, you need to use the If you want to use the rest, you need to use the
@@ -1343,32 +1525,33 @@ This is the only way of accessing the value of arguments from 10 to 256.
.Pp .Pp
.Ic SHIFT .Ic SHIFT
can optionally be given an integer parameter, and will apply the above shifting that number of times. can optionally be given an integer parameter, and will apply the above shifting that number of times.
A negative parameter will shift the arguments in reverse.
.Ss Printing things during assembly .Ss Printing things during assembly
The next four commands print text and values to the standard output. The
.Ic PRINT
and
.Ic PRINTLN
commands print text and values to the standard output.
Useful for debugging macros, or wherever you may feel the need to tell yourself some important information. Useful for debugging macros, or wherever you may feel the need to tell yourself some important information.
.Bd -literal -offset indent .Bd -literal -offset indent
PRINTT "I'm the greatest programmer in the whole wide world\[rs]n" PRINT "Hello world!\[rs]n"
PRINTI (2 + 3) / 5 PRINTLN "Hello world!"
PRINTV $FF00 + $F0 PRINT _NARG, " arguments\[rs]n"
PRINTF MUL(3.14, 3987.0) PRINTLN "sum: ", 2+3, " product: ", 2*3
PRINTLN "Line #", __LINE__
PRINTLN STRFMT("E = %f", 2.718)
.Ed .Ed
.Bl -inset .Bl -inset
.It Ic PRINTT .It Ic PRINT
prints out a string. prints out each of its comma-separated arguments.
Be careful to add a line feed Numbers are printed as unsigned uppercase hexadecimal with a leading
.Pq Qq \[rs]n .Ic $ .
at the end, as it is not added automatically. For different formats, use
.It Ic PRINTV .Ic STRFMT .
prints out an integer value in hexadecimal or, as in the example, the result of a calculation. .It Ic PRINTLN
Unsurprisingly, you can also print out a constant symbol's value. prints out each of its comma-separated arguments, if any, followed by a line feed
.It Ic PRINTI .Pq Ql \[rs]n .
prints out a signed integer value.
.It Ic PRINTF
prints out a fixed point value.
.El .El
.Pp
Be careful that none of those automatically print a line feed; if you need one, use
.Ic PRINTT "\[rs]n" .
.Ss Automatically repeating blocks of code .Ss Automatically repeating blocks of code
Suppose you want to unroll a time consuming loop without copy-pasting it. Suppose you want to unroll a time consuming loop without copy-pasting it.
.Ic REPT .Ic REPT
@@ -1391,20 +1574,115 @@ You can also use
.Ic REPT .Ic REPT
to generate tables on the fly: to generate tables on the fly:
.Bd -literal -offset indent .Bd -literal -offset indent
;\ -- ; Generate a 256-byte sine table with values in the range [0, 128]
;\ -- Generate a 256 byte sine table with values between 0 and 128 ; (shifted and scaled from the range [-1.0, 1.0])
;\ -- ANGLE = 0.0
ANGLE = 0.0 REPT 256
REPT 256 db (MUL(64.0, SIN(ANGLE)) + 64.0) >> 16
db (MUL(64.0, SIN(ANGLE)) + 64.0) >> 16 ANGLE = ANGLE + 256.0 ; 256.0 = 65536 degrees / 256 entries
ANGLE = ANGLE+256.0 ENDR
ENDR
.Ed .Ed
.Pp .Pp
As in macros, you can also use the escape sequence As in macros, you can also use the escape sequence
.Ic \[rs]@ . .Ic \[rs]@ .
.Ic REPT .Ic REPT
blocks can be nested. blocks can be nested.
.Pp
A common pattern is to repeat a block for each value in some range.
.Ic FOR
is simpler than
.Ic REPT
for that purpose.
Everything between
.Ic FOR
and the matching
.Ic ENDR
will be repeated for each value of a given symbol.
For example, this code will produce a table of squared values from 0 to 255:
.Bd -literal -offset indent
FOR N, 256
dw N * N
ENDR
.Ed
.Pp
It acts just as if you had done:
.Bd -literal -offset ident
N = 0
dw N * N
N = 1
dw N * N
N = 2
dw N * N
; ...
N = 255
dw N * N
N = 256
.Ed
.Pp
You can customize the range of
.Ic FOR
values:
.Bl -column "FOR V, start, stop, step"
.It Sy Code Ta Sy Range
.It Ic FOR Ar V , stop Ta Ar V No increments from 0 to Ar stop No
.It Ic FOR Ar V , start , stop Ta Ar V No increments from Ar start No to Ar stop No
.It Ic FOR Ar V , start , stop , step Ta Ar V No goes from Ar start No to Ar stop No by Ar step No
.El
.Pp
The
.Ic FOR
value will be updated by
.Ar step
until it reaches or exceeds
.Ar stop.
For example:
.Bd -literal -offset indent
FOR V, 4, 25, 5
PRINT "{d:V} "
ENDR
PRINTLN "done {d:V}"
.Ed
This will print:
.Bd -literal -offset indent
4 9 14 19 24 done 29
.Ed
.Pp
Just like with
.Ic REPT
blocks, you can use the escape sequence
.Ic \[rs]@
inside of
.Ic FOR
blocks, and they can be nested.
.Pp
You can stop a repeating block with the
.Ic BREAK
command.
A
.Ic BREAK
inside of a
.Ic REPT
or
.Ic FOR
block will interrupt the current iteration and not repeat any more.
It will continue running code after the block's
.Ic ENDR .
For example:
.Bd -literal -offset indent
FOR V, 1, 100
PRINT "{d:V}"
IF V == 5
PRINT " stop! "
BREAK
ENDC
PRINT ", "
ENDR
PRINTLN "done {d:V}"
.Ed
This will print:
.Bd -literal -offset indent
1, 2, 3, 4, 5 stop! done 5
.Ed
.Ss Aborting the assembly process .Ss Aborting the assembly process
.Ic FAIL .Ic FAIL
and and
@@ -1501,11 +1779,11 @@ skip over parts of your code depending on a condition.
This is a powerful feature commonly used in macros. This is a powerful feature commonly used in macros.
.Bd -literal -offset indent .Bd -literal -offset indent
IF NUM < 0 IF NUM < 0
PRINTT "NUM < 0\[rs]n" PRINTLN "NUM < 0"
ELIF NUM == 0 ELIF NUM == 0
PRINTT "NUM == 0\[rs]n" PRINTLN "NUM == 0"
ELSE ELSE
PRINTT "NUM > 0\[rs]n" PRINTLN "NUM > 0"
ENDC ENDC
.Ed .Ed
.Pp .Pp

View File

@@ -15,15 +15,18 @@
#include <inttypes.h> #include <inttypes.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include "asm/asm.h"
#include "asm/main.h" #include "asm/main.h"
#include "asm/output.h"
#include "asm/rpn.h" #include "asm/rpn.h"
#include "asm/section.h" #include "asm/section.h"
#include "asm/symbol.h" #include "asm/symbol.h"
#include "asm/warning.h" #include "asm/warning.h"
#include "opmath.h"
/* Makes an expression "not known", also setting its error message */ /* Makes an expression "not known", also setting its error message */
#define makeUnknown(expr_, ...) do { \ #define makeUnknown(expr_, ...) do { \
struct Expression *_expr = expr_; \ struct Expression *_expr = expr_; \
@@ -124,16 +127,6 @@ void rpn_Symbol(struct Expression *expr, char const *tzSym)
uint8_t *ptr = reserveSpace(expr, nameLen + 1); uint8_t *ptr = reserveSpace(expr, nameLen + 1);
*ptr++ = RPN_SYM; *ptr++ = RPN_SYM;
memcpy(ptr, sym->name, nameLen); memcpy(ptr, sym->name, nameLen);
/* RGBLINK assumes PC is at the byte being computed... */
if (sym_IsPC(sym) && nPCOffset) {
struct Expression pc = *expr, offset;
rpn_Number(&offset, nPCOffset);
rpn_BinaryOp(RPN_SUB, expr, &pc, &offset);
if (!rpn_isKnown(expr))
expr->isSymbol = true;
}
} else { } else {
rpn_Number(expr, sym_GetConstantValue(tzSym)); rpn_Number(expr, sym_GetConstantValue(tzSym));
} }
@@ -253,45 +246,6 @@ void rpn_LOGNOT(struct Expression *expr, const struct Expression *src)
} }
} }
static int32_t shift(int32_t shiftee, int32_t amount)
{
if (amount >= 0) {
// Left shift
if (amount >= 32) {
warning(WARNING_SHIFT_AMOUNT, "Shifting left by large amount %"
PRId32 "\n", amount);
return 0;
} else {
/*
* Use unsigned to force a bitwise shift
* Casting back is OK because the types implement two's
* complement behavior
*/
return (uint32_t)shiftee << amount;
}
} else {
// Right shift
amount = -amount;
if (amount >= 32) {
warning(WARNING_SHIFT_AMOUNT,
"Shifting right by large amount %" PRId32 "\n", amount);
return shiftee < 0 ? -1 : 0;
} else if (shiftee >= 0) {
return shiftee >> amount;
} else {
/*
* The C standard leaves shifting right negative values
* undefined, so use a left shift manually sign-extended
*/
return (uint32_t)shiftee >> amount
| -(UINT32_C(1) << (32 - amount));
}
}
}
struct Symbol const *rpn_SymbolOf(struct Expression const *expr) struct Symbol const *rpn_SymbolOf(struct Expression const *expr)
{ {
if (!rpn_isSymbol(expr)) if (!rpn_isSymbol(expr))
@@ -377,11 +331,17 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
"Shifting left by negative amount %" PRId32 "\n", "Shifting left by negative amount %" PRId32 "\n",
src2->nVal); src2->nVal);
expr->nVal = shift(src1->nVal, src2->nVal); if (src2->nVal >= 32)
warning(WARNING_SHIFT_AMOUNT,
"Shifting left by large amount %" PRId32 "\n",
src2->nVal);
expr->nVal = op_shift_left(src1->nVal, src2->nVal);
break; break;
case RPN_SHR: case RPN_SHR:
if (src1->nVal < 0) if (src1->nVal < 0)
warning(WARNING_SHIFT, "Shifting negative value %" PRId32 "\n", warning(WARNING_SHIFT, "Shifting right negative value %"
PRId32 "\n",
src1->nVal); src1->nVal);
if (src2->nVal < 0) if (src2->nVal < 0)
@@ -389,7 +349,12 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
"Shifting right by negative amount %" PRId32 "\n", "Shifting right by negative amount %" PRId32 "\n",
src2->nVal); src2->nVal);
expr->nVal = shift(src1->nVal, -src2->nVal); if (src2->nVal >= 32)
warning(WARNING_SHIFT_AMOUNT,
"Shifting right by large amount %" PRId32 "\n",
src2->nVal);
expr->nVal = op_shift_right(src1->nVal, src2->nVal);
break; break;
case RPN_MUL: case RPN_MUL:
expr->nVal = uleft * uright; expr->nVal = uleft * uright;
@@ -403,17 +368,26 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
PRId32 "\n", INT32_MIN, INT32_MIN); PRId32 "\n", INT32_MIN, INT32_MIN);
expr->nVal = INT32_MIN; expr->nVal = INT32_MIN;
} else { } else {
expr->nVal = src1->nVal / src2->nVal; expr->nVal = op_divide(src1->nVal, src2->nVal);
} }
break; break;
case RPN_MOD: case RPN_MOD:
if (src2->nVal == 0) if (src2->nVal == 0)
fatalerror("Division by zero\n"); fatalerror("Modulo by zero\n");
if (src1->nVal == INT32_MIN && src2->nVal == -1) if (src1->nVal == INT32_MIN && src2->nVal == -1)
expr->nVal = 0; expr->nVal = 0;
else else
expr->nVal = src1->nVal % src2->nVal; expr->nVal = op_modulo(src1->nVal, src2->nVal);
break;
case RPN_EXP:
if (src2->nVal < 0)
fatalerror("Exponentiation by negative power\n");
if (src1->nVal == INT32_MIN && src2->nVal == -1)
expr->nVal = 0;
else
expr->nVal = op_exponent(src1->nVal, src2->nVal);
break; break;
case RPN_UNSUB: case RPN_UNSUB:

View File

@@ -1,4 +1,5 @@
#include <assert.h>
#include <errno.h> #include <errno.h>
#include <inttypes.h> #include <inttypes.h>
#include <stdbool.h> #include <stdbool.h>
@@ -11,11 +12,14 @@
#include "asm/output.h" #include "asm/output.h"
#include "asm/rpn.h" #include "asm/rpn.h"
#include "asm/section.h" #include "asm/section.h"
#include "asm/symbol.h"
#include "asm/warning.h" #include "asm/warning.h"
#include "extern/err.h" #include "extern/err.h"
#include "platform.h" // strdup #include "platform.h" // strdup
uint8_t fillByte;
struct SectionStackEntry { struct SectionStackEntry {
struct Section *section; struct Section *section;
char const *scope; /* Section's symbol scope */ char const *scope; /* Section's symbol scope */
@@ -26,7 +30,7 @@ struct SectionStackEntry {
struct SectionStackEntry *sectionStack; struct SectionStackEntry *sectionStack;
uint32_t curOffset; /* Offset into the current section (see sect_GetSymbolOffset) */ uint32_t curOffset; /* Offset into the current section (see sect_GetSymbolOffset) */
static struct Section *currentLoadSection = NULL; static struct Section *currentLoadSection = NULL;
uint32_t loadOffset; /* The offset of the LOAD section within its parent */ int32_t loadOffset; /* Offset into the LOAD section's parent (see sect_GetOutputOffset) */
struct UnionStackEntry { struct UnionStackEntry {
uint32_t start; uint32_t start;
@@ -83,48 +87,247 @@ static inline void reserveSpace(uint32_t delta_size)
struct Section *out_FindSectionByName(const char *name) struct Section *out_FindSectionByName(const char *name)
{ {
struct Section *sect = pSectionList; for (struct Section *sect = pSectionList; sect; sect = sect->next) {
while (sect) {
if (strcmp(name, sect->name) == 0) if (strcmp(name, sect->name) == 0)
return sect; return sect;
sect = sect->next;
} }
return NULL; return NULL;
} }
/* #define mask(align) ((1U << (align)) - 1)
* Find a section by name and type. If it doesn't exist, create it #define fail(...) \
*/ do { \
static struct Section *getSection(char const *name, enum SectionType type, error(__VA_ARGS__); \
uint32_t org, struct SectionSpec const *attrs, nbSectErrors++; \
enum SectionModifier mod) } while (0)
static unsigned int mergeSectUnion(struct Section *sect, enum SectionType type, uint32_t org,
uint8_t alignment, uint16_t alignOffset)
{
assert(alignment < 16); // Should be ensured by the caller
unsigned int nbSectErrors = 0;
/*
* Unionized sections only need "compatible" constraints, and they end up with the strictest
* combination of both.
*/
if (sect_HasData(type))
fail("Cannot declare ROM sections as UNION\n");
if (org != -1) {
/* If both are fixed, they must be the same */
if (sect->org != -1 && sect->org != org)
fail("Section already declared as fixed at different address $%04"
PRIx32 "\n", sect->org);
else if (sect->align != 0 && (mask(sect->align) & (org - sect->alignOfs)))
fail("Section already declared as aligned to %u bytes (offset %"
PRIu16 ")\n", 1U << sect->align, sect->alignOfs);
else
/* Otherwise, just override */
sect->org = org;
} else if (alignment != 0) {
/* Make sure any fixed address given is compatible */
if (sect->org != -1) {
if ((sect->org - alignOffset) & mask(alignment))
fail("Section already declared as fixed at incompatible address $%04"
PRIx32 "\n", sect->org);
/* Check if alignment offsets are compatible */
} else if ((alignOffset & mask(sect->align))
!= (sect->alignOfs & mask(alignment))) {
fail("Section already declared with incompatible %" PRIu8
"-byte alignment (offset %" PRIu16 ")\n",
sect->align, sect->alignOfs);
} else if (alignment > sect->align) {
// If the section is not fixed, its alignment is the largest of both
sect->align = alignment;
sect->alignOfs = alignOffset;
}
}
return nbSectErrors;
}
static unsigned int mergeFragments(struct Section *sect, enum SectionType type, uint32_t org,
uint8_t alignment, uint16_t alignOffset)
{
(void)type;
assert(alignment < 16); // Should be ensured by the caller
unsigned int nbSectErrors = 0;
/*
* Fragments only need "compatible" constraints, and they end up with the strictest
* combination of both.
* The merging is however performed at the *end* of the original section!
*/
if (org != -1) {
uint16_t curOrg = org - sect->size;
/* If both are fixed, they must be the same */
if (sect->org != -1 && sect->org != curOrg)
fail("Section already declared as fixed at incompatible address $%04"
PRIx32 " (cur addr = %04" PRIx32 ")\n",
sect->org, sect->org + sect->size);
else if (sect->align != 0 && (mask(sect->align) & (curOrg - sect->alignOfs)))
fail("Section already declared as aligned to %u bytes (offset %"
PRIu16 ")\n", 1U << sect->align, sect->alignOfs);
else
/* Otherwise, just override */
sect->org = curOrg;
} else if (alignment != 0) {
int32_t curOfs = (alignOffset - sect->size) % (1U << alignment);
if (curOfs < 0)
curOfs += 1U << alignment;
/* Make sure any fixed address given is compatible */
if (sect->org != -1) {
if ((sect->org - curOfs) & mask(alignment))
fail("Section already declared as fixed at incompatible address $%04"
PRIx32 "\n", sect->org);
/* Check if alignment offsets are compatible */
} else if ((curOfs & mask(sect->align)) != (sect->alignOfs & mask(alignment))) {
fail("Section already declared with incompatible %" PRIu8
"-byte alignment (offset %" PRIu16 ")\n",
sect->align, sect->alignOfs);
} else if (alignment > sect->align) {
// If the section is not fixed, its alignment is the largest of both
sect->align = alignment;
sect->alignOfs = curOfs;
}
}
return nbSectErrors;
}
static void mergeSections(struct Section *sect, enum SectionType type, uint32_t org, uint32_t bank,
uint8_t alignment, uint16_t alignOffset, enum SectionModifier mod)
{
unsigned int nbSectErrors = 0;
if (type != sect->type)
fail("Section already exists but with type %s\n", typeNames[sect->type]);
if (sect->modifier != mod) {
fail("Section already declared as %s section\n", sectionModNames[sect->modifier]);
} else {
switch (mod) {
case SECTION_UNION:
case SECTION_FRAGMENT:
nbSectErrors += (mod == SECTION_UNION ? mergeSectUnion : mergeFragments)
(sect, type, org, alignment, alignOffset);
// Common checks
/* If the section's bank is unspecified, override it */
if (sect->bank == -1)
sect->bank = bank;
/* If both specify a bank, it must be the same one */
else if (bank != -1 && sect->bank != bank)
fail("Section already declared with different bank %" PRIu32 "\n",
sect->bank);
break;
case SECTION_NORMAL:
fail("Section already defined previously at ");
fstk_Dump(sect->src, sect->fileLine);
putc('\n', stderr);
break;
}
}
if (nbSectErrors)
fatalerror("Cannot create section \"%s\" (%u error%s)\n",
sect->name, nbSectErrors, nbSectErrors == 1 ? "" : "s");
}
#undef fail
/*
* Create a new section, not yet in the list.
*/
static struct Section *createSection(char const *name, enum SectionType type,
uint32_t org, uint32_t bank, uint8_t alignment,
uint16_t alignOffset, enum SectionModifier mod)
{
struct Section *sect = malloc(sizeof(*sect));
if (sect == NULL)
fatalerror("Not enough memory for section: %s\n", strerror(errno));
sect->name = strdup(name);
if (sect->name == NULL)
fatalerror("Not enough memory for section name: %s\n", strerror(errno));
sect->type = type;
sect->modifier = mod;
sect->src = fstk_GetFileStack();
sect->fileLine = lexer_GetLineNo();
sect->size = 0;
sect->org = org;
sect->bank = bank;
sect->align = alignment;
sect->alignOfs = alignOffset;
sect->next = NULL;
sect->patches = NULL;
/* It is only needed to allocate memory for ROM sections. */
if (sect_HasData(type)) {
sect->data = malloc(maxsize[type]);
if (sect->data == NULL)
fatalerror("Not enough memory for section: %s\n", strerror(errno));
} else {
sect->data = NULL;
}
return sect;
}
/*
* Find a section by name and type. If it doesn't exist, create it.
*/
static struct Section *getSection(char const *name, enum SectionType type, uint32_t org,
struct SectionSpec const *attrs, enum SectionModifier mod)
{ {
#define mask(align) ((1 << (align)) - 1)
uint32_t bank = attrs->bank; uint32_t bank = attrs->bank;
uint8_t alignment = attrs->alignment; uint8_t alignment = attrs->alignment;
uint16_t alignOffset = attrs->alignOfs; uint16_t alignOffset = attrs->alignOfs;
// First, validate parameters, and normalize them if applicable
if (bank != -1) { if (bank != -1) {
if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM
&& type != SECTTYPE_SRAM && type != SECTTYPE_WRAMX) && type != SECTTYPE_SRAM && type != SECTTYPE_WRAMX)
error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n"); error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections\n");
else if (bank < bankranges[type][0] else if (bank < bankranges[type][0]
|| bank > bankranges[type][1]) || bank > bankranges[type][1])
error("%s bank value $%" PRIx32 " out of range ($%" PRIx32 " to $%" error("%s bank value $%04" PRIx32 " out of range ($%04" PRIx32 " to $%04"
PRIx32 ")\n", typeNames[type], bank, PRIx32 ")\n", typeNames[type], bank,
bankranges[type][0], bankranges[type][1]); bankranges[type][0], bankranges[type][1]);
} else if (nbbanks(type) == 1) {
// If the section type only has a single bank, implicitly force it
bank = bankranges[type][0];
} }
if (alignOffset >= 1 << alignment) { if (alignOffset >= 1 << alignment) {
error("Alignment offset must not be greater than alignment (%" PRIu16 " < %u)\n", error("Alignment offset (%" PRIu16 ") must be smaller than alignment size (%u)\n",
alignOffset, 1U << alignment); alignOffset, 1U << alignment);
alignOffset = 0; alignOffset = 0;
} }
if (org != -1) {
if (org < startaddr[type] || org > endaddr(type))
error("Section \"%s\"'s fixed address %#" PRIx32
" is outside of range [%#" PRIx16 "; %#" PRIx16 "]\n",
name, org, startaddr[type], endaddr(type));
}
if (alignment != 0) { if (alignment != 0) {
if (alignment > 16) {
error("Alignment must be between 0 and 16, not %u\n", alignment);
alignment = 16;
}
/* It doesn't make sense to have both alignment and org set */ /* It doesn't make sense to have both alignment and org set */
uint32_t mask = mask(alignment); uint32_t mask = mask(alignment);
@@ -136,169 +339,28 @@ static struct Section *getSection(char const *name, enum SectionType type,
} else if (startaddr[type] & mask) { } else if (startaddr[type] & mask) {
error("Section \"%s\"'s alignment cannot be attained in %s\n", error("Section \"%s\"'s alignment cannot be attained in %s\n",
name, typeNames[type]); name, typeNames[type]);
} else if (alignment == 16) {
// Treat an alignment of 16 as being fixed at address 0
alignment = 0;
org = 0;
// The address is known to be valid, since the alignment is
} }
} }
if (org != -1) { // Check if another section exists with the same name; merge if yes, otherwise create one
if (org < startaddr[type] || org > endaddr(type))
error("Section \"%s\"'s fixed address %#" PRIx32
" is outside of range [%#" PRIx16 "; %#" PRIx16 "]\n",
name, org, startaddr[type], endaddr(type));
}
if (nbbanks(type) == 1)
bank = bankranges[type][0];
struct Section *sect = out_FindSectionByName(name); struct Section *sect = out_FindSectionByName(name);
if (sect) { if (sect) {
unsigned int nbSectErrors = 0; mergeSections(sect, type, org, bank, alignment, alignOffset, mod);
#define fail(...) \
do { \
error(__VA_ARGS__); \
nbSectErrors++; \
} while (0)
if (type != sect->type)
fail("Section \"%s\" already exists but with type %s\n",
sect->name, typeNames[sect->type]);
if (sect->modifier != mod)
fail("Section \"%s\" already declared as %s section\n",
sect->name, sectionModNames[sect->modifier]);
/*
* Normal sections need to have exactly identical constraints;
* but unionized sections only need "compatible" constraints,
* and they end up with the strictest combination of both
*/
if (mod == SECTION_UNION) {
/*
* WARNING: see comment about assumption in
* `EndLoadSection` if modifying the following check!
*/
if (sect_HasData(type))
fail("Cannot declare ROM sections as UNION\n");
if (org != -1) {
/* If both are fixed, they must be the same */
if (sect->org != -1 && sect->org != org)
fail("Section \"%s\" already declared as fixed at different address $%"
PRIx32 "\n",
sect->name, sect->org);
else if (sect->align != 0
&& (mask(sect->align)
& (org - sect->alignOfs)))
fail("Section \"%s\" already declared as aligned to %u bytes (offset %"
PRIu16 ")\n", sect->name, 1U << sect->align, sect->alignOfs);
else
/* Otherwise, just override */
sect->org = org;
} else if (alignment != 0) {
/* Make sure any fixed address is compatible */
if (sect->org != -1) {
if ((sect->org - alignOffset)
& mask(alignment))
fail("Section \"%s\" already declared as fixed at incompatible address $%"
PRIx32 "\n", sect->name, sect->org);
/* Check if alignment offsets are compatible */
} else if ((alignOffset & mask(sect->align))
!= (sect->alignOfs
& mask(alignment))) {
fail("Section \"%s\" already declared with incompatible %"
PRIu8 "-byte alignment (offset %" PRIu16 ")\n",
sect->name, sect->align, sect->alignOfs);
} else if (alignment > sect->align) {
/*
* If the section is not fixed,
* its alignment is the largest of both
*/
sect->align = alignment;
sect->alignOfs = alignOffset;
}
}
/* If the section's bank is unspecified, override it */
if (sect->bank == -1)
sect->bank = bank;
/* If both specify a bank, it must be the same one */
else if (bank != -1 && sect->bank != bank)
fail("Section \"%s\" already declared with different bank %"
PRIu32 "\n", sect->name, sect->bank);
} else { /* Section fragments are handled identically in RGBASM */
/* However, concaternating non-fragments will be made an error */
if (sect->modifier != SECTION_FRAGMENT || mod != SECTION_FRAGMENT)
warning(WARNING_OBSOLETE,
"Concatenation of non-fragment sections is deprecated\n");
if (org != sect->org) {
if (sect->org == -1)
fail("Section \"%s\" already declared as floating\n",
sect->name);
else
fail("Section \"%s\" already declared as fixed at $%"
PRIx32 "\n", sect->name, sect->org);
}
if (bank != sect->bank) {
if (sect->bank == -1)
fail("Section \"%s\" already declared as floating bank\n",
sect->name);
else
fail("Section \"%s\" already declared as fixed at bank %"
PRIu32 "\n", sect->name, sect->bank);
}
if (alignment != sect->align) {
if (sect->align == 0)
fail("Section \"%s\" already declared as unaligned\n",
sect->name);
else
fail("Section \"%s\" already declared as aligned to %u bytes\n",
sect->name, 1U << sect->align);
}
}
if (nbSectErrors)
fatalerror("Cannot create section \"%s\" (%u errors)\n",
sect->name, nbSectErrors);
#undef fail
return sect;
}
sect = malloc(sizeof(*sect));
if (sect == NULL)
fatalerror("Not enough memory for section: %s\n", strerror(errno));
sect->name = strdup(name);
if (sect->name == NULL)
fatalerror("Not enough memory for section name: %s\n", strerror(errno));
sect->type = type;
sect->modifier = mod;
sect->size = 0;
sect->org = org;
sect->bank = bank;
sect->align = alignment;
sect->alignOfs = alignOffset;
sect->next = pSectionList;
sect->patches = NULL;
/* It is only needed to allocate memory for ROM sections. */
if (sect_HasData(type)) {
uint32_t sectsize;
sectsize = maxsize[type];
sect->data = malloc(sectsize);
if (sect->data == NULL)
fatalerror("Not enough memory for section: %s\n", strerror(errno));
} else { } else {
sect->data = NULL; sect = createSection(name, type, org, bank, alignment, alignOffset, mod);
// Add the new section to the list (order doesn't matter)
sect->next = pSectionList;
pSectionList = sect;
} }
/*
* Add the new section to the list
* at the beginning because order doesn't matter
*/
pSectionList = sect;
return sect; return sect;
#undef mask
} }
/* /*
@@ -321,6 +383,11 @@ void out_NewSection(char const *name, uint32_t type, uint32_t org,
if (currentLoadSection) if (currentLoadSection)
fatalerror("Cannot change the section within a `LOAD` block\n"); fatalerror("Cannot change the section within a `LOAD` block\n");
for (struct SectionStackEntry *stack = sectionStack; stack; stack = stack->next) {
if (stack->section && !strcmp(name, stack->section->name))
fatalerror("Section '%s' is already on the stack\n", name);
}
struct Section *sect = getSection(name, type, org, attribs, mod); struct Section *sect = getSection(name, type, org, attribs, mod);
changeSection(); changeSection();
@@ -332,18 +399,22 @@ void out_NewSection(char const *name, uint32_t type, uint32_t org,
* Set the current section by name and type * Set the current section by name and type
*/ */
void out_SetLoadSection(char const *name, uint32_t type, uint32_t org, void out_SetLoadSection(char const *name, uint32_t type, uint32_t org,
struct SectionSpec const *attribs) struct SectionSpec const *attribs,
enum SectionModifier mod)
{ {
checkcodesection(); checkcodesection();
if (currentLoadSection) if (currentLoadSection)
fatalerror("`LOAD` blocks cannot be nested\n"); fatalerror("`LOAD` blocks cannot be nested\n");
struct Section *sect = getSection(name, type, org, attribs, false); if (sect_HasData(type))
error("`LOAD` blocks cannot create a ROM section\n");
struct Section *sect = getSection(name, type, org, attribs, mod);
loadOffset = curOffset;
curOffset = 0; /* curOffset -= loadOffset; */
changeSection(); changeSection();
loadOffset = curOffset - (mod == SECTION_UNION ? 0 : sect->size);
curOffset -= loadOffset;
currentLoadSection = sect; currentLoadSection = sect;
} }
@@ -351,11 +422,11 @@ void out_EndLoadSection(void)
{ {
if (!currentLoadSection) if (!currentLoadSection)
error("Found `ENDL` outside of a `LOAD` block\n"); error("Found `ENDL` outside of a `LOAD` block\n");
currentLoadSection = NULL;
changeSection(); changeSection();
curOffset += loadOffset; curOffset += loadOffset;
loadOffset = 0; loadOffset = 0;
currentLoadSection = NULL;
} }
struct Section *sect_GetSymbolSection(void) struct Section *sect_GetSymbolSection(void)
@@ -380,24 +451,24 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset)
{ {
checksection(); checksection();
struct Section *sect = sect_GetSymbolSection(); struct Section *sect = sect_GetSymbolSection();
uint16_t alignSize = 1 << alignment; // Size of an aligned "block"
if (sect->org != -1) { if (sect->org != -1) {
if ((sym_GetPCValue() - offset) % (1 << alignment)) if ((sym_GetPCValue() - offset) % alignSize)
error("Section's fixed address fails required alignment (PC = $%04" error("Section's fixed address fails required alignment (PC = $%04" PRIx32
PRIx32 ")\n", sym_GetPCValue()); ")\n", sym_GetPCValue());
} else if (sect->align != 0) { } else if (sect->align != 0) {
if ((((sect->alignOfs + curOffset) % (1 << sect->align)) if ((((sect->alignOfs + curOffset) % (1 << sect->align)) - offset) % alignSize) {
- offset) % (1 << alignment)) {
error("Section's alignment fails required alignment (offset from section start = $%04" error("Section's alignment fails required alignment (offset from section start = $%04"
PRIx32 ")\n", curOffset); PRIx32 ")\n", curOffset);
} else if (alignment > sect->align) { } else if (alignment > sect->align) {
sect->align = alignment; sect->align = alignment;
sect->alignOfs = sect->alignOfs = (offset - curOffset) % alignSize;
(offset - curOffset) % (1 << alignment);
} }
} else { } else {
sect->align = alignment; sect->align = alignment;
sect->alignOfs = offset; // We need `(sect->alignOfs + curOffset) % alignSize == offset
sect->alignOfs = (offset - curOffset) % alignSize;
} }
} }
@@ -430,10 +501,10 @@ static inline void writelong(uint32_t b)
writebyte(b >> 24); writebyte(b >> 24);
} }
static inline void createPatch(enum PatchType type, static inline void createPatch(enum PatchType type, struct Expression const *expr,
struct Expression const *expr) uint32_t pcShift)
{ {
out_CreatePatch(type, expr, sect_GetOutputOffset()); out_CreatePatch(type, expr, sect_GetOutputOffset(), pcShift);
} }
void sect_StartUnion(void) void sect_StartUnion(void)
@@ -483,7 +554,7 @@ void sect_EndUnion(void)
void sect_CheckUnionClosed(void) void sect_CheckUnionClosed(void)
{ {
if (unionStack) if (unionStack)
fatalerror("Unterminated UNION construct!\n"); error("Unterminated UNION construct!\n");
} }
/* /*
@@ -506,6 +577,24 @@ void out_AbsByteGroup(uint8_t const *s, int32_t length)
writebyte(*s++); writebyte(*s++);
} }
void out_AbsWordGroup(uint8_t const *s, int32_t length)
{
checkcodesection();
reserveSpace(length * 2);
while (length--)
writeword(*s++);
}
void out_AbsLongGroup(uint8_t const *s, int32_t length)
{
checkcodesection();
reserveSpace(length * 4);
while (length--)
writelong(*s++);
}
/* /*
* Skip this many bytes * Skip this many bytes
*/ */
@@ -515,14 +604,15 @@ void out_Skip(int32_t skip, bool ds)
reserveSpace(skip); reserveSpace(skip);
if (!ds && sect_HasData(pCurrentSection->type)) if (!ds && sect_HasData(pCurrentSection->type))
warning(WARNING_EMPTY_DATA_DIRECTIVE, "db/dw/dl directive without data in ROM\n"); warning(WARNING_EMPTY_DATA_DIRECTIVE, "%s directive without data in ROM\n",
(skip == 4) ? "DL" : (skip == 2) ? "DW" : "DB");
if (!sect_HasData(pCurrentSection->type)) { if (!sect_HasData(pCurrentSection->type)) {
growSection(skip); growSection(skip);
} else { } else {
checkcodesection(); checkcodesection();
while (skip--) while (skip--)
writebyte(CurrentOptions.fillchar); writebyte(fillByte);
} }
} }
@@ -542,13 +632,13 @@ void out_String(char const *s)
* Output a relocatable byte. Checking will be done to see if it * Output a relocatable byte. Checking will be done to see if it
* is an absolute value in disguise. * is an absolute value in disguise.
*/ */
void out_RelByte(struct Expression *expr) void out_RelByte(struct Expression *expr, uint32_t pcShift)
{ {
checkcodesection(); checkcodesection();
reserveSpace(1); reserveSpace(1);
if (!rpn_isKnown(expr)) { if (!rpn_isKnown(expr)) {
createPatch(PATCHTYPE_BYTE, expr); createPatch(PATCHTYPE_BYTE, expr, pcShift);
writebyte(0); writebyte(0);
} else { } else {
writebyte(expr->nVal); writebyte(expr->nVal);
@@ -560,33 +650,37 @@ void out_RelByte(struct Expression *expr)
* Output several copies of a relocatable byte. Checking will be done to see if * Output several copies of a relocatable byte. Checking will be done to see if
* it is an absolute value in disguise. * it is an absolute value in disguise.
*/ */
void out_RelBytes(struct Expression *expr, uint32_t n) void out_RelBytes(uint32_t n, struct Expression *exprs, size_t size)
{ {
checkcodesection(); checkcodesection();
reserveSpace(n); reserveSpace(n);
while (n--) { for (uint32_t i = 0; i < n; i++) {
struct Expression *expr = &exprs[i % size];
if (!rpn_isKnown(expr)) { if (!rpn_isKnown(expr)) {
createPatch(PATCHTYPE_BYTE, expr); createPatch(PATCHTYPE_BYTE, expr, i);
writebyte(0); writebyte(0);
} else { } else {
writebyte(expr->nVal); writebyte(expr->nVal);
} }
} }
rpn_Free(expr);
for (size_t i = 0; i < size; i++)
rpn_Free(&exprs[i]);
} }
/* /*
* Output a relocatable word. Checking will be done to see if * Output a relocatable word. Checking will be done to see if
* it's an absolute value in disguise. * it's an absolute value in disguise.
*/ */
void out_RelWord(struct Expression *expr) void out_RelWord(struct Expression *expr, uint32_t pcShift)
{ {
checkcodesection(); checkcodesection();
reserveSpace(2); reserveSpace(2);
if (!rpn_isKnown(expr)) { if (!rpn_isKnown(expr)) {
createPatch(PATCHTYPE_WORD, expr); createPatch(PATCHTYPE_WORD, expr, pcShift);
writeword(0); writeword(0);
} else { } else {
writeword(expr->nVal); writeword(expr->nVal);
@@ -598,13 +692,13 @@ void out_RelWord(struct Expression *expr)
* Output a relocatable longword. Checking will be done to see if * Output a relocatable longword. Checking will be done to see if
* is an absolute value in disguise. * is an absolute value in disguise.
*/ */
void out_RelLong(struct Expression *expr) void out_RelLong(struct Expression *expr, uint32_t pcShift)
{ {
checkcodesection(); checkcodesection();
reserveSpace(2); reserveSpace(2);
if (!rpn_isKnown(expr)) { if (!rpn_isKnown(expr)) {
createPatch(PATCHTYPE_LONG, expr); createPatch(PATCHTYPE_LONG, expr, pcShift);
writelong(0); writelong(0);
} else { } else {
writelong(expr->nVal); writelong(expr->nVal);
@@ -616,14 +710,14 @@ void out_RelLong(struct Expression *expr)
* Output a PC-relative relocatable byte. Checking will be done to see if it * Output a PC-relative relocatable byte. Checking will be done to see if it
* is an absolute value in disguise. * is an absolute value in disguise.
*/ */
void out_PCRelByte(struct Expression *expr) void out_PCRelByte(struct Expression *expr, uint32_t pcShift)
{ {
checkcodesection(); checkcodesection();
reserveSpace(1); reserveSpace(1);
struct Symbol const *pc = sym_GetPC(); struct Symbol const *pc = sym_GetPC();
if (!rpn_IsDiffConstant(expr, pc)) { if (!rpn_IsDiffConstant(expr, pc)) {
createPatch(PATCHTYPE_JR, expr); createPatch(PATCHTYPE_JR, expr, pcShift);
writebyte(0); writebyte(0);
} else { } else {
struct Symbol const *sym = rpn_SymbolOf(expr); struct Symbol const *sym = rpn_SymbolOf(expr);
@@ -670,7 +764,8 @@ void out_BinaryFile(char const *s, int32_t startPos)
oFailedOnMissingInclude = true; oFailedOnMissingInclude = true;
return; return;
} }
fatalerror("Error opening INCBIN file '%s': %s\n", s, strerror(errno)); error("Error opening INCBIN file '%s': %s\n", s, strerror(errno));
return;
} }
int32_t fsize = -1; int32_t fsize = -1;
@@ -680,7 +775,7 @@ void out_BinaryFile(char const *s, int32_t startPos)
if (fseek(f, 0, SEEK_END) != -1) { if (fseek(f, 0, SEEK_END) != -1) {
fsize = ftell(f); fsize = ftell(f);
if (startPos >= fsize) { if (startPos > fsize) {
error("Specified start position is greater than length of file\n"); error("Specified start position is greater than length of file\n");
fclose(f); fclose(f);
return; return;
@@ -736,7 +831,8 @@ void out_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
oFailedOnMissingInclude = true; oFailedOnMissingInclude = true;
return; return;
} }
fatalerror("Error opening INCBIN file '%s': %s\n", s, strerror(errno)); error("Error opening INCBIN file '%s': %s\n", s, strerror(errno));
return;
} }
checkcodesection(); checkcodesection();
@@ -747,13 +843,16 @@ void out_BinaryFileSlice(char const *s, int32_t start_pos, int32_t length)
if (fseek(f, 0, SEEK_END) != -1) { if (fseek(f, 0, SEEK_END) != -1) {
fsize = ftell(f); fsize = ftell(f);
if (start_pos >= fsize) { if (start_pos > fsize) {
error("Specified start position is greater than length of file\n"); error("Specified start position is greater than length of file\n");
return; return;
} }
if ((start_pos + length) > fsize) if ((start_pos + length) > fsize) {
fatalerror("Specified range in INCBIN is out of bounds\n"); error("Specified range in INCBIN is out of bounds (%" PRIu32 " + %" PRIu32
" > %" PRIu32 ")\n", start_pos, length, fsize);
return;
}
fseek(f, start_pos, SEEK_SET); fseek(f, start_pos, SEEK_SET);
} else { } else {

View File

@@ -13,16 +13,16 @@
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <inttypes.h> #include <inttypes.h>
#include <limits.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#include "asm/asm.h" #include "asm/fixpoint.h"
#include "asm/fstack.h" #include "asm/fstack.h"
#include "asm/macro.h" #include "asm/macro.h"
#include "asm/main.h" #include "asm/main.h"
#include "asm/mymath.h"
#include "asm/output.h" #include "asm/output.h"
#include "asm/section.h" #include "asm/section.h"
#include "asm/symbol.h" #include "asm/symbol.h"
@@ -153,10 +153,12 @@ int32_t sym_GetValue(struct Symbol const *sym)
static void dumpFilename(struct Symbol const *sym) static void dumpFilename(struct Symbol const *sym)
{ {
if (!sym->src) if (sym->src)
fputs("<builtin>", stderr);
else
fstk_Dump(sym->src, sym->fileLine); fstk_Dump(sym->src, sym->fileLine);
else if (sym->fileLine == 0)
fputs("<command-line>", stderr);
else
fputs("<builtin>", stderr);
} }
/* /*
@@ -165,7 +167,7 @@ static void dumpFilename(struct Symbol const *sym)
static void setSymbolFilename(struct Symbol *sym) static void setSymbolFilename(struct Symbol *sym)
{ {
sym->src = fstk_GetFileStack(); sym->src = fstk_GetFileStack();
sym->fileLine = lexer_GetLineNo(); sym->fileLine = sym->src ? lexer_GetLineNo() : 0; // This is (NULL, 1) for built-ins
} }
/* /*
@@ -177,7 +179,7 @@ static void updateSymbolFilename(struct Symbol *sym)
setSymbolFilename(sym); setSymbolFilename(sym);
/* If the old node was referenced, ensure the new one is */ /* If the old node was referenced, ensure the new one is */
if (oldSrc->referenced && oldSrc->ID != -1) if (oldSrc && oldSrc->referenced && oldSrc->ID != -1)
out_RegisterNode(sym->src); out_RegisterNode(sym->src);
/* TODO: unref the old node, and use `out_ReplaceNode` instead if deleting it */ /* TODO: unref the old node, and use `out_ReplaceNode` instead if deleting it */
} }
@@ -222,6 +224,19 @@ static void fullSymbolName(char *output, size_t outputSize,
fatalerror("Symbol name is too long: '%s%s'\n", scopeName, localName); fatalerror("Symbol name is too long: '%s%s'\n", scopeName, localName);
} }
static void assignStringSymbol(struct Symbol *sym, char const *value)
{
char *string = strdup(value);
if (string == NULL)
fatalerror("No memory for string equate: %s\n", strerror(errno));
sym->type = SYM_EQUS;
/* TODO: use other fields */
sym->macro = string;
sym->macroSize = strlen(string);
}
struct Symbol *sym_FindExactSymbol(char const *name) struct Symbol *sym_FindExactSymbol(char const *name)
{ {
return hash_GetElement(symbols, name); return hash_GetElement(symbols, name);
@@ -283,6 +298,11 @@ void sym_Purge(char const *symName)
if (symbol->name == labelScope) if (symbol->name == labelScope)
labelScope = NULL; labelScope = NULL;
/*
* FIXME: this leaks symbol->macro for SYM_EQUS and SYM_MACRO, but this can't
* free(symbol->macro) because the expansion may be purging itself.
*/
hash_RemoveElement(symbols, symbol->name); hash_RemoveElement(symbols, symbol->name);
/* TODO: ideally, also unref the file stack nodes */ /* TODO: ideally, also unref the file stack nodes */
free(symbol); free(symbol);
@@ -346,8 +366,10 @@ void sym_SetCurrentSymbolScope(char const *newScope)
* Create a symbol that will be non-relocatable and ensure that it * Create a symbol that will be non-relocatable and ensure that it
* hasn't already been defined or referenced in a context that would * hasn't already been defined or referenced in a context that would
* require that it be relocatable * require that it be relocatable
* @param symbolName The name of the symbol to create
* @param numeric If false, the symbol may not have been referenced earlier
*/ */
static struct Symbol *createNonrelocSymbol(char const *symbolName) static struct Symbol *createNonrelocSymbol(char const *symbolName, bool numeric)
{ {
struct Symbol *symbol = sym_FindExactSymbol(symbolName); struct Symbol *symbol = sym_FindExactSymbol(symbolName);
@@ -357,6 +379,12 @@ static struct Symbol *createNonrelocSymbol(char const *symbolName)
error("'%s' already defined at ", symbolName); error("'%s' already defined at ", symbolName);
dumpFilename(symbol); dumpFilename(symbol);
putc('\n', stderr); putc('\n', stderr);
} else if (!numeric) {
// The symbol has already been referenced, but it's not allowed
error("'%s' already referenced at ", symbolName);
dumpFilename(symbol);
putc('\n', stderr);
return NULL; // Don't allow overriding the symbol, that'd be bad!
} }
return symbol; return symbol;
@@ -367,7 +395,10 @@ static struct Symbol *createNonrelocSymbol(char const *symbolName)
*/ */
struct Symbol *sym_AddEqu(char const *symName, int32_t value) struct Symbol *sym_AddEqu(char const *symName, int32_t value)
{ {
struct Symbol *sym = createNonrelocSymbol(symName); struct Symbol *sym = createNonrelocSymbol(symName, true);
if (!sym)
return NULL;
sym->type = SYM_EQU; sym->type = SYM_EQU;
sym->value = value; sym->value = value;
@@ -389,18 +420,33 @@ struct Symbol *sym_AddEqu(char const *symName, int32_t value)
*/ */
struct Symbol *sym_AddString(char const *symName, char const *value) struct Symbol *sym_AddString(char const *symName, char const *value)
{ {
struct Symbol *sym = createNonrelocSymbol(symName); struct Symbol *sym = createNonrelocSymbol(symName, false);
size_t len = strlen(value);
char *string = malloc(len + 1);
if (string == NULL) if (!sym)
fatalerror("No memory for string equate: %s\n", strerror(errno)); return NULL;
strcpy(string, value);
sym->type = SYM_EQUS; assignStringSymbol(sym, value);
/* TODO: use other fields */ return sym;
sym->macroSize = len; }
sym->macro = string;
struct Symbol *sym_RedefString(char const *symName, char const *value)
{
struct Symbol *sym = sym_FindExactSymbol(symName);
if (!sym) {
sym = createsymbol(symName);
} else if (sym->type != SYM_EQUS) {
error("'%s' already defined as non-EQUS at ", symName);
dumpFilename(sym);
putc('\n', stderr);
}
/*
* FIXME: this leaks the previous sym->macro value, but this can't
* free(sym->macro) because the expansion may be redefining itself.
*/
assignStringSymbol(sym, value);
return sym; return sym;
} }
@@ -492,8 +538,10 @@ struct Symbol *sym_AddLocalLabel(char const *name)
* Check that `labelScope[i]` ended the check, guaranteeing that `name` is at least * Check that `labelScope[i]` ended the check, guaranteeing that `name` is at least
* as long, and then that this was the entirety of the `Parent` part of `name`. * as long, and then that this was the entirety of the `Parent` part of `name`.
*/ */
if (labelScope[i] != '\0' || name[i] != '.') if (labelScope[i] != '\0' || name[i] != '.') {
error("Not currently in the scope of '%.*s'\n", parentLen, name); assert(parentLen <= INT_MAX);
error("Not currently in the scope of '%.*s'\n", (int)parentLen, name);
}
if (strchr(&name[parentLen + 1], '.')) /* There will at least be a terminator */ if (strchr(&name[parentLen + 1], '.')) /* There will at least be a terminator */
fatalerror("'%s' is a nonsensical reference to a nested local label\n", fatalerror("'%s' is a nonsensical reference to a nested local label\n",
name); name);
@@ -515,11 +563,60 @@ struct Symbol *sym_AddLabel(char const *name)
return sym; return sym;
} }
static uint32_t anonLabelID;
/*
* Add an anonymous label
*/
struct Symbol *sym_AddAnonLabel(void)
{
if (anonLabelID == UINT32_MAX) {
error("Only %" PRIu32 " anonymous labels can be created!", anonLabelID);
return NULL;
}
char name[MAXSYMLEN + 1];
sym_WriteAnonLabelName(name, 0, true); // The direction is important!!
anonLabelID++;
return addLabel(name);
}
/*
* Write an anonymous label's name to a buffer
*/
void sym_WriteAnonLabelName(char buf[MIN_NB_ELMS(MAXSYMLEN + 1)], uint32_t ofs, bool neg)
{
uint32_t id = 0;
if (neg) {
if (ofs > anonLabelID)
error("Reference to anonymous label %" PRIu32 " before, when only %" PRIu32
" ha%s been created so far\n",
ofs, anonLabelID, anonLabelID == 1 ? "s" : "ve");
else
id = anonLabelID - ofs;
} else {
ofs--; // We're referencing symbols that haven't been created yet...
if (ofs > UINT32_MAX - anonLabelID)
error("Reference to anonymous label %" PRIu32 " after, when only %" PRIu32
" may still be created\n", ofs + 1, UINT32_MAX - anonLabelID);
else
id = anonLabelID + ofs;
}
sprintf(buf, "!%u", id);
}
/* /*
* Export a symbol * Export a symbol
*/ */
void sym_Export(char const *symName) void sym_Export(char const *symName)
{ {
if (symName[0] == '!') {
error("Anonymous labels cannot be exported\n");
return;
}
struct Symbol *sym = sym_FindScopedSymbol(symName); struct Symbol *sym = sym_FindScopedSymbol(symName);
/* If the symbol doesn't exist, create a ref that can be purged */ /* If the symbol doesn't exist, create a ref that can be purged */
@@ -533,7 +630,10 @@ void sym_Export(char const *symName)
*/ */
struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body, size_t size) struct Symbol *sym_AddMacro(char const *symName, int32_t defLineNo, char *body, size_t size)
{ {
struct Symbol *sym = createNonrelocSymbol(symName); struct Symbol *sym = createNonrelocSymbol(symName, false);
if (!sym)
return NULL;
sym->type = SYM_MACRO; sym->type = SYM_MACRO;
sym->macroSize = size; sym->macroSize = size;
@@ -599,14 +699,14 @@ static inline struct Symbol *createBuiltinSymbol(char const *name)
sym->isBuiltin = true; sym->isBuiltin = true;
sym->hasCallback = true; sym->hasCallback = true;
sym->src = NULL; sym->src = NULL;
sym->fileLine = 0; sym->fileLine = 1; // This is 0 for CLI-defined symbols
return sym; return sym;
} }
/* /*
* Initialize the symboltable * Initialize the symboltable
*/ */
void sym_Init(void) void sym_Init(time_t now)
{ {
PCSymbol = createBuiltinSymbol("@"); PCSymbol = createBuiltinSymbol("@");
struct Symbol *_NARGSymbol = createBuiltinSymbol("_NARG"); struct Symbol *_NARGSymbol = createBuiltinSymbol("_NARG");
@@ -627,8 +727,9 @@ void sym_Init(void)
sym_AddEqu("__RGBDS_MAJOR__", PACKAGE_VERSION_MAJOR)->isBuiltin = true; sym_AddEqu("__RGBDS_MAJOR__", PACKAGE_VERSION_MAJOR)->isBuiltin = true;
sym_AddEqu("__RGBDS_MINOR__", PACKAGE_VERSION_MINOR)->isBuiltin = true; sym_AddEqu("__RGBDS_MINOR__", PACKAGE_VERSION_MINOR)->isBuiltin = true;
sym_AddEqu("__RGBDS_PATCH__", PACKAGE_VERSION_PATCH)->isBuiltin = true; sym_AddEqu("__RGBDS_PATCH__", PACKAGE_VERSION_PATCH)->isBuiltin = true;
#ifdef PACKAGE_VERSION_RC
time_t now = time(NULL); sym_AddEqu("__RGBDS_RC__", PACKAGE_VERSION_RC)->isBuiltin = true;
#endif
if (now == (time_t)-1) { if (now == (time_t)-1) {
warn("Couldn't determine current time"); warn("Couldn't determine current time");
@@ -671,6 +772,14 @@ void sym_Init(void)
#undef addString #undef addString
labelScope = NULL; labelScope = NULL;
anonLabelID = 0;
math_DefinePI(); /* _PI is deprecated */
struct Symbol *_PISymbol = createBuiltinSymbol("_PI");
_PISymbol->type = SYM_EQU;
_PISymbol->src = NULL;
_PISymbol->fileLine = 0;
_PISymbol->hasCallback = true;
_PISymbol->numCallback = fix_Callback_PI;
} }

View File

@@ -16,7 +16,9 @@
#include "extern/utf8decoder.h" #include "extern/utf8decoder.h"
/* /*
* Calculate the hash value for a string * Calculate the hash value for a string.
* Uses the djb2 algorithm (xor version).
* http://www.cse.yorku.ca/~oz/hash.html
*/ */
uint32_t calchash(const char *s) uint32_t calchash(const char *s)
{ {
@@ -70,7 +72,7 @@ size_t readUTF8Char(uint8_t *dest, char const *src)
for (;;) { for (;;) {
if (decode(&state, &codep, src[i]) == 1) if (decode(&state, &codep, src[i]) == 1)
fatalerror("invalid UTF-8 character\n"); return 0;
dest[i] = src[i]; dest[i] = src[i];
i++; i++;

View File

@@ -10,6 +10,7 @@
#include <stdarg.h> #include <stdarg.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include "asm/fstack.h" #include "asm/fstack.h"
@@ -33,9 +34,11 @@ static enum WarningState const defaultWarnings[NB_WARNINGS] = {
[WARNING_CHARMAP_REDEF] = WARNING_DISABLED, [WARNING_CHARMAP_REDEF] = WARNING_DISABLED,
[WARNING_DIV] = WARNING_DISABLED, [WARNING_DIV] = WARNING_DISABLED,
[WARNING_EMPTY_DATA_DIRECTIVE] = WARNING_DISABLED, [WARNING_EMPTY_DATA_DIRECTIVE] = WARNING_DISABLED,
[WARNING_EMPTY_ENTRY] = WARNING_DISABLED, [WARNING_EMPTY_MACRO_ARG] = WARNING_DISABLED,
[WARNING_EMPTY_STRRPL] = WARNING_DISABLED,
[WARNING_LARGE_CONSTANT] = WARNING_DISABLED, [WARNING_LARGE_CONSTANT] = WARNING_DISABLED,
[WARNING_LONG_STR] = WARNING_DISABLED, [WARNING_LONG_STR] = WARNING_DISABLED,
[WARNING_MACRO_SHIFT] = WARNING_DISABLED,
[WARNING_NESTED_COMMENT] = WARNING_ENABLED, [WARNING_NESTED_COMMENT] = WARNING_ENABLED,
[WARNING_OBSOLETE] = WARNING_ENABLED, [WARNING_OBSOLETE] = WARNING_ENABLED,
[WARNING_SHIFT] = WARNING_DISABLED, [WARNING_SHIFT] = WARNING_DISABLED,
@@ -73,9 +76,11 @@ static char const *warningFlags[NB_WARNINGS_ALL] = {
"charmap-redef", "charmap-redef",
"div", "div",
"empty-data-directive", "empty-data-directive",
"empty-entry", "empty-macro-arg",
"empty-strrpl",
"large-constant", "large-constant",
"long-string", "long-string",
"macro-shift",
"nested-comment", "nested-comment",
"obsolete", "obsolete",
"shift", "shift",
@@ -98,6 +103,7 @@ static uint8_t const _wallCommands[] = {
WARNING_BUILTIN_ARG, WARNING_BUILTIN_ARG,
WARNING_CHARMAP_REDEF, WARNING_CHARMAP_REDEF,
WARNING_EMPTY_DATA_DIRECTIVE, WARNING_EMPTY_DATA_DIRECTIVE,
WARNING_EMPTY_STRRPL,
WARNING_LARGE_CONSTANT, WARNING_LARGE_CONSTANT,
WARNING_LONG_STR, WARNING_LONG_STR,
META_WARNING_DONE META_WARNING_DONE
@@ -105,7 +111,8 @@ static uint8_t const _wallCommands[] = {
/* Warnings that are less likely to indicate an error */ /* Warnings that are less likely to indicate an error */
static uint8_t const _wextraCommands[] = { static uint8_t const _wextraCommands[] = {
WARNING_EMPTY_ENTRY, WARNING_EMPTY_MACRO_ARG,
WARNING_MACRO_SHIFT,
WARNING_NESTED_COMMENT, WARNING_NESTED_COMMENT,
META_WARNING_DONE META_WARNING_DONE
}; };
@@ -115,9 +122,11 @@ static uint8_t const _weverythingCommands[] = {
WARNING_BUILTIN_ARG, WARNING_BUILTIN_ARG,
WARNING_DIV, WARNING_DIV,
WARNING_EMPTY_DATA_DIRECTIVE, WARNING_EMPTY_DATA_DIRECTIVE,
WARNING_EMPTY_ENTRY, WARNING_EMPTY_MACRO_ARG,
WARNING_EMPTY_STRRPL,
WARNING_LARGE_CONSTANT, WARNING_LARGE_CONSTANT,
WARNING_LONG_STR, WARNING_LONG_STR,
WARNING_MACRO_SHIFT,
WARNING_NESTED_COMMENT, WARNING_NESTED_COMMENT,
WARNING_OBSOLETE, WARNING_OBSOLETE,
WARNING_SHIFT, WARNING_SHIFT,

7
src/check_bison_ver.sh Executable file
View File

@@ -0,0 +1,7 @@
#!/bin/sh
bison -V | awk -v major="$1" -v minor="$2" '
/^bison.*[0-9]+(\.[0-9]+)(\.[0-9]+)?$/ {
match($0, /[0-9]+(\.[0-9]+)(\.[0-9]+)?$/);
split(substr($0, RSTART), ver, ".");
if (ver[1] == major && ver[2] >= minor) { exit 0 } else { exit 1 }
}'

203
src/extern/getopt.c vendored
View File

@@ -26,21 +26,16 @@
#include <stddef.h> #include <stddef.h>
#include <stdlib.h> #include <stdlib.h>
#include <limits.h> #include <limits.h>
#ifndef _MSC_VER
# include <unistd.h>
#endif
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <wchar.h> #include <wchar.h>
#include "extern/getopt.h" #include "extern/getopt.h"
#ifdef _MSC_VER char *musl_optarg;
char *optarg; int musl_optind = 1, musl_opterr = 1, musl_optopt;
int optind=1, opterr=1, optopt; int musl_optreset = 0;
#endif static int musl_optpos;
int optreset=0;
static int optpos;
static void musl_getopt_msg(const char *a, const char *b, const char *c, size_t l) static void musl_getopt_msg(const char *a, const char *b, const char *c, size_t l)
{ {
@@ -52,7 +47,6 @@ static void musl_getopt_msg(const char *a, const char *b, const char *c, size_t
putc('\n', f); putc('\n', f);
} }
#ifdef _MSC_VER
static int getopt(int argc, char *argv[], const char *optstring) static int getopt(int argc, char *argv[], const char *optstring)
{ {
int i; int i;
@@ -60,40 +54,42 @@ static int getopt(int argc, char *argv[], const char *optstring)
int k, l; int k, l;
char *optchar; char *optchar;
if (!optind || optreset) { if (!musl_optind || musl_optreset) {
optreset = 0; musl_optreset = 0;
optpos = 0; musl_optpos = 0;
optind = 1; musl_optind = 1;
} }
if (optind >= argc || !argv[optind]) if (musl_optind >= argc || !argv[musl_optind])
return -1; return -1;
if (argv[optind][0] != '-') { if (argv[musl_optind][0] != '-') {
if (optstring[0] == '-') { if (optstring[0] == '-') {
optarg = argv[optind++]; musl_optarg = argv[musl_optind++];
return 1; return 1;
} }
return -1; return -1;
} }
if (!argv[optind][1]) if (!argv[musl_optind][1])
return -1; return -1;
if (argv[optind][1] == '-' && !argv[optind][2]) if (argv[musl_optind][1] == '-' && !argv[musl_optind][2])
return optind++, -1; return musl_optind++, -1;
if (!optpos) optpos++; if (!musl_optpos)
if ((k = mbtowc(&c, argv[optind]+optpos, MB_LEN_MAX)) < 0) { musl_optpos++;
k = mbtowc(&c, argv[musl_optind] + musl_optpos, MB_LEN_MAX);
if (k < 0) {
k = 1; k = 1;
c = 0xfffd; /* replacement char */ c = 0xfffd; /* replacement char */
} }
optchar = argv[optind]+optpos; optchar = argv[musl_optind] + musl_optpos;
optpos += k; musl_optpos += k;
if (!argv[optind][optpos]) { if (!argv[musl_optind][musl_optpos]) {
optind++; musl_optind++;
optpos = 0; musl_optpos = 0;
} }
if (optstring[0] == '-' || optstring[0] == '+') if (optstring[0] == '-' || optstring[0] == '+')
@@ -103,39 +99,43 @@ static int getopt(int argc, char *argv[], const char *optstring)
d = 0; d = 0;
do { do {
l = mbtowc(&d, optstring+i, MB_LEN_MAX); l = mbtowc(&d, optstring+i, MB_LEN_MAX);
if (l>0) i+=l; else i++; if (l > 0)
i += l;
else
i++;
} while (l && d != c); } while (l && d != c);
if (d != c || c == ':') { if (d != c || c == ':') {
optopt = c; musl_optopt = c;
if (optstring[0] != ':' && opterr) if (optstring[0] != ':' && musl_opterr)
musl_getopt_msg(argv[0], ": unrecognized option: ", optchar, k); musl_getopt_msg(argv[0], ": unrecognized option: ", optchar, k);
return '?'; return '?';
} }
if (optstring[i] == ':') { if (optstring[i] == ':') {
optarg = 0; musl_optarg = 0;
if (optstring[i+1] != ':' || optpos) { if (optstring[i + 1] != ':' || musl_optpos) {
optarg = argv[optind++] + optpos; musl_optarg = argv[musl_optind++] + musl_optpos;
optpos = 0; musl_optpos = 0;
} }
if (optind > argc) { if (musl_optind > argc) {
optopt = c; musl_optopt = c;
if (optstring[0] == ':') return ':'; if (optstring[0] == ':')
if (opterr) musl_getopt_msg(argv[0], return ':';
": option requires an argument: ", if (musl_opterr)
optchar, k); musl_getopt_msg(argv[0], ": option requires an argument: ",
optchar, k);
return '?'; return '?';
} }
} }
return c; return c;
} }
#endif /* _MSC_VER */
static void permute(char **argv, int dest, int src) static void permute(char **argv, int dest, int src)
{ {
char *tmp = argv[src]; char *tmp = argv[src];
int i; int i;
for (i=src; i>dest; i--)
for (i = src; i > dest; i--)
argv[i] = argv[i-1]; argv[i] = argv[i-1];
argv[dest] = tmp; argv[dest] = tmp;
} }
@@ -145,49 +145,61 @@ static int musl_getopt_long_core(int argc, char **argv, const char *optstring, c
static int musl_getopt_long(int argc, char **argv, const char *optstring, const struct option *longopts, int *idx, int longonly) static int musl_getopt_long(int argc, char **argv, const char *optstring, const struct option *longopts, int *idx, int longonly)
{ {
int ret, skipped, resumed; int ret, skipped, resumed;
if (!optind || optreset) {
optreset = 0; if (!musl_optind || musl_optreset) {
optpos = 0; musl_optreset = 0;
optind = 1; musl_optpos = 0;
musl_optind = 1;
} }
if (optind >= argc || !argv[optind]) return -1;
skipped = optind; if (musl_optind >= argc || !argv[musl_optind])
return -1;
skipped = musl_optind;
if (optstring[0] != '+' && optstring[0] != '-') { if (optstring[0] != '+' && optstring[0] != '-') {
int i; int i;
for (i=optind; ; i++) { for (i = musl_optind; ; i++) {
if (i >= argc || !argv[i]) return -1; if (i >= argc || !argv[i])
if (argv[i][0] == '-' && argv[i][1]) break; return -1;
if (argv[i][0] == '-' && argv[i][1])
break;
} }
optind = i; musl_optind = i;
} }
resumed = optind; resumed = musl_optind;
ret = musl_getopt_long_core(argc, argv, optstring, longopts, idx, longonly); ret = musl_getopt_long_core(argc, argv, optstring, longopts, idx, longonly);
if (resumed > skipped) { if (resumed > skipped) {
int i, cnt = optind-resumed; int i, cnt = musl_optind - resumed;
for (i=0; i<cnt; i++)
permute(argv, skipped, optind-1); for (i = 0; i < cnt; i++)
optind = skipped + cnt; permute(argv, skipped, musl_optind - 1);
musl_optind = skipped + cnt;
} }
return ret; return ret;
} }
static int musl_getopt_long_core(int argc, char **argv, const char *optstring, const struct option *longopts, int *idx, int longonly) static int musl_getopt_long_core(int argc, char **argv, const char *optstring, const struct option *longopts, int *idx, int longonly)
{ {
optarg = 0; musl_optarg = 0;
if (longopts && argv[optind][0] == '-' && if (longopts && argv[musl_optind][0] == '-' &&
((longonly && argv[optind][1] && argv[optind][1] != '-') || ((longonly && argv[musl_optind][1] && argv[musl_optind][1] != '-') ||
(argv[optind][1] == '-' && argv[optind][2]))) (argv[musl_optind][1] == '-' && argv[musl_optind][2]))) {
{ int colon = optstring[optstring[0] == '+' || optstring[0] == '-'] == ':';
int colon = optstring[optstring[0]=='+'||optstring[0]=='-']==':';
int i, cnt, match = 0; int i, cnt, match = 0;
char *arg = 0, *opt, *start = argv[optind]+1; char *arg = 0, *opt, *start = argv[musl_optind] + 1;
for (cnt=i=0; longopts[i].name; i++) {
for (cnt = i = 0; longopts[i].name; i++) {
const char *name = longopts[i].name; const char *name = longopts[i].name;
opt = start; opt = start;
if (*opt == '-') opt++; if (*opt == '-')
while (*opt && *opt != '=' && *opt == *name) opt++;
name++, opt++; while (*opt && *opt != '=' && *opt == *name) {
if (*opt && *opt != '=') continue; name++;
opt++;
}
if (*opt && *opt != '=')
continue;
arg = opt; arg = opt;
match = i; match = i;
if (!*name) { if (!*name) {
@@ -196,25 +208,28 @@ static int musl_getopt_long_core(int argc, char **argv, const char *optstring, c
} }
cnt++; cnt++;
} }
if (cnt==1 && longonly && arg-start == mblen(start, MB_LEN_MAX)) { if (cnt == 1 && longonly && arg - start == mblen(start, MB_LEN_MAX)) {
int l = arg-start; int l = arg - start;
for (i=0; optstring[i]; i++) {
int j; for (i = 0; optstring[i]; i++) {
for (j=0; j<l && start[j]==optstring[i+j]; j++); int j = 0;
if (j==l) {
while (j < l && start[j] == optstring[i + j])
j++;
if (j == l) {
cnt++; cnt++;
break; break;
} }
} }
} }
if (cnt==1) { if (cnt == 1) {
i = match; i = match;
opt = arg; opt = arg;
optind++; musl_optind++;
if (*opt == '=') { if (*opt == '=') {
if (!longopts[i].has_arg) { if (!longopts[i].has_arg) {
optopt = longopts[i].val; musl_optopt = longopts[i].val;
if (colon || !opterr) if (colon || !musl_opterr)
return '?'; return '?';
musl_getopt_msg(argv[0], musl_getopt_msg(argv[0],
": option does not take an argument: ", ": option does not take an argument: ",
@@ -222,36 +237,40 @@ static int musl_getopt_long_core(int argc, char **argv, const char *optstring, c
strlen(longopts[i].name)); strlen(longopts[i].name));
return '?'; return '?';
} }
optarg = opt+1; musl_optarg = opt + 1;
} else if (longopts[i].has_arg == required_argument) { } else if (longopts[i].has_arg == required_argument) {
if (!(optarg = argv[optind])) { musl_optarg = argv[musl_optind];
optopt = longopts[i].val; if (!musl_optarg) {
if (colon) return ':'; musl_optopt = longopts[i].val;
if (!opterr) return '?'; if (colon)
return ':';
if (!musl_opterr)
return '?';
musl_getopt_msg(argv[0], musl_getopt_msg(argv[0],
": option requires an argument: ", ": option requires an argument: ",
longopts[i].name, longopts[i].name,
strlen(longopts[i].name)); strlen(longopts[i].name));
return '?'; return '?';
} }
optind++; musl_optind++;
} }
if (idx) *idx = i; if (idx)
*idx = i;
if (longopts[i].flag) { if (longopts[i].flag) {
*longopts[i].flag = longopts[i].val; *longopts[i].flag = longopts[i].val;
return 0; return 0;
} }
return longopts[i].val; return longopts[i].val;
} }
if (argv[optind][1] == '-') { if (argv[musl_optind][1] == '-') {
optopt = 0; musl_optopt = 0;
if (!colon && opterr) if (!colon && musl_opterr)
musl_getopt_msg(argv[0], cnt ? musl_getopt_msg(argv[0], cnt ?
": option is ambiguous: " : ": option is ambiguous: " :
": unrecognized option: ", ": unrecognized option: ",
argv[optind]+2, argv[musl_optind] + 2,
strlen(argv[optind]+2)); strlen(argv[musl_optind] + 2));
optind++; musl_optind++;
return '?'; return '?';
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -24,39 +24,48 @@
.Op Fl p Ar pad_value .Op Fl p Ar pad_value
.Op Fl r Ar ram_size .Op Fl r Ar ram_size
.Op Fl t Ar title_str .Op Fl t Ar title_str
.Ar file .Op Ar
.Sh DESCRIPTION .Sh DESCRIPTION
The The
.Nm .Nm
program changes headers of Game Boy ROM images. program changes headers of Game Boy ROM images, typically generated by
.Xr rgblink 1 ,
though it will work with
.Em any
Game Boy ROM.
It also performs other correctness operations, such as padding. It also performs other correctness operations, such as padding.
.Nm
only changes the fields for which it has values specified.
Developers are advised to fill those fields with 0x00 bytes in their source code before running
.Nm ,
and to have already populated whichever fields they don't specify using
.Nm .
.Pp .Pp
Note that options can be abbreviated as long as the abbreviation is unambiguous: Note that options can be abbreviated as long as the abbreviation is unambiguous:
.Fl Fl verb .Fl Fl color-o
is is
.Fl Fl verbose , .Fl Fl color-only ,
but but
.Fl Fl ver .Fl Fl color
is invalid because it could also be is invalid because it could also be
.Fl Fl version . .Fl Fl color-compatible .
The arguments are as follows: Options later in the command line override those set earlier.
Accepted options are as follows:
.Bl -tag -width Ds .Bl -tag -width Ds
.It Fl C , Fl Fl color-only .It Fl C , Fl Fl color-only
Set the Game Boy Color\(enonly flag: Set the Game Boy Color\(enonly flag
.Ad 0x143 .Pq Ad 0x143
= 0xC0. to 0xC0.
If both this and the This overrides
.Fl c .Fl c
flag are set, this takes precedence. if it was set prior.
.It Fl c , Fl Fl color-compatible .It Fl c , Fl Fl color-compatible
Set the Game Boy Color\(encompatible flag: Set the Game Boy Color\(encompatible flag:
.Ad 0x143 .Pq Ad 0x143
= 0x80. to 0x80.
If both this and the This overrides
.Fl C .Fl c
flag are set, if it was set prior.
.Fl C
takes precedence.
.It Fl f Ar fix_spec , Fl Fl fix-spec Ar fix_spec .It Fl f Ar fix_spec , Fl Fl fix-spec Ar fix_spec
Fix certain header values that the Game Boy checks for correctness. Fix certain header values that the Game Boy checks for correctness.
Alternatively, intentionally trash these values by writing their binary inverse instead. Alternatively, intentionally trash these values by writing their binary inverse instead.
@@ -83,54 +92,63 @@ Trash the global checksum.
.It Fl i Ar game_id , Fl Fl game-id Ar game_id .It Fl i Ar game_id , Fl Fl game-id Ar game_id
Set the game ID string Set the game ID string
.Pq Ad 0x13F Ns \(en Ns Ad 0x142 .Pq Ad 0x13F Ns \(en Ns Ad 0x142
to a given string of exactly 4 characters. to a given string.
If both this and the title are set, the game ID will overwrite the overlapping portion of the title. If it's longer than 4 chars, it will be truncated, and a warning emitted.
.It Fl j , Fl Fl non-japanese .It Fl j , Fl Fl non-japanese
Set the non-Japanese region flag: Set the non-Japanese region flag
.Ad 0x14A .Pq Ad 0x14A
= 1. to 0x01.
.It Fl k Ar licensee_str , Fl Fl new-licensee Ar licensee_str .It Fl k Ar licensee_str , Fl Fl new-licensee Ar licensee_str
Set the new licensee string Set the new licensee string
.Pq Ad 0x144 Ns \(en Ns Ad 0x145 .Pq Ad 0x144 Ns \(en Ns Ad 0x145
to a given string, truncated to at most two characters. to a given string.
If it's longer than 2 chars, it will be truncated, and a warning emitted.
.It Fl l Ar licensee_id , Fl Fl old-licensee Ar licensee_id .It Fl l Ar licensee_id , Fl Fl old-licensee Ar licensee_id
Set the old licensee code, Set the old licensee code
.Ad 0x14B , .Pq Ad 0x14B
to a given value from 0 to 0xFF. to a given value from 0 to 0xFF.
This value is deprecated and should be set to 0x33 in all new software. This value is deprecated and should be set to 0x33 in all new software.
.It Fl m Ar mbc_type , Fl Fl mbc-type Ar mbc_type .It Fl m Ar mbc_type , Fl Fl mbc-type Ar mbc_type
Set the MBC type, Set the MBC type
.Ad 0x147 , .Pq Ad 0x147
to a given value from 0 to 0xFF. to a given value from 0 to 0xFF.
This value may also be an MBC name from the Pan Docs.
.It Fl n Ar rom_version , Fl Fl rom-version Ar rom_version .It Fl n Ar rom_version , Fl Fl rom-version Ar rom_version
Set the ROM version, Set the ROM version
.Ad 0x14C , .Pq Ad 0x14C
to a given value from 0 to 0xFF. to a given value from 0 to 0xFF.
.It Fl p Ar pad_value , Fl Fl pad-value Ar pad_value .It Fl p Ar pad_value , Fl Fl pad-value Ar pad_value
Pad the image to a valid size with a given pad value from 0 to 0xFF. Pad the ROM image to a valid size with a given pad value from 0 to 255 (0xFF).
.Nm .Nm
will automatically pick a size from 32 KiB, 64 KiB, 128 KiB, ..., 8192 KiB. will automatically pick a size from 32 KiB, 64 KiB, 128 KiB, ..., 8192 KiB.
The cartridge size byte The cartridge size byte
.Pq Ad 0x148 .Pq Ad 0x148
will be changed to reflect this new size. will be changed to reflect this new size.
The recommended padding value is 0xFF, to speed up writing the ROM to flash chips, and to avoid "nop slides" into VRAM.
.It Fl r Ar ram_size , Fl Fl ram-size Ar ram_size .It Fl r Ar ram_size , Fl Fl ram-size Ar ram_size
Set the RAM size, Set the RAM size
.Ad 0x149 , .Pq Ad 0x149
to a given value from 0 to 0xFF. to a given value from 0 to 0xFF.
.It Fl s , Fl Fl sgb-compatible .It Fl s , Fl Fl sgb-compatible
Set the SGB flag: Set the SGB flag
.Ad 0x146 .Pq Ad 0x146
= 3. This flag will be ignored by the SGB unless the old licensee code is 0x33! to 0x03.
This flag will be ignored by the SGB unless the old licensee code is 0x33!
If this is given as well as
.Fl l ,
but is not set to 0x33, a warning will be printed.
.It Fl t Ar title , Fl Fl title Ar title .It Fl t Ar title , Fl Fl title Ar title
Set the title string Set the title string
.Pq Ad 0x134 Ns \(en Ns Ad 0x143 .Pq Ad 0x134 Ns \(en Ns Ad 0x143
to a given string, truncated to at most 16 characters. to a given string.
It is recommended to use 15 characters instead, to avoid clashing with the CGB flag If the title is longer than the max length, it will be truncated, and a warning emitted.
.Po Fl c The max length is 11 characters if the game ID
.Pq Fl i
is specified, 15 characters if the CGB flag
.Fl ( c
or or
.Fl C .Fl C )
.Pc . is specified but the game ID is not, and 16 characters otherwise.
If both this and the game ID are set, the game ID will overwrite the overlapping portion of the title.
.It Fl V , Fl Fl version .It Fl V , Fl Fl version
Print the version of the program and exit. Print the version of the program and exit.
.It Fl v , Fl Fl validate .It Fl v , Fl Fl validate
@@ -138,7 +156,7 @@ Equivalent to
.Fl f Cm lhg . .Fl f Cm lhg .
.El .El
.Sh EXAMPLES .Sh EXAMPLES
Most values in the ROM header are only cosmetic. Most values in the ROM header do not matter to the actual console, and most are seldom useful anyway.
The bare minimum requirements for a workable program are the header checksum, the Nintendo logo, and (if needed) the CGB/SGB flags. The bare minimum requirements for a workable program are the header checksum, the Nintendo logo, and (if needed) the CGB/SGB flags.
It is a good idea to pad the image to a valid size as well It is a good idea to pad the image to a valid size as well
.Pq Do valid Dc meaning a power of 2, times 32 KiB . .Pq Do valid Dc meaning a power of 2, times 32 KiB .
@@ -151,14 +169,13 @@ a valid size:
The following will make a SGB-enabled, color-enabled game with a title of The following will make a SGB-enabled, color-enabled game with a title of
.Dq foobar , .Dq foobar ,
and pad it to a valid size. and pad it to a valid size.
.Po .Pq The Game Boy itself does not use the title, but some emulators or ROM managers do.
The Game Boy itself does not use the title, but some emulators or ROM managers do.
.Pc
.Pp .Pp
.D1 $ rgbfix -vcs -l 0x33 -p 255 -t foobar baz.gb .D1 $ rgbfix -vcs -l 0x33 -p 255 -t foobar baz.gb
.Pp .Pp
The following will duplicate the header (sans global checksum) of the game The following will duplicate the header of the game
.Dq Survival Kids : .Dq Survival Kids ,
sans global checksum:
.Pp .Pp
.D1 $ rgbfix -cjsv -k A4 -l 0x33 -m 0x1B -p 0xFF -r 3 -t SURVIVALKIDAVKE \ .D1 $ rgbfix -cjsv -k A4 -l 0x33 -m 0x1B -p 0xFF -r 3 -t SURVIVALKIDAVKE \
SurvivalKids.gbc SurvivalKids.gbc

View File

@@ -96,7 +96,7 @@ int main(int argc, char *argv[])
opts.attrmapout = true; opts.attrmapout = true;
break; break;
case 'a': case 'a':
opts.attrmapfile = optarg; opts.attrmapfile = musl_optarg;
break; break;
case 'C': case 'C':
opts.colorcurve = true; opts.colorcurve = true;
@@ -105,7 +105,7 @@ int main(int argc, char *argv[])
opts.debug = true; opts.debug = true;
break; break;
case 'd': case 'd':
depth = strtoul(optarg, NULL, 0); depth = strtoul(musl_optarg, NULL, 0);
break; break;
case 'F': case 'F':
opts.hardfix = true; opts.hardfix = true;
@@ -121,19 +121,19 @@ int main(int argc, char *argv[])
opts.unique = true; opts.unique = true;
break; break;
case 'o': case 'o':
opts.outfile = optarg; opts.outfile = musl_optarg;
break; break;
case 'P': case 'P':
opts.palout = true; opts.palout = true;
break; break;
case 'p': case 'p':
opts.palfile = optarg; opts.palfile = musl_optarg;
break; break;
case 'T': case 'T':
opts.tilemapout = true; opts.tilemapout = true;
break; break;
case 't': case 't':
opts.tilemapfile = optarg; opts.tilemapfile = musl_optarg;
break; break;
case 'u': case 'u':
opts.unique = true; opts.unique = true;
@@ -145,15 +145,15 @@ int main(int argc, char *argv[])
opts.verbose = true; opts.verbose = true;
break; break;
case 'x': case 'x':
opts.trim = strtoul(optarg, NULL, 0); opts.trim = strtoul(musl_optarg, NULL, 0);
break; break;
default: default:
print_usage(); print_usage();
/* NOTREACHED */ /* NOTREACHED */
} }
} }
argc -= optind; argc -= musl_optind;
argv += optind; argv += musl_optind;
if (argc == 0) { if (argc == 0) {
fputs("FATAL: no input files\n", stderr); fputs("FATAL: no input files\n", stderr);

View File

@@ -217,23 +217,23 @@ int main(int argc, char *argv[])
isWRA0Mode = true; isWRA0Mode = true;
break; break;
case 'l': case 'l':
linkerScriptName = optarg; linkerScriptName = musl_optarg;
break; break;
case 'm': case 'm':
mapFileName = optarg; mapFileName = musl_optarg;
break; break;
case 'n': case 'n':
symFileName = optarg; symFileName = musl_optarg;
break; break;
case 'O': case 'O':
overlayFileName = optarg; overlayFileName = musl_optarg;
break; break;
case 'o': case 'o':
outputFileName = optarg; outputFileName = musl_optarg;
break; break;
case 'p': case 'p':
value = strtoul(optarg, &endptr, 0); value = strtoul(musl_optarg, &endptr, 0);
if (optarg[0] == '\0' || *endptr != '\0') { if (musl_optarg[0] == '\0' || *endptr != '\0') {
error(NULL, 0, "Invalid argument for option 'p'"); error(NULL, 0, "Invalid argument for option 'p'");
value = 0xFF; value = 0xFF;
} }
@@ -245,7 +245,7 @@ int main(int argc, char *argv[])
break; break;
case 's': case 's':
/* FIXME: nobody knows what this does, figure it out */ /* FIXME: nobody knows what this does, figure it out */
(void)optarg; (void)musl_optarg;
warning(NULL, 0, "Nobody has any idea what `-s` does"); warning(NULL, 0, "Nobody has any idea what `-s` does");
break; break;
case 't': case 't':
@@ -271,7 +271,7 @@ int main(int argc, char *argv[])
} }
} }
int curArgIndex = optind; int curArgIndex = musl_optind;
/* If no input files were specified, the user must have screwed up */ /* If no input files were specified, the user must have screwed up */
if (curArgIndex == argc) { if (curArgIndex == argc) {

View File

@@ -260,11 +260,11 @@ static void readSymbol(FILE *file, struct Symbol *symbol,
* @param fileName The filename to report in errors * @param fileName The filename to report in errors
* @param i The number of the patch to report in errors * @param i The number of the patch to report in errors
*/ */
static void readPatch(FILE *file, struct Patch *patch, char const *fileName, static void readPatch(FILE *file, struct Patch *patch, char const *fileName, char const *sectName,
char const *sectName, uint32_t i, uint32_t i, struct Section *fileSections[], struct FileStackNode fileNodes[])
struct Section *fileSections[], struct FileStackNode fileNodes[])
{ {
uint32_t nodeID; uint32_t nodeID;
uint8_t type;
tryReadlong(nodeID, file, tryReadlong(nodeID, file,
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s node ID: %s", "%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s node ID: %s",
@@ -279,14 +279,14 @@ static void readPatch(FILE *file, struct Patch *patch, char const *fileName,
tryReadlong(patch->pcSectionID, file, tryReadlong(patch->pcSectionID, file,
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s", "%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s",
fileName, sectName, i); fileName, sectName, i);
patch->pcSection = patch->pcSectionID == -1 ? NULL patch->pcSection = patch->pcSectionID == -1 ? NULL : fileSections[patch->pcSectionID];
: fileSections[patch->pcSectionID];
tryReadlong(patch->pcOffset, file, tryReadlong(patch->pcOffset, file,
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s", "%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s PC offset: %s",
fileName, sectName, i); fileName, sectName, i);
tryGetc(patch->type, file, tryGetc(type, file,
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s type: %s", "%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s type: %s",
fileName, sectName, i); fileName, sectName, i);
patch->type = type;
tryReadlong(patch->rpnSize, file, tryReadlong(patch->rpnSize, file,
"%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s RPN size: %s", "%s: Unable to read \"%s\"'s patch #%" PRIu32 "'s RPN size: %s",
fileName, sectName, i); fileName, sectName, i);
@@ -349,6 +349,8 @@ static void readSection(FILE *file, struct Section *section, char const *fileNam
section->bank = tmp; section->bank = tmp;
tryGetc(byte, file, "%s: Cannot read \"%s\"'s alignment: %s", tryGetc(byte, file, "%s: Cannot read \"%s\"'s alignment: %s",
fileName, section->name); fileName, section->name);
if (byte > 16)
byte = 16;
section->isAlignFixed = byte != 0; section->isAlignFixed = byte != 0;
section->alignMask = (1 << byte) - 1; section->alignMask = (1 << byte) - 1;
tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s alignment offset: %s", tryReadlong(tmp, file, "%s: Cannot read \"%s\"'s alignment offset: %s",
@@ -435,7 +437,7 @@ static void readAssertion(FILE *file, struct Assertion *assert,
char const *fileName, uint32_t i, char const *fileName, uint32_t i,
struct Section *fileSections[], struct FileStackNode fileNodes[]) struct Section *fileSections[], struct FileStackNode fileNodes[])
{ {
char assertName[sizeof("Assertion #" EXPAND_AND_STR(UINT32_MAX))]; char assertName[sizeof("Assertion #4294967295")]; // UINT32_MAX
snprintf(assertName, sizeof(assertName), "Assertion #%" PRIu32, i); snprintf(assertName, sizeof(assertName), "Assertion #%" PRIu32, i);

View File

@@ -17,7 +17,13 @@
#include "extern/err.h" #include "extern/err.h"
FILE * outputFile; #include "linkdefs.h"
#include "platform.h" // MIN_NB_ELMS
#define BANK_SIZE 0x4000
FILE *outputFile;
FILE *overlayFile; FILE *overlayFile;
FILE *symFile; FILE *symFile;
FILE *mapFile; FILE *mapFile;
@@ -35,6 +41,18 @@ static struct {
} *banks; } *banks;
} sections[SECTTYPE_INVALID]; } sections[SECTTYPE_INVALID];
/* Defines the order in which types are output to the sym and map files */
static enum SectionType typeMap[SECTTYPE_INVALID] = {
SECTTYPE_ROM0,
SECTTYPE_ROMX,
SECTTYPE_VRAM,
SECTTYPE_SRAM,
SECTTYPE_WRAM0,
SECTTYPE_WRAMX,
SECTTYPE_OAM,
SECTTYPE_HRAM
};
void out_AddSection(struct Section const *section) void out_AddSection(struct Section const *section)
{ {
static uint32_t maxNbBanks[] = { static uint32_t maxNbBanks[] = {
@@ -60,8 +78,7 @@ void out_AddSection(struct Section const *section)
sections[section->type].banks = sections[section->type].banks =
realloc(sections[section->type].banks, realloc(sections[section->type].banks,
sizeof(*sections[0].banks) * minNbBanks); sizeof(*sections[0].banks) * minNbBanks);
for (uint32_t i = sections[section->type].nbBanks; for (uint32_t i = sections[section->type].nbBanks; i < minNbBanks; i++) {
i < minNbBanks; i++) {
sections[section->type].banks[i].sections = NULL; sections[section->type].banks[i].sections = NULL;
sections[section->type].banks[i].zeroLenSections = NULL; sections[section->type].banks[i].zeroLenSections = NULL;
} }
@@ -103,43 +120,63 @@ struct Section const *out_OverlappingSection(struct Section const *section)
/** /**
* Performs sanity checks on the overlay file. * Performs sanity checks on the overlay file.
* @return The number of ROM banks in the overlay file
*/ */
static void checkOverlay(void) static uint32_t checkOverlaySize(void)
{ {
if (!overlayFile) if (!overlayFile)
return; return 0;
if (fseek(overlayFile, 0, SEEK_END) != 0) { if (fseek(overlayFile, 0, SEEK_END) != 0) {
warnx("Overlay file is not seekable, cannot check if properly formed"); warnx("Overlay file is not seekable, cannot check if properly formed");
return; return 0;
} }
long overlaySize = ftell(overlayFile); long overlaySize = ftell(overlayFile);
if (overlaySize % 0x4000)
errx(1, "Overlay file must have a size multiple of 0x4000");
/* Reset back to beginning */ /* Reset back to beginning */
fseek(overlayFile, 0, SEEK_SET); fseek(overlayFile, 0, SEEK_SET);
uint32_t nbOverlayBanks = overlaySize / 0x4000 - 1; if (overlaySize % BANK_SIZE)
errx(1, "Overlay file must have a size multiple of 0x4000");
if (nbOverlayBanks < 1) uint32_t nbOverlayBanks = overlaySize / BANK_SIZE;
if (is32kMode && nbOverlayBanks != 2)
errx(1, "Overlay must be exactly 0x8000 bytes large");
if (nbOverlayBanks < 2)
errx(1, "Overlay must be at least 0x8000 bytes large"); errx(1, "Overlay must be at least 0x8000 bytes large");
if (nbOverlayBanks > sections[SECTTYPE_ROMX].nbBanks) { return nbOverlayBanks;
}
/**
* Expand sections[SECTTYPE_ROMX].banks to cover all the overlay banks.
* This ensures that writeROM will output each bank, even if some are not
* covered by any sections.
* @param nbOverlayBanks The number of banks in the overlay file
*/
static void coverOverlayBanks(uint32_t nbOverlayBanks)
{
/* 2 if is32kMode, 1 otherwise */
uint32_t nbRom0Banks = maxsize[SECTTYPE_ROM0] / BANK_SIZE;
/* Discount ROM0 banks to avoid outputting too much */
uint32_t nbUncoveredBanks = nbOverlayBanks - nbRom0Banks > sections[SECTTYPE_ROMX].nbBanks
? nbOverlayBanks - nbRom0Banks
: 0;
if (nbUncoveredBanks > sections[SECTTYPE_ROMX].nbBanks) {
sections[SECTTYPE_ROMX].banks = sections[SECTTYPE_ROMX].banks =
realloc(sections[SECTTYPE_ROMX].banks, realloc(sections[SECTTYPE_ROMX].banks,
sizeof(*sections[SECTTYPE_ROMX].banks) * sizeof(*sections[SECTTYPE_ROMX].banks) * nbUncoveredBanks);
nbOverlayBanks);
if (!sections[SECTTYPE_ROMX].banks) if (!sections[SECTTYPE_ROMX].banks)
err(1, "Failed to realloc banks for overlay"); err(1, "Failed to realloc banks for overlay");
for (uint32_t i = sections[SECTTYPE_ROMX].nbBanks; for (uint32_t i = sections[SECTTYPE_ROMX].nbBanks; i < nbUncoveredBanks; i++) {
i < nbOverlayBanks; i++) {
sections[SECTTYPE_ROMX].banks[i].sections = NULL; sections[SECTTYPE_ROMX].banks[i].sections = NULL;
sections[SECTTYPE_ROMX].banks[i].zeroLenSections = NULL; sections[SECTTYPE_ROMX].banks[i].zeroLenSections = NULL;
} }
sections[SECTTYPE_ROMX].nbBanks = nbOverlayBanks; sections[SECTTYPE_ROMX].nbBanks = nbUncoveredBanks;
} }
} }
@@ -195,16 +232,19 @@ static void writeROM(void)
outputFile = openFile(outputFileName, "wb"); outputFile = openFile(outputFileName, "wb");
overlayFile = openFile(overlayFileName, "rb"); overlayFile = openFile(overlayFileName, "rb");
checkOverlay(); uint32_t nbOverlayBanks = checkOverlaySize();
if (nbOverlayBanks > 0)
coverOverlayBanks(nbOverlayBanks);
if (outputFile) { if (outputFile) {
if (sections[SECTTYPE_ROM0].nbBanks > 0) if (sections[SECTTYPE_ROM0].nbBanks > 0)
writeBank(sections[SECTTYPE_ROM0].banks[0].sections, writeBank(sections[SECTTYPE_ROM0].banks[0].sections,
0x0000, 0x4000); startaddr[SECTTYPE_ROM0], maxsize[SECTTYPE_ROM0]);
for (uint32_t i = 0 ; i < sections[SECTTYPE_ROMX].nbBanks; i++) for (uint32_t i = 0 ; i < sections[SECTTYPE_ROMX].nbBanks; i++)
writeBank(sections[SECTTYPE_ROMX].banks[i].sections, writeBank(sections[SECTTYPE_ROMX].banks[i].sections,
0x4000, 0x4000); startaddr[SECTTYPE_ROMX], maxsize[SECTTYPE_ROMX]);
} }
closeFile(outputFile); closeFile(outputFile);
@@ -296,12 +336,13 @@ static void writeSymBank(struct SortedSections const *bankSections)
/** /**
* Write a bank's contents to the map file * Write a bank's contents to the map file
* @param bankSections The bank's sections * @param bankSections The bank's sections
* @return The bank's slack space
*/ */
static void writeMapBank(struct SortedSections const *sectList, static uint16_t writeMapBank(struct SortedSections const *sectList,
enum SectionType type, uint32_t bank) enum SectionType type, uint32_t bank)
{ {
if (!mapFile) if (!mapFile)
return; return 0;
struct SortedSection const *section = sectList->sections; struct SortedSection const *section = sectList->sections;
struct SortedSection const *zeroLenSection = sectList->zeroLenSections; struct SortedSection const *zeroLenSection = sectList->zeroLenSections;
@@ -340,6 +381,34 @@ static void writeMapBank(struct SortedSections const *sectList,
else else
fprintf(mapFile, " SLACK: $%04" PRIx16 " byte%s\n\n", slack, fprintf(mapFile, " SLACK: $%04" PRIx16 " byte%s\n\n", slack,
slack == 1 ? "" : "s"); slack == 1 ? "" : "s");
return slack;
}
/**
* Write the total slack space by section type to the map file
* @param slackMap The total slack space by section type
*/
static void writeMapSlack(uint32_t slackMap[MIN_NB_ELMS(SECTTYPE_INVALID)])
{
if (!mapFile)
return;
fputs("FREE:\n", mapFile);
for (uint8_t i = 0; i < SECTTYPE_INVALID; i++) {
enum SectionType type = typeMap[i];
// Do not output slack space for VRAM or OAM
if (type == SECTTYPE_VRAM || type == SECTTYPE_OAM)
continue;
if (sections[type].nbBanks > 0) {
fprintf(mapFile, " %s: $%04" PRIx32 " byte%s in %" PRIu32 " bank%s\n",
typeNames[type], slackMap[type], slackMap[type] == 1 ? "" : "s",
sections[type].nbBanks, sections[type].nbBanks == 1 ? "" : "s");
}
}
} }
/** /**
@@ -350,16 +419,7 @@ static void writeSymAndMap(void)
if (!symFileName && !mapFileName) if (!symFileName && !mapFileName)
return; return;
enum SectionType typeMap[SECTTYPE_INVALID] = { uint32_t slackMap[SECTTYPE_INVALID] = {0};
SECTTYPE_ROM0,
SECTTYPE_ROMX,
SECTTYPE_VRAM,
SECTTYPE_SRAM,
SECTTYPE_WRAM0,
SECTTYPE_WRAMX,
SECTTYPE_OAM,
SECTTYPE_HRAM
};
symFile = openFile(symFileName, "w"); symFile = openFile(symFileName, "w");
mapFile = openFile(mapFileName, "w"); mapFile = openFile(mapFileName, "w");
@@ -370,16 +430,16 @@ static void writeSymAndMap(void)
for (uint8_t i = 0; i < SECTTYPE_INVALID; i++) { for (uint8_t i = 0; i < SECTTYPE_INVALID; i++) {
enum SectionType type = typeMap[i]; enum SectionType type = typeMap[i];
if (sections[type].nbBanks > 0) { for (uint32_t bank = 0; bank < sections[type].nbBanks; bank++) {
for (uint32_t bank = 0; bank < sections[type].nbBanks; struct SortedSections const *sect = &sections[type].banks[bank];
bank++) {
writeSymBank(&sections[type].banks[bank]); writeSymBank(sect);
writeMapBank(&sections[type].banks[bank], slackMap[type] += writeMapBank(sect, type, bank);
type, bank);
}
} }
} }
writeMapSlack(slackMap);
closeFile(symFile); closeFile(symFile);
closeFile(mapFile); closeFile(mapFile);
} }

View File

@@ -18,50 +18,10 @@
#include "link/symbol.h" #include "link/symbol.h"
#include "linkdefs.h" #include "linkdefs.h"
#include "opmath.h"
#include "extern/err.h" #include "extern/err.h"
static int32_t asl(int32_t value, int32_t shiftamt); // Forward decl for below
static int32_t asr(int32_t value, int32_t shiftamt)
{
uint32_t uvalue = value;
// Get the easy cases out of the way
if (shiftamt == 0)
return value;
if (value == 0 || shiftamt <= -32)
return 0;
if (shiftamt > 31)
return (value < 0) ? -1 : 0;
if (shiftamt < 0)
return asl(value, -shiftamt);
if (value > 0)
return uvalue >> shiftamt;
{
// Calculate an OR mask for sign extension
// 1->0x80000000, 2->0xC0000000, ..., 31->0xFFFFFFFE
uint32_t shiftamt_high_bits = -((uint32_t)1 << (32 - shiftamt));
return (uvalue >> shiftamt) | shiftamt_high_bits;
}
}
static int32_t asl(int32_t value, int32_t shiftamt)
{
// Repeat the easy cases here to avoid INT_MIN funny business
if (shiftamt == 0)
return value;
if (value == 0 || shiftamt >= 32)
return 0;
if (shiftamt < -31)
return (value < 0) ? -1 : 0;
if (shiftamt < 0)
return asr(value, -shiftamt);
return (uint32_t)value << shiftamt;
}
/* /*
* This is an "empty"-type stack. Apart from the actual values, we also remember * This is an "empty"-type stack. Apart from the actual values, we also remember
* whether the value is a placeholder inserted for error recovery. This allows * whether the value is a placeholder inserted for error recovery. This allows
@@ -219,7 +179,7 @@ static int32_t computeRPNExpr(struct Patch const *patch,
popRPN(); popRPN();
value = INT32_MAX; value = INT32_MAX;
} else { } else {
value = popRPN() / value; value = op_divide(popRPN(), value);
} }
break; break;
case RPN_MOD: case RPN_MOD:
@@ -231,12 +191,24 @@ static int32_t computeRPNExpr(struct Patch const *patch,
popRPN(); popRPN();
value = 0; value = 0;
} else { } else {
value = popRPN() % value; value = op_modulo(popRPN(), value);
} }
break; break;
case RPN_UNSUB: case RPN_UNSUB:
value = -popRPN(); value = -popRPN();
break; break;
case RPN_EXP:
value = popRPN();
if (value < 0) {
if (!isError)
error(patch->src, patch->lineNo, "Exponent by negative");
isError = true;
popRPN();
value = 0;
} else {
value = op_exponent(popRPN(), value);
}
break;
case RPN_OR: case RPN_OR:
value = popRPN() | popRPN(); value = popRPN() | popRPN();
@@ -288,11 +260,11 @@ static int32_t computeRPNExpr(struct Patch const *patch,
case RPN_SHL: case RPN_SHL:
value = popRPN(); value = popRPN();
value = asl(popRPN(), value); value = op_shift_left(popRPN(), value);
break; break;
case RPN_SHR: case RPN_SHR:
value = popRPN(); value = popRPN();
value = asr(popRPN(), value); value = op_shift_right(popRPN(), value);
break; break;
case RPN_BANK_SYM: case RPN_BANK_SYM:
@@ -490,9 +462,10 @@ static void applyFilePatches(struct Section *section, struct Section *dataSectio
/* `jr` is quite unlike the others... */ /* `jr` is quite unlike the others... */
if (patch->type == PATCHTYPE_JR) { if (patch->type == PATCHTYPE_JR) {
/* Target is relative to the byte *after* the operand */ // Offset is relative to the byte *after* the operand
// PC as operand to `jr` is lower than reference PC by 2
uint16_t address = patch->pcSection->org uint16_t address = patch->pcSection->org
+ patch->pcOffset + 1; + patch->pcOffset + 2;
int16_t jumpOffset = value - address; int16_t jumpOffset = value - address;
if (!isError && (jumpOffset < -128 || jumpOffset > 127)) if (!isError && (jumpOffset < -128 || jumpOffset > 127))

View File

@@ -39,62 +39,123 @@ void sect_ForEach(void (*callback)(struct Section *, void *), void *arg)
hash_ForEach(sections, forEach, &callbackArg); hash_ForEach(sections, forEach, &callbackArg);
} }
static void mergeSections(struct Section *target, struct Section *other, enum SectionModifier mod) static void checkSectUnionCompat(struct Section *target, struct Section *other)
{ {
if (target->type != other->type)
errx(1, "Section \"%s\" is defined with conflicting types %s and %s",
other->name,
typeNames[target->type], typeNames[other->type]);
if (other->isAddressFixed) { if (other->isAddressFixed) {
if (target->isAddressFixed) { if (target->isAddressFixed) {
if (target->org != other->org) if (target->org != other->org)
errx(1, "Section \"%s\" is defined with conflicting addresses $%" PRIx16 " and $%" PRIx16, errx(1, "Section \"%s\" is defined with conflicting addresses $%04"
PRIx16 " and $%04" PRIx16,
other->name, target->org, other->org); other->name, target->org, other->org);
} else if (target->isAlignFixed) { } else if (target->isAlignFixed) {
if ((other->org - target->alignOfs) & target->alignMask) if ((other->org - target->alignOfs) & target->alignMask)
errx(1, "Section \"%s\" is defined with conflicting %" PRIu16 "-byte alignment (offset %" PRIu16 ") and address $%" PRIx16, errx(1, "Section \"%s\" is defined with conflicting %" PRIu16
"-byte alignment (offset %" PRIu16 ") and address $%04" PRIx16,
other->name, target->alignMask + 1, other->name, target->alignMask + 1,
target->alignOfs, other->org); target->alignOfs, other->org);
} }
target->isAddressFixed = true; target->isAddressFixed = true;
target->org = other->org; target->org = other->org;
} else if (other->isAlignFixed) { } else if (other->isAlignFixed) {
if (target->isAddressFixed) { if (target->isAddressFixed) {
if ((target->org - other->alignOfs) & other->alignMask) if ((target->org - other->alignOfs) & other->alignMask)
errx(1, "Section \"%s\" is defined with conflicting address $%" PRIx16 " and %" PRIu16 "-byte alignment (offset %" PRIu16 ")", errx(1, "Section \"%s\" is defined with conflicting address $%04"
PRIx16 " and %" PRIu16 "-byte alignment (offset %" PRIu16 ")",
other->name, target->org, other->name, target->org,
other->alignMask + 1, other->alignOfs); other->alignMask + 1, other->alignOfs);
} else if (target->isAlignFixed } else if (target->isAlignFixed
&& (other->alignMask & target->alignOfs) && (other->alignMask & target->alignOfs)
!= (target->alignMask & other->alignOfs)) { != (target->alignMask & other->alignOfs)) {
errx(1, "Section \"%s\" is defined with conflicting %" PRIu16 "-byte alignment (offset %" PRIu16 ") and %" PRIu16 "-byte alignment (offset %" PRIu16 ")", errx(1, "Section \"%s\" is defined with conflicting %" PRIu16
other->name, target->alignMask + 1, "-byte alignment (offset %" PRIu16 ") and %" PRIu16
target->alignOfs, other->alignMask + 1, "-byte alignment (offset %" PRIu16 ")",
other->alignOfs); other->name, target->alignMask + 1, target->alignOfs,
} else if (!target->isAlignFixed other->alignMask + 1, other->alignOfs);
|| (other->alignMask > target->alignMask)) { } else if (!target->isAlignFixed || (other->alignMask > target->alignMask)) {
target->isAlignFixed = true; target->isAlignFixed = true;
target->alignMask = other->alignMask; target->alignMask = other->alignMask;
} }
} }
}
static void checkFragmentCompat(struct Section *target, struct Section *other)
{
if (other->isAddressFixed) {
uint16_t org = other->org - target->size;
if (target->isAddressFixed) {
if (target->org != org)
errx(1, "Section \"%s\" is defined with conflicting addresses $%04"
PRIx16 " and $%04" PRIx16,
other->name, target->org, other->org);
} else if (target->isAlignFixed) {
if ((org - target->alignOfs) & target->alignMask)
errx(1, "Section \"%s\" is defined with conflicting %" PRIu16
"-byte alignment (offset %" PRIu16 ") and address $%04" PRIx16,
other->name, target->alignMask + 1,
target->alignOfs, other->org);
}
target->isAddressFixed = true;
target->org = org;
} else if (other->isAlignFixed) {
int32_t ofs = (other->alignOfs - target->size) % (other->alignMask + 1);
if (ofs < 0)
ofs += other->alignMask + 1;
if (target->isAddressFixed) {
if ((target->org - ofs) & other->alignMask)
errx(1, "Section \"%s\" is defined with conflicting address $%04"
PRIx16 " and %" PRIu16 "-byte alignment (offset %" PRIu16 ")",
other->name, target->org,
other->alignMask + 1, other->alignOfs);
} else if (target->isAlignFixed
&& (other->alignMask & target->alignOfs) != (target->alignMask & ofs)) {
errx(1, "Section \"%s\" is defined with conflicting %" PRIu16
"-byte alignment (offset %" PRIu16 ") and %" PRIu16
"-byte alignment (offset %" PRIu16 ")",
other->name, target->alignMask + 1, target->alignOfs,
other->alignMask + 1, other->alignOfs);
} else if (!target->isAlignFixed || (other->alignMask > target->alignMask)) {
target->isAlignFixed = true;
target->alignMask = other->alignMask;
target->alignOfs = ofs;
}
}
}
static void mergeSections(struct Section *target, struct Section *other, enum SectionModifier mod)
{
// Common checks
if (target->type != other->type)
errx(1, "Section \"%s\" is defined with conflicting types %s and %s",
other->name, typeNames[target->type], typeNames[other->type]);
if (other->isBankFixed) { if (other->isBankFixed) {
if (!target->isBankFixed) { if (!target->isBankFixed) {
target->isBankFixed = true; target->isBankFixed = true;
target->bank = other->bank; target->bank = other->bank;
} else if (target->bank != other->bank) { } else if (target->bank != other->bank) {
errx(1, "Section \"%s\" is defined with conflicting banks %" PRIu32 " and %" PRIu32, errx(1, "Section \"%s\" is defined with conflicting banks %" PRIu32 " and %"
other->name, target->bank, other->bank); PRIu32, other->name, target->bank, other->bank);
} }
} }
switch (mod) { switch (mod) {
case SECTION_UNION: case SECTION_UNION:
checkSectUnionCompat(target, other);
if (other->size > target->size) if (other->size > target->size)
target->size = other->size; target->size = other->size;
break; break;
case SECTION_FRAGMENT: case SECTION_FRAGMENT:
checkFragmentCompat(target, other);
target->size += other->size; target->size += other->size;
other->offset = target->size - other->size; other->offset = target->size - other->size;
if (sect_HasData(target->type)) { if (sect_HasData(target->type)) {
@@ -167,20 +228,20 @@ static void doSanityChecks(struct Section *section, void *ptr)
fail("Section \"%s\" has an invalid type.", section->name); fail("Section \"%s\" has an invalid type.", section->name);
if (is32kMode && section->type == SECTTYPE_ROMX) { if (is32kMode && section->type == SECTTYPE_ROMX) {
if (section->isBankFixed && section->bank != 1) if (section->isBankFixed && section->bank != 1)
fail("%s: ROMX sections must be in bank 1 with option -t.", fail("%s: ROMX sections must be in bank 1 (if any) with option -t",
section->name); section->name);
else else
section->type = SECTTYPE_ROM0; section->type = SECTTYPE_ROM0;
} }
if (isWRA0Mode && section->type == SECTTYPE_WRAMX) { if (isWRA0Mode && section->type == SECTTYPE_WRAMX) {
if (section->isBankFixed && section->bank != 1) if (section->isBankFixed && section->bank != 1)
fail("%s: WRAMX sections must be in bank 1 with options -w or -d.", fail("%s: WRAMX sections must be in bank 1 with options -w or -d",
section->name); section->name);
else else
section->type = SECTTYPE_WRAMX; section->type = SECTTYPE_WRAMX;
} }
if (isDmgMode && section->type == SECTTYPE_VRAM && section->bank == 1) if (isDmgMode && section->type == SECTTYPE_VRAM && section->bank == 1)
fail("%s: VRAM bank 1 can't be used with option -d.", fail("%s: VRAM bank 1 can't be used with option -d",
section->name); section->name);
/* /*
@@ -191,17 +252,13 @@ static void doSanityChecks(struct Section *section, void *ptr)
section->isAlignFixed = false; section->isAlignFixed = false;
/* Too large an alignment may not be satisfiable */ /* Too large an alignment may not be satisfiable */
if (section->isAlignFixed if (section->isAlignFixed && (section->alignMask & startaddr[section->type]))
&& (section->alignMask & startaddr[section->type])) fail("%s: %s sections cannot be aligned to $%04" PRIx16 " bytes",
fail("%s: %s sections cannot be aligned to $%" PRIx16 " bytes", section->name, typeNames[section->type], section->alignMask + 1);
section->name, typeNames[section->type],
section->alignMask + 1);
uint32_t minbank = bankranges[section->type][0], uint32_t minbank = bankranges[section->type][0], maxbank = bankranges[section->type][1];
maxbank = bankranges[section->type][1];
if (section->isBankFixed && section->bank < minbank if (section->isBankFixed && section->bank < minbank && section->bank > maxbank)
&& section->bank > maxbank)
fail(minbank == maxbank fail(minbank == maxbank
? "Cannot place section \"%s\" in bank %" PRIu32 ", it must be %" PRIu32 ? "Cannot place section \"%s\" in bank %" PRIu32 ", it must be %" PRIu32
: "Cannot place section \"%s\" in bank %" PRIu32 ", it must be between %" PRIu32 " and %" PRIu32, : "Cannot place section \"%s\" in bank %" PRIu32 ", it must be between %" PRIu32 " and %" PRIu32,
@@ -219,35 +276,26 @@ static void doSanityChecks(struct Section *section, void *ptr)
section->isBankFixed = true; section->isBankFixed = true;
} }
if (section->isAlignFixed) { if (section->isAddressFixed) {
enum SectionType type = section->type;
/* It doesn't make sense to have both org and alignment set */ /* It doesn't make sense to have both org and alignment set */
if (section->isAddressFixed) { if (section->isAlignFixed) {
if (section->org & section->alignMask) if ((section->org & section->alignMask) != section->alignOfs)
fail("Section \"%s\"'s fixed address doesn't match its alignment", fail("Section \"%s\"'s fixed address doesn't match its alignment",
section->name); section->name);
section->isAlignFixed = false; section->isAlignFixed = false;
} else if ((endaddr(type) & section->alignMask)
== startaddr[type]) {
section->org = startaddr[type];
section->isAlignFixed = false;
section->isAddressFixed = true;
} }
}
if (section->isAddressFixed) {
/* Ensure the target address is valid */ /* Ensure the target address is valid */
if (section->org < startaddr[section->type] if (section->org < startaddr[section->type]
|| section->org > endaddr(section->type)) || section->org > endaddr(section->type))
fail("Section \"%s\"'s fixed address %#" PRIx16 " is outside of range [%#" PRIx16 "; %#" PRIx16 "]", fail("Section \"%s\"'s fixed address %#" PRIx16 " is outside of range [%#"
section->name, section->org, PRIx16 "; %#" PRIx16 "]", section->name, section->org,
startaddr[section->type], endaddr(section->type)); startaddr[section->type], endaddr(section->type));
if (section->org + section->size > endaddr(section->type) + 1) if (section->org + section->size > endaddr(section->type) + 1)
fail("Section \"%s\"'s end address %#" PRIx16 " is greater than last address %#" PRIx16, fail("Section \"%s\"'s end address %#" PRIx16
section->name, section->org + section->size, " is greater than last address %#" PRIx16, section->name,
endaddr(section->type) + 1); section->org + section->size, endaddr(section->type) + 1);
} }
#undef fail #undef fail

View File

@@ -13,11 +13,11 @@ uint16_t startaddr[] = {
}; };
uint16_t maxsize[] = { uint16_t maxsize[] = {
[SECTTYPE_ROM0] = 0x8000, [SECTTYPE_ROM0] = 0x8000, // patched to 0x4000 if !is32kMode
[SECTTYPE_ROMX] = 0x4000, [SECTTYPE_ROMX] = 0x4000,
[SECTTYPE_VRAM] = 0x2000, [SECTTYPE_VRAM] = 0x2000,
[SECTTYPE_SRAM] = 0x2000, [SECTTYPE_SRAM] = 0x2000,
[SECTTYPE_WRAM0] = 0x2000, [SECTTYPE_WRAM0] = 0x2000, // patched to 0x1000 if !isWRA0Mode
[SECTTYPE_WRAMX] = 0x1000, [SECTTYPE_WRAMX] = 0x1000,
[SECTTYPE_OAM] = 0x00A0, [SECTTYPE_OAM] = 0x00A0,
[SECTTYPE_HRAM] = 0x007F [SECTTYPE_HRAM] = 0x007F

88
src/opmath.c Normal file
View File

@@ -0,0 +1,88 @@
/*
* This file is part of RGBDS.
*
* Copyright (c) 1997-2021, RGBDS contributors.
*
* SPDX-License-Identifier: MIT
*/
/*
* Mathematical operators that don't reuse C's behavior
*/
#include <stdint.h>
#include "opmath.h"
int32_t op_divide(int32_t dividend, int32_t divisor)
{
// Adjust division to floor toward negative infinity,
// not truncate toward zero
return dividend / divisor - ((dividend % divisor < 0) != (divisor < 0));
}
int32_t op_modulo(int32_t dividend, int32_t divisor)
{
int32_t remainder = dividend % divisor;
// Adjust modulo to have the sign of the divisor,
// not the sign of the dividend
return remainder + divisor * ((remainder < 0) != (divisor < 0));
}
int32_t op_exponent(int32_t base, uint32_t power)
{
int32_t result = 1;
for (;;) {
if (power % 2)
result *= base;
power /= 2;
if (!power)
break;
base *= base;
}
return result;
}
int32_t op_shift_left(int32_t value, int32_t amount)
{
// Get the easy cases out of the way
if (amount == 0)
return value;
if (value == 0 || amount >= 32)
return 0;
if (amount < -31)
return (value < 0) ? -1 : 0;
if (amount < 0)
return op_shift_right(value, -amount);
// Use unsigned to force a bitwise shift
// Casting back is OK because the types implement two's complement behavior
return (uint32_t)value << amount;
}
int32_t op_shift_right(int32_t value, int32_t amount)
{
// Repeat the easy cases here to avoid INT_MIN funny business
if (amount == 0)
return value;
if (value == 0 || amount <= -32)
return 0;
if (amount > 31)
return (value < 0) ? -1 : 0;
if (amount < 0)
return op_shift_left(value, -amount);
if (value > 0)
return (uint32_t)value >> amount;
// Calculate an OR mask for sign extension
// 1->0x80000000, 2->0xC0000000, ..., 31->0xFFFFFFFE
uint32_t amount_high_bits = -(UINT32_C(1) << (32 - amount));
// The C standard leaves shifting right negative values
// undefined, so use a left shift manually sign-extended
return ((uint32_t)value >> amount) | amount_high_bits;
}

View File

@@ -41,7 +41,7 @@ LONG NumberOfSections ; The number of sections used in this file.
LONG NumberOfNodes ; The number of nodes contained in this file. LONG NumberOfNodes ; The number of nodes contained in this file.
REPT NumberOfNodes ; IMPORTANT NOTE: the nodes are actually written in REPT NumberOfNodes ; IMPORTANT NOTE: the nodes are actually written in
; **reverse** order, meaningthe node with ID 0 is ; **reverse** order, meaning the node with ID 0 is
; the last one in the file! ; the last one in the file!
LONG ParentID ; ID of the parent node, -1 means this is the root. LONG ParentID ; ID of the parent node, -1 means this is the root.
@@ -225,6 +225,7 @@ with some bytes being special prefixes for integers and symbols.
.It Li $03 Ta Li / operator .It Li $03 Ta Li / operator
.It Li $04 Ta Li % operator .It Li $04 Ta Li % operator
.It Li $05 Ta Li unary - .It Li $05 Ta Li unary -
.It Li $06 Ta Li ** operator
.It Li $10 Ta Li | operator .It Li $10 Ta Li | operator
.It Li $11 Ta Li & operator .It Li $11 Ta Li & operator
.It Li $12 Ta Li ^ operator .It Li $12 Ta Li ^ operator

View File

@@ -9,17 +9,24 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include "helpers.h"
#include "version.h" #include "version.h"
const char *get_package_version_string(void) const char *get_package_version_string(void)
{ {
static char s[50]; // The following conditional should be simplified by the compiler.
/* The following conditional should be simplified by the compiler. */
if (strlen(BUILD_VERSION_STRING) == 0) { if (strlen(BUILD_VERSION_STRING) == 0) {
snprintf(s, sizeof(s), "v%d.%d.%d", PACKAGE_VERSION_MAJOR, // Fallback if version string can't be obtained from Git
PACKAGE_VERSION_MINOR, PACKAGE_VERSION_PATCH); #ifndef PACKAGE_VERSION_RC
return s; return "v" EXPAND_AND_STR(PACKAGE_VERSION_MAJOR)
"." EXPAND_AND_STR(PACKAGE_VERSION_MINOR)
"." EXPAND_AND_STR(PACKAGE_VERSION_PATCH);
#else
return "v" EXPAND_AND_STR(PACKAGE_VERSION_MAJOR)
"." EXPAND_AND_STR(PACKAGE_VERSION_MINOR)
"." EXPAND_AND_STR(PACKAGE_VERSION_PATCH)
"-rc" EXPAND_AND_STR(PACKAGE_VERSION_RC);
#endif
} else { } else {
return BUILD_VERSION_STRING; return BUILD_VERSION_STRING;
} }

4
test/asm/.gitignore vendored
View File

@@ -1 +1,3 @@
quote\"file.* /quote\"file.*
/version.asm
/version.out

8
test/asm/align-16.asm Normal file
View File

@@ -0,0 +1,8 @@
SECTION "Byte", ROM0
db 2
SECTION "ROM0", ROM0, ALIGN[16]
db 1

View File

@@ -0,0 +1 @@


View File

@@ -0,0 +1,2 @@
SECTION "Tesst", ROM0, ALIGN[1,2]

View File

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

View File

2
test/asm/align-large.asm Normal file
View File

@@ -0,0 +1,2 @@
SECTION "You lost the game", ROM0[17]

0
test/asm/align-large.err Normal file
View File

0
test/asm/align-large.out Normal file
View File

View File

@@ -0,0 +1,18 @@
: ; Outside of section
SECTION "Anonymous label errors test", ROM0
db :-- ; Reference goes too far back
; Uncomment this if you're a badass with a *lot* of RAM
; REPT 2147483647
; :
; ENDR
; REPT 2147483647
; :
; ENDR
; db :+ ; OK
; db :++ ; Reference goes too far
:: ; Syntax error, can't export this

View File

@@ -0,0 +1,7 @@
ERROR: anon-label-bad.asm(2):
Label "!0" created outside of a SECTION
ERROR: anon-label-bad.asm(6):
Reference to anonymous label 2 before, when only 1 has been created so far
ERROR: anon-label-bad.asm(18):
syntax error, unexpected :
error: Assembly aborted (3 errors)!

View File

View File

@@ -0,0 +1,7 @@
ERROR: anon-label-bad.asm(2):
Label "!0" created outside of a SECTION
ERROR: anon-label-bad.asm(6):
Reference to anonymous label 2 before, when only 1 has been created so far
ERROR: anon-label-bad.asm(18):
syntax error
error: Assembly aborted (3 errors)!

12
test/asm/anon-label.asm Normal file
View File

@@ -0,0 +1,12 @@
SECTION "Anonymous label test", ROM0[0]
ld hl, :++
: ld a, [hli]
ldh [c], a
dec c
jr nz, :-
ret
:
dw $7FFF, $1061, $03E0, $58A5

0
test/asm/anon-label.err Normal file
View File

0
test/asm/anon-label.out Normal file
View File

BIN
test/asm/anon-label.out.bin Normal file

Binary file not shown.

View File

@@ -1,24 +1,23 @@
print_all: MACRO print_all: MACRO
REPT _NARG REPT _NARG
PRINTT " \1" PRINT " \1"
SHIFT SHIFT
ENDR ENDR
PRINTT "\n" PRINTLN
ENDM ENDM
print_some: MACRO print_some: MACRO
PRINTT "\1" PRINT "\1"
SHIFT 5 SHIFT 5
PRINTT "\2\6\9" PRINT "\2\6\9"
SHIFT 17 SHIFT 17
SHIFT SHIFT
PRINTT "\3\9" PRINT "\3\9"
ENDM ENDM
bad: MACRO bad: MACRO
shift _NARG - 1 shift _NARG - 1
PRINTT \1 PRINTLN \1
PRINTT "\n"
ENDM ENDM
bad_rept: MACRO bad_rept: MACRO
@@ -27,8 +26,7 @@ bad_rept: MACRO
shift shift
ENDR ENDR
ENDR ENDR
PRINTT \1 PRINTLN \1
PRINTT "\n"
ENDM ENDM
print_all This test, probably, passes\,, but who knows, ? print_all This test, probably, passes\,, but who knows, ?

View File

@@ -5,8 +5,7 @@ def_sect: macro
SECTION "\1", \2, BANK[\3] SECTION "\1", \2, BANK[\3]
ENDC ENDC
PRINTV BANK("\1") PRINTLN BANK("\1")
PRINTT "\n"
endm endm
def_sect ROM0_ok, ROM0 def_sect ROM0_ok, ROM0

View File

@@ -1,9 +1,9 @@
ERROR: bank.asm(14) -> bank.asm::def_sect(8): ERROR: bank.asm(13) -> bank.asm::def_sect(8):
Expected constant expression: Section "ROMX_bad"'s bank is not known Expected constant expression: Section "ROMX_bad"'s bank is not known
ERROR: bank.asm(16) -> bank.asm::def_sect(8): ERROR: bank.asm(15) -> bank.asm::def_sect(8):
Expected constant expression: Section "VRAM_bad"'s bank is not known Expected constant expression: Section "VRAM_bad"'s bank is not known
ERROR: bank.asm(18) -> bank.asm::def_sect(8): ERROR: bank.asm(17) -> bank.asm::def_sect(8):
Expected constant expression: Section "SRAM_bad"'s bank is not known Expected constant expression: Section "SRAM_bad"'s bank is not known
ERROR: bank.asm(21) -> bank.asm::def_sect(8): ERROR: bank.asm(20) -> bank.asm::def_sect(8):
Expected constant expression: Section "WRAMX_bad"'s bank is not known Expected constant expression: Section "WRAMX_bad"'s bank is not known
error: Assembly aborted (4 errors)! error: Assembly aborted (4 errors)!

View File

@@ -1,2 +1,2 @@
/* block comments containing /* throw warnings */ /* block comments containing /* throw warnings */
PRINTT "reachable\n" PRINTLN "reachable"

View File

@@ -1 +1 @@
PRINTT /* block comments must terminate before EOF PRINT /* block comments must terminate before EOF

View File

@@ -1,5 +1,5 @@
ERROR: block-comment-termination-error.asm(1): ERROR: block-comment-termination-error.asm(1):
Unterminated block comment Unterminated block comment
ERROR: block-comment-termination-error.asm(1): ERROR: block-comment-termination-error.asm(1):
syntax error syntax error, unexpected newline
error: Assembly aborted (2 errors)! error: Assembly aborted (2 errors)!

View File

@@ -0,0 +1,5 @@
ERROR: block-comment-termination-error.asm(1):
Unterminated block comment
ERROR: block-comment-termination-error.asm(1):
syntax error
error: Assembly aborted (2 errors)!

View File

@@ -1,5 +1,5 @@
PRINTT /* block comments are ignored // ** */ "hi\n" PRINTLN /* block comments are ignored // ** */ "hi"
PRINTT "block (/* ... */) comments at ends of line are fine\n" /* hi */ PRINTLN "block (/* ... */) comments at ends of line are fine" /* hi */
PRINTT /* block comments PRINTLN /* block comments
can span multiple lines can span multiple lines
*/ "mutliline\n" */ "mutliline"

View File

@@ -1,21 +1,21 @@
X = 42 X = 42
PRINTT "{X}\n" PRINTLN "{X}"
PRINTT "{x:X}\n" PRINTLN "{x:X}"
PRINTT "{X:X}\n" PRINTLN "{X:X}"
PRINTT "{d:X}\n" PRINTLN "{d:X}"
PRINTT "{b:X}\n" PRINTLN "{b:X}"
Y equ 1337 Y equ 1337
PRINTT "{b:Y}\n" PRINTLN "{b:Y}"
rsreset rsreset
R rb 0 R rb 0
PRINTT "{d:R}\n" PRINTLN "{d:R}"
S equs "You can't format me!" S equs "You can't format me!"
PRINTT "{X:S}\n" PRINTLN "{X:S}"
SECTION "Test", ROM0 SECTION "Test", ROM0
Label: Label:
PRINTT "{x:Label}\n" PRINTLN "{x:Label}"
PRINTT "{x:@}\n" PRINTLN "{x:@}"

View File

@@ -1,5 +1,5 @@
ERROR: bracketed-symbols.asm(16): ERROR: bracketed-symbols.asm(16):
Print types are only allowed for numbers Formatting string as type 'X'
ERROR: bracketed-symbols.asm(20): ERROR: bracketed-symbols.asm(20):
"Label" does not have a constant value "Label" does not have a constant value
ERROR: bracketed-symbols.asm(21): ERROR: bracketed-symbols.asm(21):

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

@@ -0,0 +1,23 @@
FOR V, 1, 100
PRINTLN "- {d:V}"
IF V == 5
PRINTLN "stop"
BREAK
ENDC
PRINTLN "cont"
ENDR
WARN "done {d:V}"
rept 1
break
; skips invalid code
!@#$%
elif: macro
invalid
endr
warn "OK"
rept 1
if 1
break
no endc
endr
println "done"

6
test/asm/break.err Normal file
View File

@@ -0,0 +1,6 @@
warning: break.asm(9): [-Wuser]
done 5
warning: break.asm(17): [-Wuser]
OK
FATAL: break.asm(18) -> break.asm::REPT~1(22):
Ended block with 1 unterminated IF construct

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

@@ -0,0 +1,10 @@
- 1
cont
- 2
cont
- 3
cont
- 4
cont
- 5
stop

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