Hi uxn friends,
I've been thinking for a while about how one might implement Forth-style
IF...THEN clauses. e.g.
#01 #00 NEQ IF LIT "X #18 DEO THEN
This is hard to do, because the memory offsets to branch to aren't known
in advance, and, since labels can't be programmatically generated at
this time, having macros with hard-coded labels also doesn't really
work.
This morning I managed to get something working! The idea is to have the
IF statement expand to a subroutine call that walks the code inside the
IF...THEN to find the THEN statement (a sentinel value of 0xFF), fills
in that offset on the JCN call, and rewrites the first 3 bytes of the IF
statement to do the actual comparison logic.
Nice things about this approach:
- Each IF...THEN statement has zero bytes of memory overhead. The 3-byte
call to generate the IF...THEN statement becomes the "#01 NEQ"
instructions.
- After the first run of an IF...THEN statement, the resulting rewritten
code has zero runtime overhead.
Downsides:
- There is a runtime overhead in "compiling" each IF...THEN statement on
its first run, proportional to the length of the body.
- It will break if someone uses the SFT2kr instruction (0xFF) inside of
an IF...THEN statement. :')
- A relative 1-byte jump is made, so conditional bodies must be less
than around 255 bytes. This was an intentional choice to save bytes;
this implementation could be adapted to use absolute addresses!
Finally, here is the code, with some example code demonstrating its use:
---
( Proof of Concept of a Forth-style IF...THEN word pair )
( June 2024 // Kira Oakley )
%IF { IF-gen #00 JCN }
%THEN { ff }
|0100
#01 IF LIT "T EMIT THEN
#00 @loop
INC
LIT "Q EMIT
DUP #10 NEQ IF ,loop JMP THEN
BRK
@EMIT
#18 DEO
JMP2r
@IF-gen
( get PC back at where this word was called )
STH2kr
( loop, looking for the FF sentinel value )
#0000
ADD2k LDA
#ff EQU #04 JCN ( jump out of loop )
INC2 #f4 JMP ( loop back to the ADD2k instruction )
( rewrite FF to POPk (no-op) )
ADD2k #82 ROT ROT STA
( write the offset of the sentinel as the JCN target, which is back 2 bytes )
#0002 SUB2 NIP ROT ROT INC2 STA
( re-write the 3 bytes of "IF-gen" being called to be "LIT 01 NEQ" )
#80 STH2kr #fffd ADD2 STA
#01 STH2kr #fffe ADD2 STA
#09 STH2kr #ffff ADD2 STA
( push back return stack's PC by 3 bytes )
LIT2r fffd ADD2r
( yay, all done! )
JMP2r
---
I don't think it would be hard to adapt this approach to implement an
ELSE clause, DO...LOOP loops, and possibly other control structures.
This was fun to hack on and puzzle out. :) I'm glad that uxn exists.
Open to positivity, and constructive comments & corrections.
Warmth,
~ Kira
Hey Kira,
That's a nice idea and implementation!
The IF THEN case is very useful:
12 = IF FILL-CARTON THEN
In uxntal, the lack of jump-on-zero often creates these double equality
checks. I noticed reading your email how macros encapsulation conflict
with the lambdas synthax.
The IF THEN example above in Uxntal is as follow, making it a negative
check which looks a bit confusing at first, maybe except to people who
write z80:
#0c NEQ ?{ FILL-CARTON }
Unfortunately, there's no way to create macros that will be able to
generate these lambdas..
%IF { #00 EQU ?{ }
%THEN { } }
Just doesn't work, so I think you found a really nice way to work around
that :)
Dll
On 2024-06-16 12:51, Kira Oakley wrote:
> Hi uxn friends,> > I've been thinking for a while about how one might implement Forth-style> IF...THEN clauses. e.g.> > #01 #00 NEQ IF LIT "X #18 DEO THEN> > This is hard to do, because the memory offsets to branch to aren't known> in advance, and, since labels can't be programmatically generated at> this time, having macros with hard-coded labels also doesn't really> work.> > This morning I managed to get something working! The idea is to have the> IF statement expand to a subroutine call that walks the code inside the> IF...THEN to find the THEN statement (a sentinel value of 0xFF), fills> in that offset on the JCN call, and rewrites the first 3 bytes of the IF> statement to do the actual comparison logic.> > Nice things about this approach:> - Each IF...THEN statement has zero bytes of memory overhead. The 3-byte> call to generate the IF...THEN statement becomes the "#01 NEQ"> instructions.> - After the first run of an IF...THEN statement, the resulting rewritten> code has zero runtime overhead.> > Downsides:> - There is a runtime overhead in "compiling" each IF...THEN statement on> its first run, proportional to the length of the body.> - It will break if someone uses the SFT2kr instruction (0xFF) inside of> an IF...THEN statement. :')> - A relative 1-byte jump is made, so conditional bodies must be less> than around 255 bytes. This was an intentional choice to save bytes;> this implementation could be adapted to use absolute addresses!> > Finally, here is the code, with some example code demonstrating its use:> > ---> > ( Proof of Concept of a Forth-style IF...THEN word pair )> ( June 2024 // Kira Oakley )> > %IF { IF-gen #00 JCN }> %THEN { ff }> > |0100> > #01 IF LIT "T EMIT THEN> > #00 @loop> INC> LIT "Q EMIT> DUP #10 NEQ IF ,loop JMP THEN> > BRK> > > @EMIT> #18 DEO> JMP2r> > > @IF-gen> ( get PC back at where this word was called )> STH2kr> ( loop, looking for the FF sentinel value )> #0000> ADD2k LDA> #ff EQU #04 JCN ( jump out of loop )> INC2 #f4 JMP ( loop back to the ADD2k instruction )> ( rewrite FF to POPk (no-op) )> ADD2k #82 ROT ROT STA> ( write the offset of the sentinel as the JCN target, which is back 2 bytes )> #0002 SUB2 NIP ROT ROT INC2 STA> ( re-write the 3 bytes of "IF-gen" being called to be "LIT 01 NEQ" )> #80 STH2kr #fffd ADD2 STA> #01 STH2kr #fffe ADD2 STA> #09 STH2kr #ffff ADD2 STA> ( push back return stack's PC by 3 bytes )> LIT2r fffd ADD2r> ( yay, all done! )> JMP2r> > ---> > I don't think it would be hard to adapt this approach to implement an> ELSE clause, DO...LOOP loops, and possibly other control structures.> > This was fun to hack on and puzzle out. :) I'm glad that uxn exists.> > Open to positivity, and constructive comments & corrections.> > Warmth,> > ~ Kira>
Devine,
Thanks.
Oh neat, lambdas look interesting. They are new to me since I last
looked at tal.
Given what you wrote & my very limited understanding of conditional
lambdas, it seems one could just do
---
%IF { #01 NEQ }
#0c EQU IF ?{ FILL-CARTON }
---
to get a nice-ish positive-check syntax.
It would be nice if macros could utilize lambdas! Perhaps characters
inside of macro definitions could be escaped? e.g.
---
%IF { #00 EQU \?\{ }
%THEN { \} }
---
This mechanism would allow any other future reserved characters to also
appear within macros without issue.
Warmth,
~ Kira
On 06/16 14:43, Hundred Rabbits wrote:
> Hey Kira,> > That's a nice idea and implementation!> > The IF THEN case is very useful:> > 12 = IF FILL-CARTON THEN> > In uxntal, the lack of jump-on-zero often creates these double equality> checks. I noticed reading your email how macros encapsulation conflict with> the lambdas synthax.> > The IF THEN example above in Uxntal is as follow, making it a negative check> which looks a bit confusing at first, maybe except to people who write z80:> > #0c NEQ ?{ FILL-CARTON }> > Unfortunately, there's no way to create macros that will be able to generate> these lambdas..> > %IF { #00 EQU ?{ }> %THEN { } }> > Just doesn't work, so I think you found a really nice way to work around> that :)> > Dll> > On 2024-06-16 12:51, Kira Oakley wrote:> > Hi uxn friends,> > > > I've been thinking for a while about how one might implement Forth-style> > IF...THEN clauses. e.g.> > > > #01 #00 NEQ IF LIT "X #18 DEO THEN> > > > This is hard to do, because the memory offsets to branch to aren't known> > in advance, and, since labels can't be programmatically generated at> > this time, having macros with hard-coded labels also doesn't really> > work.> > > > This morning I managed to get something working! The idea is to have the> > IF statement expand to a subroutine call that walks the code inside the> > IF...THEN to find the THEN statement (a sentinel value of 0xFF), fills> > in that offset on the JCN call, and rewrites the first 3 bytes of the IF> > statement to do the actual comparison logic.> > > > Nice things about this approach:> > - Each IF...THEN statement has zero bytes of memory overhead. The 3-byte> > call to generate the IF...THEN statement becomes the "#01 NEQ"> > instructions.> > - After the first run of an IF...THEN statement, the resulting rewritten> > code has zero runtime overhead.> > > > Downsides:> > - There is a runtime overhead in "compiling" each IF...THEN statement on> > its first run, proportional to the length of the body.> > - It will break if someone uses the SFT2kr instruction (0xFF) inside of> > an IF...THEN statement. :')> > - A relative 1-byte jump is made, so conditional bodies must be less> > than around 255 bytes. This was an intentional choice to save bytes;> > this implementation could be adapted to use absolute addresses!> > > > Finally, here is the code, with some example code demonstrating its use:> > > > ---> > > > ( Proof of Concept of a Forth-style IF...THEN word pair )> > ( June 2024 // Kira Oakley )> > > > %IF { IF-gen #00 JCN }> > %THEN { ff }> > > > |0100> > > > #01 IF LIT "T EMIT THEN> > > > #00 @loop> > INC> > LIT "Q EMIT> > DUP #10 NEQ IF ,loop JMP THEN> > > > BRK> > > > > > @EMIT> > #18 DEO> > JMP2r> > > > > > @IF-gen> > ( get PC back at where this word was called )> > STH2kr> > ( loop, looking for the FF sentinel value )> > #0000> > ADD2k LDA> > #ff EQU #04 JCN ( jump out of loop )> > INC2 #f4 JMP ( loop back to the ADD2k instruction )> > ( rewrite FF to POPk (no-op) )> > ADD2k #82 ROT ROT STA> > ( write the offset of the sentinel as the JCN target, which is back 2 bytes )> > #0002 SUB2 NIP ROT ROT INC2 STA> > ( re-write the 3 bytes of "IF-gen" being called to be "LIT 01 NEQ" )> > #80 STH2kr #fffd ADD2 STA> > #01 STH2kr #fffe ADD2 STA> > #09 STH2kr #ffff ADD2 STA> > ( push back return stack's PC by 3 bytes )> > LIT2r fffd ADD2r> > ( yay, all done! )> > JMP2r> > > > ---> > > > I don't think it would be hard to adapt this approach to implement an> > ELSE clause, DO...LOOP loops, and possibly other control structures.> > > > This was fun to hack on and puzzle out. :) I'm glad that uxn exists.> > > > Open to positivity, and constructive comments & corrections.> > > > Warmth,> > > > ~ Kira> >
Oh dang, that escape character is an excellent idea!
Let me try it out :)
On 2024-06-16 16:34, Kira Oakley wrote:
> Devine,> > Thanks.> > Oh neat, lambdas look interesting. They are new to me since I last> looked at tal.> > Given what you wrote & my very limited understanding of conditional> lambdas, it seems one could just do> > ---> > %IF { #01 NEQ }> > #0c EQU IF ?{ FILL-CARTON }> > ---> > to get a nice-ish positive-check syntax.> > It would be nice if macros could utilize lambdas! Perhaps characters> inside of macro definitions could be escaped? e.g.> > ---> > %IF { #00 EQU \?\{ }> %THEN { \} }> > ---> > This mechanism would allow any other future reserved characters to also> appear within macros without issue.> > Warmth,> > ~ Kira> > > On 06/16 14:43, Hundred Rabbits wrote:>> Hey Kira,>>>> That's a nice idea and implementation!>>>> The IF THEN case is very useful:>>>> 12 = IF FILL-CARTON THEN>>>> In uxntal, the lack of jump-on-zero often creates these double equality>> checks. I noticed reading your email how macros encapsulation conflict with>> the lambdas synthax.>>>> The IF THEN example above in Uxntal is as follow, making it a negative check>> which looks a bit confusing at first, maybe except to people who write z80:>>>> #0c NEQ ?{ FILL-CARTON }>>>> Unfortunately, there's no way to create macros that will be able to generate>> these lambdas..>>>> %IF { #00 EQU ?{ }>> %THEN { } }>>>> Just doesn't work, so I think you found a really nice way to work around>> that :)>>>> Dll>>>> On 2024-06-16 12:51, Kira Oakley wrote:>>> Hi uxn friends,>>>>>> I've been thinking for a while about how one might implement Forth-style>>> IF...THEN clauses. e.g.>>>>>> #01 #00 NEQ IF LIT "X #18 DEO THEN>>>>>> This is hard to do, because the memory offsets to branch to aren't known>>> in advance, and, since labels can't be programmatically generated at>>> this time, having macros with hard-coded labels also doesn't really>>> work.>>>>>> This morning I managed to get something working! The idea is to have the>>> IF statement expand to a subroutine call that walks the code inside the>>> IF...THEN to find the THEN statement (a sentinel value of 0xFF), fills>>> in that offset on the JCN call, and rewrites the first 3 bytes of the IF>>> statement to do the actual comparison logic.>>>>>> Nice things about this approach:>>> - Each IF...THEN statement has zero bytes of memory overhead. The 3-byte>>> call to generate the IF...THEN statement becomes the "#01 NEQ">>> instructions.>>> - After the first run of an IF...THEN statement, the resulting rewritten>>> code has zero runtime overhead.>>>>>> Downsides:>>> - There is a runtime overhead in "compiling" each IF...THEN statement on>>> its first run, proportional to the length of the body.>>> - It will break if someone uses the SFT2kr instruction (0xFF) inside of>>> an IF...THEN statement. :')>>> - A relative 1-byte jump is made, so conditional bodies must be less>>> than around 255 bytes. This was an intentional choice to save bytes;>>> this implementation could be adapted to use absolute addresses!>>>>>> Finally, here is the code, with some example code demonstrating its use:>>>>>> --->>>>>> ( Proof of Concept of a Forth-style IF...THEN word pair )>>> ( June 2024 // Kira Oakley )>>>>>> %IF { IF-gen #00 JCN }>>> %THEN { ff }>>>>>> |0100>>>>>> #01 IF LIT "T EMIT THEN>>>>>> #00 @loop>>> INC>>> LIT "Q EMIT>>> DUP #10 NEQ IF ,loop JMP THEN>>>>>> BRK>>>>>>>>> @EMIT>>> #18 DEO>>> JMP2r>>>>>>>>> @IF-gen>>> ( get PC back at where this word was called )>>> STH2kr>>> ( loop, looking for the FF sentinel value )>>> #0000>>> ADD2k LDA>>> #ff EQU #04 JCN ( jump out of loop )>>> INC2 #f4 JMP ( loop back to the ADD2k instruction )>>> ( rewrite FF to POPk (no-op) )>>> ADD2k #82 ROT ROT STA>>> ( write the offset of the sentinel as the JCN target, which is back 2 bytes )>>> #0002 SUB2 NIP ROT ROT INC2 STA>>> ( re-write the 3 bytes of "IF-gen" being called to be "LIT 01 NEQ" )>>> #80 STH2kr #fffd ADD2 STA>>> #01 STH2kr #fffe ADD2 STA>>> #09 STH2kr #ffff ADD2 STA>>> ( push back return stack's PC by 3 bytes )>>> LIT2r fffd ADD2r>>> ( yay, all done! )>>> JMP2r>>>>>> --->>>>>> I don't think it would be hard to adapt this approach to implement an>>> ELSE clause, DO...LOOP loops, and possibly other control structures.>>>>>> This was fun to hack on and puzzle out. :) I'm glad that uxn exists.>>>>>> Open to positivity, and constructive comments & corrections.>>>>>> Warmth,>>>>>> ~ Kira>>>
Hallo,
Apologies for jumping in to your thread out of nowhere Kira....
Devine, why does a macro body need to be in braces at all? Is it just to simplify the parser somehow? Instead of introducing additional escapes in the macro body, wouldn't it be cleaner to make the macro body be everything between the initial spacing following the macro name declaration and the very next newline? Like this:
%IF #00 EQU ?{
%THEN }
Better yet, an assembler could support forth-like constructs in the same way that forth does with (equivalent of) branch and 0branch operations and back filling address arguments at addresses stored on the stack while assembling flow control mnemonics?
Further, --and I haven't thought through implications at all, so there could be way more to this than I'm anticipating-- but intuitively, I think that if macros were implemented as assembly-time lambdas and the stack state was fully specified, then it should be possible to implement a feature complete and extensible assembler with a dumb macro processor that executes those assembly-time lambdas along with a set of macros that would be able to handle all the drifblim features as well as any "special forms" such as loops and conditionals.
Cheers,
Gary
> On Jun 16, 2024, at 6:08 PM, Hundred Rabbits <rabbits@100r.co> wrote:> > Oh dang, that escape character is an excellent idea!> Let me try it out :)> >> On 2024-06-16 16:34, Kira Oakley wrote:>> Devine,>> Thanks.>> Oh neat, lambdas look interesting. They are new to me since I last>> looked at tal.>> Given what you wrote & my very limited understanding of conditional>> lambdas, it seems one could just do>> --->> %IF { #01 NEQ }>> #0c EQU IF ?{ FILL-CARTON }>> --->> to get a nice-ish positive-check syntax.>> It would be nice if macros could utilize lambdas! Perhaps characters>> inside of macro definitions could be escaped? e.g.>> --->> %IF { #00 EQU \?\{ }>> %THEN { \} }>> --->> This mechanism would allow any other future reserved characters to also>> appear within macros without issue.>> Warmth,>> ~ Kira>>> On 06/16 14:43, Hundred Rabbits wrote:>>> Hey Kira,>>> >>> That's a nice idea and implementation!>>> >>> The IF THEN case is very useful:>>> >>> 12 = IF FILL-CARTON THEN>>> >>> In uxntal, the lack of jump-on-zero often creates these double equality>>> checks. I noticed reading your email how macros encapsulation conflict with>>> the lambdas synthax.>>> >>> The IF THEN example above in Uxntal is as follow, making it a negative check>>> which looks a bit confusing at first, maybe except to people who write z80:>>> >>> #0c NEQ ?{ FILL-CARTON }>>> >>> Unfortunately, there's no way to create macros that will be able to generate>>> these lambdas..>>> >>> %IF { #00 EQU ?{ }>>> %THEN { } }>>> >>> Just doesn't work, so I think you found a really nice way to work around>>> that :)>>> >>> Dll>>> >>> On 2024-06-16 12:51, Kira Oakley wrote:>>>> Hi uxn friends,>>>> >>>> I've been thinking for a while about how one might implement Forth-style>>>> IF...THEN clauses. e.g.>>>> >>>> #01 #00 NEQ IF LIT "X #18 DEO THEN>>>> >>>> This is hard to do, because the memory offsets to branch to aren't known>>>> in advance, and, since labels can't be programmatically generated at>>>> this time, having macros with hard-coded labels also doesn't really>>>> work.>>>> >>>> This morning I managed to get something working! The idea is to have the>>>> IF statement expand to a subroutine call that walks the code inside the>>>> IF...THEN to find the THEN statement (a sentinel value of 0xFF), fills>>>> in that offset on the JCN call, and rewrites the first 3 bytes of the IF>>>> statement to do the actual comparison logic.>>>> >>>> Nice things about this approach:>>>> - Each IF...THEN statement has zero bytes of memory overhead. The 3-byte>>>> call to generate the IF...THEN statement becomes the "#01 NEQ">>>> instructions.>>>> - After the first run of an IF...THEN statement, the resulting rewritten>>>> code has zero runtime overhead.>>>> >>>> Downsides:>>>> - There is a runtime overhead in "compiling" each IF...THEN statement on>>>> its first run, proportional to the length of the body.>>>> - It will break if someone uses the SFT2kr instruction (0xFF) inside of>>>> an IF...THEN statement. :')>>>> - A relative 1-byte jump is made, so conditional bodies must be less>>>> than around 255 bytes. This was an intentional choice to save bytes;>>>> this implementation could be adapted to use absolute addresses!>>>> >>>> Finally, here is the code, with some example code demonstrating its use:>>>> >>>> --->>>> >>>> ( Proof of Concept of a Forth-style IF...THEN word pair )>>>> ( June 2024 // Kira Oakley )>>>> >>>> %IF { IF-gen #00 JCN }>>>> %THEN { ff }>>>> >>>> |0100>>>> >>>> #01 IF LIT "T EMIT THEN>>>> >>>> #00 @loop>>>> INC>>>> LIT "Q EMIT>>>> DUP #10 NEQ IF ,loop JMP THEN>>>> >>>> BRK>>>> >>>> >>>> @EMIT>>>> #18 DEO>>>> JMP2r>>>> >>>> >>>> @IF-gen>>>> ( get PC back at where this word was called )>>>> STH2kr>>>> ( loop, looking for the FF sentinel value )>>>> #0000>>>> ADD2k LDA>>>> #ff EQU #04 JCN ( jump out of loop )>>>> INC2 #f4 JMP ( loop back to the ADD2k instruction )>>>> ( rewrite FF to POPk (no-op) )>>>> ADD2k #82 ROT ROT STA>>>> ( write the offset of the sentinel as the JCN target, which is back 2 bytes )>>>> #0002 SUB2 NIP ROT ROT INC2 STA>>>> ( re-write the 3 bytes of "IF-gen" being called to be "LIT 01 NEQ" )>>>> #80 STH2kr #fffd ADD2 STA>>>> #01 STH2kr #fffe ADD2 STA>>>> #09 STH2kr #ffff ADD2 STA>>>> ( push back return stack's PC by 3 bytes )>>>> LIT2r fffd ADD2r>>>> ( yay, all done! )>>>> JMP2r>>>> >>>> --->>>> >>>> I don't think it would be hard to adapt this approach to implement an>>>> ELSE clause, DO...LOOP loops, and possibly other control structures.>>>> >>>> This was fun to hack on and puzzle out. :) I'm glad that uxn exists.>>>> >>>> Open to positivity, and constructive comments & corrections.>>>> >>>> Warmth,>>>> >>>> ~ Kira>>>> >
The reason I deliminate the macros is that I found it was sometimes
practical to write it over many lines, for readability, but tbf I never
use macros anymore so it was merely an assumption. It's true that in
most case, macros tend to be just on one line and rather small! :)
Even in their current rather primitive state, macros are kind of tricky
to implement so I've meant to keep them small, but if you'd like to help
me write a pre-processor program in uxntal that can handle forth-like
constructs I'd love to talk about it with you :)
Keep me posted
On 2024-06-16 22:03, Gary V. Vaughan wrote:
> Hallo,> > Apologies for jumping in to your thread out of nowhere Kira....> > Devine, why does a macro body need to be in braces at all? Is it just to simplify the parser somehow? Instead of introducing additional escapes in the macro body, wouldn't it be cleaner to make the macro body be everything between the initial spacing following the macro name declaration and the very next newline? Like this:> > %IF #00 EQU ?{> %THEN }> > Better yet, an assembler could support forth-like constructs in the same way that forth does with (equivalent of) branch and 0branch operations and back filling address arguments at addresses stored on the stack while assembling flow control mnemonics?> > Further, --and I haven't thought through implications at all, so there could be way more to this than I'm anticipating-- but intuitively, I think that if macros were implemented as assembly-time lambdas and the stack state was fully specified, then it should be possible to implement a feature complete and extensible assembler with a dumb macro processor that executes those assembly-time lambdas along with a set of macros that would be able to handle all the drifblim features as well as any "special forms" such as loops and conditionals.> > Cheers,> Gary> >> On Jun 16, 2024, at 6:08 PM, Hundred Rabbits <rabbits@100r.co> wrote:>>>> Oh dang, that escape character is an excellent idea!>> Let me try it out :)>>>>> On 2024-06-16 16:34, Kira Oakley wrote:>>> Devine,>>> Thanks.>>> Oh neat, lambdas look interesting. They are new to me since I last>>> looked at tal.>>> Given what you wrote & my very limited understanding of conditional>>> lambdas, it seems one could just do>>> --->>> %IF { #01 NEQ }>>> #0c EQU IF ?{ FILL-CARTON }>>> --->>> to get a nice-ish positive-check syntax.>>> It would be nice if macros could utilize lambdas! Perhaps characters>>> inside of macro definitions could be escaped? e.g.>>> --->>> %IF { #00 EQU \?\{ }>>> %THEN { \} }>>> --->>> This mechanism would allow any other future reserved characters to also>>> appear within macros without issue.>>> Warmth,>>> ~ Kira>>>> On 06/16 14:43, Hundred Rabbits wrote:>>>> Hey Kira,>>>>>>>> That's a nice idea and implementation!>>>>>>>> The IF THEN case is very useful:>>>>>>>> 12 = IF FILL-CARTON THEN>>>>>>>> In uxntal, the lack of jump-on-zero often creates these double equality>>>> checks. I noticed reading your email how macros encapsulation conflict with>>>> the lambdas synthax.>>>>>>>> The IF THEN example above in Uxntal is as follow, making it a negative check>>>> which looks a bit confusing at first, maybe except to people who write z80:>>>>>>>> #0c NEQ ?{ FILL-CARTON }>>>>>>>> Unfortunately, there's no way to create macros that will be able to generate>>>> these lambdas..>>>>>>>> %IF { #00 EQU ?{ }>>>> %THEN { } }>>>>>>>> Just doesn't work, so I think you found a really nice way to work around>>>> that :)>>>>>>>> Dll>>>>>>>> On 2024-06-16 12:51, Kira Oakley wrote:>>>>> Hi uxn friends,>>>>>>>>>> I've been thinking for a while about how one might implement Forth-style>>>>> IF...THEN clauses. e.g.>>>>>>>>>> #01 #00 NEQ IF LIT "X #18 DEO THEN>>>>>>>>>> This is hard to do, because the memory offsets to branch to aren't known>>>>> in advance, and, since labels can't be programmatically generated at>>>>> this time, having macros with hard-coded labels also doesn't really>>>>> work.>>>>>>>>>> This morning I managed to get something working! The idea is to have the>>>>> IF statement expand to a subroutine call that walks the code inside the>>>>> IF...THEN to find the THEN statement (a sentinel value of 0xFF), fills>>>>> in that offset on the JCN call, and rewrites the first 3 bytes of the IF>>>>> statement to do the actual comparison logic.>>>>>>>>>> Nice things about this approach:>>>>> - Each IF...THEN statement has zero bytes of memory overhead. The 3-byte>>>>> call to generate the IF...THEN statement becomes the "#01 NEQ">>>>> instructions.>>>>> - After the first run of an IF...THEN statement, the resulting rewritten>>>>> code has zero runtime overhead.>>>>>>>>>> Downsides:>>>>> - There is a runtime overhead in "compiling" each IF...THEN statement on>>>>> its first run, proportional to the length of the body.>>>>> - It will break if someone uses the SFT2kr instruction (0xFF) inside of>>>>> an IF...THEN statement. :')>>>>> - A relative 1-byte jump is made, so conditional bodies must be less>>>>> than around 255 bytes. This was an intentional choice to save bytes;>>>>> this implementation could be adapted to use absolute addresses!>>>>>>>>>> Finally, here is the code, with some example code demonstrating its use:>>>>>>>>>> --->>>>>>>>>> ( Proof of Concept of a Forth-style IF...THEN word pair )>>>>> ( June 2024 // Kira Oakley )>>>>>>>>>> %IF { IF-gen #00 JCN }>>>>> %THEN { ff }>>>>>>>>>> |0100>>>>>>>>>> #01 IF LIT "T EMIT THEN>>>>>>>>>> #00 @loop>>>>> INC>>>>> LIT "Q EMIT>>>>> DUP #10 NEQ IF ,loop JMP THEN>>>>>>>>>> BRK>>>>>>>>>>>>>>> @EMIT>>>>> #18 DEO>>>>> JMP2r>>>>>>>>>>>>>>> @IF-gen>>>>> ( get PC back at where this word was called )>>>>> STH2kr>>>>> ( loop, looking for the FF sentinel value )>>>>> #0000>>>>> ADD2k LDA>>>>> #ff EQU #04 JCN ( jump out of loop )>>>>> INC2 #f4 JMP ( loop back to the ADD2k instruction )>>>>> ( rewrite FF to POPk (no-op) )>>>>> ADD2k #82 ROT ROT STA>>>>> ( write the offset of the sentinel as the JCN target, which is back 2 bytes )>>>>> #0002 SUB2 NIP ROT ROT INC2 STA>>>>> ( re-write the 3 bytes of "IF-gen" being called to be "LIT 01 NEQ" )>>>>> #80 STH2kr #fffd ADD2 STA>>>>> #01 STH2kr #fffe ADD2 STA>>>>> #09 STH2kr #ffff ADD2 STA>>>>> ( push back return stack's PC by 3 bytes )>>>>> LIT2r fffd ADD2r>>>>> ( yay, all done! )>>>>> JMP2r>>>>>>>>>> --->>>>>>>>>> I don't think it would be hard to adapt this approach to implement an>>>>> ELSE clause, DO...LOOP loops, and possibly other control structures.>>>>>>>>>> This was fun to hack on and puzzle out. :) I'm glad that uxn exists.>>>>>>>>>> Open to positivity, and constructive comments & corrections.>>>>>>>>>> Warmth,>>>>>>>>>> ~ Kira>>>>>>>
For multi-line macro bodies, the internal newline characters could be backslash escaped (like the c preprocessor for example). given the common case being one-line macros definitions.
While I’m pretty sure I could write an assembler *for* uxntal, I’m not fluent enough in it to implement a macro assembler *in* uxntal, but I have some ideas (maybe good!) about doing exactly this for my #DecemberAdventure vm. I think it would be fun to prototype it in a high level language first (maybe Lua or Python), which would be more agile in response to any changes that seem worth trying. I actually quite like the idea of a universal concatenative macro-assembler, that could translate any arbitrary stream of mnemonics (and arguments) into any arbitrary stream of bytes (bits?) according to the set of macros it loads at startup. And it opens the door to adding language features (i.e. flow control mnemonics) implemented entirely in macros too.
If I make any progress I’ll post to this list, and if it seems interesting to you, maybe an uxntal implementation would be worthwhile afterwards…
Cheers,
Gary
> On Jun 17, 2024, at 8:17 AM, Hundred Rabbits <rabbits@100r.co> wrote:> > The reason I deliminate the macros is that I found it was sometimes practical to write it over many lines, for readability, but tbf I never use macros anymore so it was merely an assumption. It's true that in most case, macros tend to be just on one line and rather small! :)> > Even in their current rather primitive state, macros are kind of tricky to implement so I've meant to keep them small, but if you'd like to help me write a pre-processor program in uxntal that can handle forth-like constructs I'd love to talk about it with you :)> > Keep me posted> > On 2024-06-16 22:03, Gary V. Vaughan wrote:>> Hallo,>> Apologies for jumping in to your thread out of nowhere Kira....>> Devine, why does a macro body need to be in braces at all? Is it just to simplify the parser somehow? Instead of introducing additional escapes in the macro body, wouldn't it be cleaner to make the macro body be everything between the initial spacing following the macro name declaration and the very next newline? Like this:>> %IF #00 EQU ?{>> %THEN }>> Better yet, an assembler could support forth-like constructs in the same way that forth does with (equivalent of) branch and 0branch operations and back filling address arguments at addresses stored on the stack while assembling flow control mnemonics?>> Further, --and I haven't thought through implications at all, so there could be way more to this than I'm anticipating-- but intuitively, I think that if macros were implemented as assembly-time lambdas and the stack state was fully specified, then it should be possible to implement a feature complete and extensible assembler with a dumb macro processor that executes those assembly-time lambdas along with a set of macros that would be able to handle all the drifblim features as well as any "special forms" such as loops and conditionals.>> Cheers,>> Gary>>> On Jun 16, 2024, at 6:08 PM, Hundred Rabbits <rabbits@100r.co> wrote:>>> >>> Oh dang, that escape character is an excellent idea!>>> Let me try it out :)>>> >>>> On 2024-06-16 16:34, Kira Oakley wrote:>>>> Devine,>>>> Thanks.>>>> Oh neat, lambdas look interesting. They are new to me since I last>>>> looked at tal.>>>> Given what you wrote & my very limited understanding of conditional>>>> lambdas, it seems one could just do>>>> --->>>> %IF { #01 NEQ }>>>> #0c EQU IF ?{ FILL-CARTON }>>>> --->>>> to get a nice-ish positive-check syntax.>>>> It would be nice if macros could utilize lambdas! Perhaps characters>>>> inside of macro definitions could be escaped? e.g.>>>> --->>>> %IF { #00 EQU \?\{ }>>>> %THEN { \} }>>>> --->>>> This mechanism would allow any other future reserved characters to also>>>> appear within macros without issue.>>>> Warmth,>>>> ~ Kira>>>>> On 06/16 14:43, Hundred Rabbits wrote:>>>>> Hey Kira,>>>>> >>>>> That's a nice idea and implementation!>>>>> >>>>> The IF THEN case is very useful:>>>>> >>>>> 12 = IF FILL-CARTON THEN>>>>> >>>>> In uxntal, the lack of jump-on-zero often creates these double equality>>>>> checks. I noticed reading your email how macros encapsulation conflict with>>>>> the lambdas synthax.>>>>> >>>>> The IF THEN example above in Uxntal is as follow, making it a negative check>>>>> which looks a bit confusing at first, maybe except to people who write z80:>>>>> >>>>> #0c NEQ ?{ FILL-CARTON }>>>>> >>>>> Unfortunately, there's no way to create macros that will be able to generate>>>>> these lambdas..>>>>> >>>>> %IF { #00 EQU ?{ }>>>>> %THEN { } }>>>>> >>>>> Just doesn't work, so I think you found a really nice way to work around>>>>> that :)>>>>> >>>>> Dll>>>>> >>>>> On 2024-06-16 12:51, Kira Oakley wrote:>>>>>> Hi uxn friends,>>>>>> >>>>>> I've been thinking for a while about how one might implement Forth-style>>>>>> IF...THEN clauses. e.g.>>>>>> >>>>>> #01 #00 NEQ IF LIT "X #18 DEO THEN>>>>>> >>>>>> This is hard to do, because the memory offsets to branch to aren't known>>>>>> in advance, and, since labels can't be programmatically generated at>>>>>> this time, having macros with hard-coded labels also doesn't really>>>>>> work.>>>>>> >>>>>> This morning I managed to get something working! The idea is to have the>>>>>> IF statement expand to a subroutine call that walks the code inside the>>>>>> IF...THEN to find the THEN statement (a sentinel value of 0xFF), fills>>>>>> in that offset on the JCN call, and rewrites the first 3 bytes of the IF>>>>>> statement to do the actual comparison logic.>>>>>> >>>>>> Nice things about this approach:>>>>>> - Each IF...THEN statement has zero bytes of memory overhead. The 3-byte>>>>>> call to generate the IF...THEN statement becomes the "#01 NEQ">>>>>> instructions.>>>>>> - After the first run of an IF...THEN statement, the resulting rewritten>>>>>> code has zero runtime overhead.>>>>>> >>>>>> Downsides:>>>>>> - There is a runtime overhead in "compiling" each IF...THEN statement on>>>>>> its first run, proportional to the length of the body.>>>>>> - It will break if someone uses the SFT2kr instruction (0xFF) inside of>>>>>> an IF...THEN statement. :')>>>>>> - A relative 1-byte jump is made, so conditional bodies must be less>>>>>> than around 255 bytes. This was an intentional choice to save bytes;>>>>>> this implementation could be adapted to use absolute addresses!>>>>>> >>>>>> Finally, here is the code, with some example code demonstrating its use:>>>>>> >>>>>> --->>>>>> >>>>>> ( Proof of Concept of a Forth-style IF...THEN word pair )>>>>>> ( June 2024 // Kira Oakley )>>>>>> >>>>>> %IF { IF-gen #00 JCN }>>>>>> %THEN { ff }>>>>>> >>>>>> |0100>>>>>> >>>>>> #01 IF LIT "T EMIT THEN>>>>>> >>>>>> #00 @loop>>>>>> INC>>>>>> LIT "Q EMIT>>>>>> DUP #10 NEQ IF ,loop JMP THEN>>>>>> >>>>>> BRK>>>>>> >>>>>> >>>>>> @EMIT>>>>>> #18 DEO>>>>>> JMP2r>>>>>> >>>>>> >>>>>> @IF-gen>>>>>> ( get PC back at where this word was called )>>>>>> STH2kr>>>>>> ( loop, looking for the FF sentinel value )>>>>>> #0000>>>>>> ADD2k LDA>>>>>> #ff EQU #04 JCN ( jump out of loop )>>>>>> INC2 #f4 JMP ( loop back to the ADD2k instruction )>>>>>> ( rewrite FF to POPk (no-op) )>>>>>> ADD2k #82 ROT ROT STA>>>>>> ( write the offset of the sentinel as the JCN target, which is back 2 bytes )>>>>>> #0002 SUB2 NIP ROT ROT INC2 STA>>>>>> ( re-write the 3 bytes of "IF-gen" being called to be "LIT 01 NEQ" )>>>>>> #80 STH2kr #fffd ADD2 STA>>>>>> #01 STH2kr #fffe ADD2 STA>>>>>> #09 STH2kr #ffff ADD2 STA>>>>>> ( push back return stack's PC by 3 bytes )>>>>>> LIT2r fffd ADD2r>>>>>> ( yay, all done! )>>>>>> JMP2r>>>>>> >>>>>> --->>>>>> >>>>>> I don't think it would be hard to adapt this approach to implement an>>>>>> ELSE clause, DO...LOOP loops, and possibly other control structures.>>>>>> >>>>>> This was fun to hack on and puzzle out. :) I'm glad that uxn exists.>>>>>> >>>>>> Open to positivity, and constructive comments & corrections.>>>>>> >>>>>> Warmth,>>>>>> >>>>>> ~ Kira>>>>>> >>> >