rgbds
rgbds copied to clipboard
Replace __LINE__ and __FILE__ with something that handles inclusions and repetitions
These preprocessor symbols are a lot more useful than #1068 and #1072 would have you believe. Here are a few examples*:
* Tested with RGBDS 0.5.2, so they might not work exactly the same in other versions. Also note that many of the macros here require that a symbol named DEBUG
be defined (otherwise they function as no-ops).
Anonymous Sections
With anonymous sections, you can create a section without having to give it a name. It's a lot more convenient than having to maintain unique section names. This is especially true if you like to put each function into its own section so that the linker can rearrange things more effectively.
This macro will create a section with an auto-generated name. The name is generated using __FILE__
, among other preprocessor symbols.
;* Defines an anonymous section.
;* Uses the same arguments as the "section" command.
macro makeSection
def file\@ equs __FILE__ ; Remove quotes
section "__anonymousSection_{s:file\@}\@", \#
purge file\@
endm
Example usage:
makeSection romx[$4567], bank[2]
myFunction::
; << code here >>
ret
This results in the creation a section with a name something like __anonymousSection_engine/entity.z80_u38
.
Also note the required indentation before makeSection
(it is a macro, after all).
Debug Logging
Emulators such as BGB allow you to log messages to the emulator's internal debug console. This is done by using a special instruction/data sequence. Here are some macros for outputting to BGB's debug console. It makes use of anonymous sections so that the actual string data gets stored elsewhere in a non-fixed bank.
;* Writes a line of text to the debug output.
;* If DEBUG is not defined, then no action is taken.
;*
;* @param strSource A label referring to the null-terminated string that will be written.
macro debug_writeLineAt
if def(DEBUG)
ld d, d
jr .debug_writeLineAt_stringData_end\@
dw $6464
dw $0001
dw (\1)
dw bank(\1)
.debug_writeLineAt_stringData_end\@
endc
endm
;* Writes a line of text to the debug output.
;* If DEBUG is not defined, then no action is taken.
;*
;* @param str A string constant that will be written.
macro debug_writeLine
if def(DEBUG)
pushs
makeSection romx
__string_\@: db \1, 0
pops
debug_writeLineAt __string_\@
endc
endm
Example usage:
myFunction:
debug_writeLine "This is a debug message."
ret
Ouput: This is a debug message.
Error Handling
Here, anonymous sections and debug_writeLine
are used together with __FILE__
and __LINE__
to print an error message to the debug console and then hang the program. Another variant of debug_fail
exists with a condition code argument, but I feel it's a little too verbose to include here.
macro debug_break
if def(DEBUG)
ld b, b
endc
endm
;* Prints an error and halts the program.
;* @param message The message to print.
;* @param [lineNumber]
;* The current line number.
;* This should take the form {__LINE__} (including the curly braces).
macro debug_fail
if def(DEBUG)
if _NARG == 1
; Remove quotes.
def message\@ equs \1
debug_writeLine "Error at {s:__FILE__}: {s:message\@}"
purge message\@
elif _NARG == 2
; Remove quotes.
def message\@ equs \1
def line\@ equ \2
debug_writeLine "Error at {s:__FILE__} line {u:line\@}: {s:message\@}"
purge line\@
purge message\@
else
fail "Invalid number of arguments."
endc
di
.debug_fail_failureLoop\@
debug_break
jr .debug_fail_failureLoop\@
endc
endm
Example usage:
myFunction:
debug_fail "Oops! Something went wrong.", {__LINE__}
ret
Output: Error at "Engine/Map.z80" line 681: Oops! Something went wrong.
Note that the line number preprocessor symbol must be written out at the call site as shown above. Otherwise the line number reported by the error message may not be correct. This is the one caveat of using this method.
... anyway, that's all for now.
Doing def file\@ equs __FILE__ / ... / purge file\@
is a clever way to get at the "real" filename in a macro, but it's still surprising and inconvenient compared to how __FILE__
works in C. And __LINE__
, as you found, really does have to be passed as a literal {__LINE__}
argument every time, which is no better than just writing your own line identifier every time:
If you want to use real filenames in a macro, you could instead put redef FILENAME EQUS "foo.asm"
at the top of every file whose name you care about, and just use FILENAME
as-is inside macros.
In the case of "just writing your own line identifier every time", you'd need some sort of preprocessor to change the literal 840 every time a line is added or deleted above line 840 so that the value remains correct.
Rather than un-deprecating these, let’s add new versions that act the way we want? Specifically a line constant that tracks includes and repetitions
Rather than un-deprecating these, let’s add new versions that act the way we want? Specifically a line constant that tracks includes and repetitions
I have to admit this is probably a better course of action. And it would remove the need for weird workarounds.
For functions, a simple "unique name" is, by necessity, the function's:
SECTION "MyFunction", ROM0 ; (Aside: using this pattern for ROM0 functions is a good idea, but ROMX is not.)
MyFunction::
; snip
ret
For those debug messages, and for a unique name across the whole program, __FILE__
is ill-advised:
$ bat a.asm b.asm common.inc
───────┬───────────────────────────────────────────────────
│ File: a.asm
───────┼───────────────────────────────────────────────────
1 │ INCLUDE "common.inc"
───────┴───────────────────────────────────────────────────
───────┬───────────────────────────────────────────────────
│ File: b.asm
───────┼───────────────────────────────────────────────────
1 │ INCLUDE "common.inc"
───────┴───────────────────────────────────────────────────
───────┬───────────────────────────────────────────────────
│ File: common.inc
───────┼───────────────────────────────────────────────────
1 │ MACRO make_section
2 │ def anon_file\@ equs __FILE__
3 │ SECTION "Anonymous section {anon_file\@}", \#
4 │ purge anon_file\@
5 │ ENDM
6 │
7 │ make_section ROM0
───────┴───────────────────────────────────────────────────
$ rgbasm a.asm -o a.o && rgbasm b.asm -o b.o && rgblink a.o b.o -m /dev/stdout
warning: a.asm(1) -> common.inc(7) -> common.inc::make_section(2): [-Wobsolete]
`__FILE__` is deprecated
warning: b.asm(1) -> common.inc(7) -> common.inc::make_section(2): [-Wobsolete]
`__FILE__` is deprecated
error: Section name "Anonymous section common.inc" is already in use
What would be useful would be the output file name... and even then, it's iffy. (The root source file name may not be enough, for example if I run rgbasm -o v1.o configurable.asm -DVERSION=1
and rgbasm -o v2.o configurable.asm -DVERSION=2
, though maybe that's niche enough that we can ignore it.) I'd rather advocate for some kind of unique name passed by the build system, e.g. -DUNIT_NAME=main
.
What would be useful would be the output file name... and even then, it's iffy. (...) I'd rather advocate for some kind of unique name passed by the build system, e.g.
-DUNIT_NAME=main
.
@ISSOtm So would you be okay with a predefined __OUTPUT_FILE__
string? (One edge case: if there was no -o
specified, would this be empty or undefined?) Or shall we just close this?