enso wrote:
In fasm on x86 and ARM, I used recursive macro definitions extensively to build dictionaries for Forth-like systems. For every word in the bootstrap kernel the HEAD macro was redefined to include its previous definition with the new head appended and linked to previous one. In the end, the macro was executed, creating hundreds of words perfectly linked and ready to go! I learned that trick from the creators of RetroForth, I think (before they decided to use some kind of a VM and the whole project became useless for me). Creating a bootstrap Forth environment with macros is surprisingly impossible with some assemblers.
In particular, you want to keep the pointer to the last word in a variable after its defined, so that the next one can create a link pointer to it. Some assemblers just can't keep that variable set correctly. In many, you cant ORG <variable> or <expression>, so you can't adjust the output pointer or calculate the position of objects in memory dynamically.
In my '816 Forth, the header macro is:
Code:
HEADER: MACRO NAME, precedence ; Lay down name and link fields. NAME is a quoted string or string expression.
IF HEADERS? && ! OMIT_HEADERS ; If HEADERS is true and OMIT_HEADERS is false,
; then go ahead lay down the header.
last_NFA: SETL new_NFA
new_NFA: SETL $
DFB precedence | {npc-$-1}, NAME
npc: ; Use this label for the calculation of the name length byte above.
IF $ & 1 ; If next addr is odd,
DFB 0 ; add a nul byte before you
ENDI
DWL last_NFA ; lay down the link field.
ELSE
IF $ & 1
DFB 0 ; Even if headers are not allowed,
ENDI ; you should still align.
ENDI
ENDM
;-------------------
where HEADERS?, OMIT_HEADERS, last_NFA, and new_NFA are assembler variable set and changed as many times as you like by SETL (SET Label value). The two variables HEADERS? and OMIT_HEADERS offer three levels of headersless code: locally headerless (like for just a word or two), global, and, in between, a range that's wider than just local. (I know the macro is kind of ugly, but macros tend to be that way, which is one reason we like to make them macros and hide that stuff.) Use of the header macro is like:
Code:
HEADER "1+", NOT_IMMEDIATE ; ( n -- n+1 )
_1ADD: PRIMITIVE
INC 0,X
GO_NEXT
;-------------------
where PRIMITIVE just lays down the address of the INC 0,X as the CFA, and the GO_NEXT macro makes it easy to change all the JMP NEXT's for different versions of NEXT like with or without interrupt support, do single-stepping, program-trace, etc..
Quote:
And anyway, because HXA can nest macros the reason you want file inclusion in them is moot!
Yes, macro nesting is nice. Since C32 doesn't have it, I resorted to the INCLude files as a work-around, and then ran into the problem that required another work-around.