Skip to content

transpiler#77

Merged
mkobetic merged 4 commits intomainfrom
tpile
Mar 28, 2026
Merged

transpiler#77
mkobetic merged 4 commits intomainfrom
tpile

Conversation

@mkobetic
Copy link
Copy Markdown
Owner

@mkobetic mkobetic commented Mar 22, 2026

Basic idea is to emit a stream of tokens during normal compilation that should be relatively easy to transform into actual ITC code (by amforth-shell) given the list of known XT addresses (read from the symbol table). The example below shows the interaction between amshell and amforth compiling word fib followed by the transpiled result emitted by amshell:

|S|    2|: fib 
|O|    2|WW666962 X400428D4 X40000681
|S|    3|    dup 2 > if 
|O|    3|X400002D4 X40001E98 X400007BC X400011C0 X00000000 F400428E8
|S|    4|       dup 1- recurse swap 1- 1- recurse + exit 
|O|    4|X400002D4 X40000E64 X400428D4 X400000D4 X40000E64 X40000E64 X400428D4 X40000128 X400002A4
|S|    5|    then 
|O|    5|L400428E8
|S|    6|    drop 1 
|O|    6|X40000098 X40001E84
|S|    7|;
|O|    7|X40000288 END

# : fib
COLON "fib", FIB
    # dup 2 > if
    .word XT_DUP, XT_TWO, XT_GREATER, XT_DOCONDBRANCH, 1f
       # dup 1- recurse swap 1- 1- recurse + exit
       .word XT_DUP, XT_1MINUS, XT_FIB, XT_SWAP, XT_1MINUS, XT_1MINUS, XT_FIB, XT_PLUS, XT_FINISH
    # then
1:     # drop 1
    .word XT_DROP, XT_ONE
   # ;
   .word XT_EXIT
END FIB

Transpiler should be able to handle most words with few exceptions like :noname , <builds does> or postpone. Full list is maintained in the comment of tpile.s.

The amforth-shell side handles the transcription into ITC, it emits the ITC along the original Forth line (as a comment). It follows the offsets of the original lines to offset the ITC code. It collects the continuous block of comments before the word and emits it as a long description /* */ block comment after the ITC word header. Similarly it parses the line that starts the word definition to look for stack signature and following short description to emit with the ITC header, to be used in the refcard tables.

Here's a sample original with comments and the resulting ITC

\ Arena state is stored in the first record of the arena.
\ First cell is a counter with MSB set (so that it doesn't match any real RAM address).
\ Second cell is either -1 or 0. Erased dormant arena has both cells set to -1.
\ Current arena is the one with higher ID that is not -1.
: pvarena.init ( -- ) \ set pvarena to whichever arena should be current
    pvarena1 @ dup pvflash.erased <> if \ is arena1 dormant? (ID == pvlfash.erased)
        pvarena2 @ dup pvflash.erased <> if \ is arena2 dormant?
            \ (a1id a2id) higher ID wins
            < if pvarena2 to pvarena
            else pvarena1 to pvarena
            then
        else \ arena2 is dormant ( a1id pvlfash.erased )
            pvarena1 to pvarena
            2drop \ unused arena IDs
        then
    else \ arena1 is dormant ( pvlfash.erased )
        pvarena2 @ pvflash.erased <> if \ is arena2 dormant?
            pvarena2 to pvarena
        else \ arena2 is dormant
            \ both arenas are blank, initialize arena1 and use it.
            pvflash.erased invert $80000000 pvarena1 2!pvf
            pvarena1 to pvarena
        then
        drop \ unused arena1 ID
    then ;

# : pvarena.init ( -- ) \ set pvarena to whichever arena should be current
COLON "pvarena.init", PVARENADOTINIT /* ( -- ) set pvarena to whichever arena should be current */
/* Arena state is stored in the first record of the arena.
   First cell is a counter with MSB set (so that it doesn't match any real RAM address).
   Second cell is either -1 or 0. Erased dormant arena has both cells set to -1.
   Current arena is the one with higher ID that is not -1. */
    # pvarena1 @ dup pvflash.erased <> if \ is arena1 dormant? (ID == pvlfash.erased)
    .word XT_PVARENA1, XT_FETCH, XT_DUP, XT_PVFLASH_ERASED, XT_NOTEQUAL, XT_DOCONDBRANCH, 1f
        # pvarena2 @ dup pvflash.erased <> if \ is arena2 dormant?
        .word XT_PVARENA2, XT_FETCH, XT_DUP, XT_PVFLASH_ERASED, XT_NOTEQUAL, XT_DOCONDBRANCH, 2f
            # \ (a1id a2id) higher ID wins
            # < if pvarena2 to pvarena
            .word XT_LESS, XT_DOCONDBRANCH, 3f, XT_PVARENA2, XT_DOXLITERAL, 0x400055E8, XT_DEFER_STORE
            # else pvarena1 to pvarena
            .word XT_DOBRANCH, 4f
3:          .word XT_PVARENA1, XT_DOXLITERAL, 0x400055E8, XT_DEFER_STORE
            # then
4:         # else \ arena2 is dormant ( a1id pvlfash.erased )
        .word XT_DOBRANCH, 5f
2: 
            # pvarena1 to pvarena
            .word XT_PVARENA1, XT_DOXLITERAL, 0x400055E8, XT_DEFER_STORE
            # 2drop \ unused arena IDs
            .word XT_2DROP
        # then
5:     # else \ arena1 is dormant ( pvlfash.erased )
    .word XT_DOBRANCH, 6f
1: 
        # pvarena2 @ pvflash.erased <> if \ is arena2 dormant?
        .word XT_PVARENA2, XT_FETCH, XT_PVFLASH_ERASED, XT_NOTEQUAL, XT_DOCONDBRANCH, 7f
            # pvarena2 to pvarena
            .word XT_PVARENA2, XT_DOXLITERAL, 0x400055E8, XT_DEFER_STORE
        # else \ arena2 is dormant
        .word XT_DOBRANCH, 8f
7: 
            # \ both arenas are blank, initialize arena1 and use it.
            # pvflash.erased invert $80000000 pvarena1 2!pvf
            .word XT_PVFLASH_ERASED, XT_INVERT, XT_DOLITERAL, 0x80000000, XT_PVARENA1, XT_2STORE_PVF
            # pvarena1 to pvarena
            .word XT_PVARENA1, XT_DOXLITERAL, 0x400055E8, XT_DEFER_STORE
        # then
8:         # drop \ unused arena1 ID
        .word XT_DROP
    # then ;
6:  .word XT_EXIT
END PVARENADOTINIT

The transpiler can be compiled in or out with WANT_TRANSPILER config option. Amshell will report an error if #transpile is used without transpiler being available on the target.

> #transpile fib.frt
|D=#transpile fib.frt
|E=transpiling not supported by target
> 

@mkobetic mkobetic force-pushed the tpile branch 5 times, most recently from 5d89be2 to 4cc2356 Compare March 23, 2026 17:44
@@ -66,15 +66,6 @@ The DEBUG register is controlled through the USER variable `debug.next`, which d
# then
# ;
#
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moving this to new words/output.s so that the transpiler does not depend on debugger.

IMMED "until", UNTIL /* ( f -- )(C: a -- ) if f is true jump back to begin, otherwise leave the loop */
.word XT_QNOP
.word XT_DOLITERAL
.word XT_COMPILE
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use COMPILE like everything else, this way we don't have to do anything special for TPILE to handle until.

@mkobetic mkobetic force-pushed the tpile branch 2 times, most recently from d22732c to f30ed32 Compare March 26, 2026 14:23
@mkobetic mkobetic merged commit 1e2206f into main Mar 28, 2026
3 checks passed
@mkobetic mkobetic deleted the tpile branch March 28, 2026 00:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant