Richard Suchenwirth 2013-11-30 - dis2asm, the converter from the language produced by tcl::unsupported::disassemble. to the one accepted by tcl::unsupported::assemble, TAL, had a troublesome issue: it could do for and while loops, but not foreach. Disassembling a foreach loop came out as
% aproc f x {foreach j $x {puts $j}} ByteCode 0x0x9eaad68, refCt 1, epoch 16, interp 0x0x9e057a0 (epoch 16) Source "foreach j $x {puts $j}" Cmds 2, src 22, inst 29, litObjs 2, aux 1, stkDepth 2, code/src 0.00 Proc 0x0x9ebb820, refCt 1, args 1, compiled locals 4 slot 0, scalar, arg, "x" slot 1, scalar, temp slot 2, scalar, temp slot 3, scalar, "j" Exception ranges 1, depth 1: 0: level 0, loop, pc 17-22, continue 10, break 26 Commands 2: 1: pc 0-27, src 0-21 2: pc 17-22, src 14-20 Command 1: "foreach j $x {puts $j}" (0) loadScalar1 %v0 # var "x" (2) storeScalar1 %v1 # temp var 1 (4) pop (5) foreach_start4 0 [data=[%v1], loop=%v2 it%v1 [%v3]] (10) foreach_step4 0 [data=[%v1], loop=%v2 it%v1 [%v3]] (15) jumpFalse1 +11 # pc 26 Command 2: "puts $j" (17) push1 0 # "puts" (19) loadScalar1 %v3 # var "j" (21) invokeStk1 2 (23) pop (24) jump1 -14 # pc 10 (26) push1 1 # "" (28) done
and in TAL there are no instructions foreach_start (see (5)) or foreach_step (see (10)). But I wanted them so badly...
Luckily it was weekend, and I had some time on my hands - so I made this my fun project of the day. At first, I manually coded a working solution in TAL:
proc foreachtest {x {_i ""} {_l ""}} {asm { load x listLength store _l push -1 store _i label loop; push puts load x load _i listIndex invokeStk 2 pop incrImm _i +1 load _l lt jumpTrue loop pop pop push {} }} foreachtest {a b c d}
This uses two local variables, _i as index into the input list, and _l to hold the length of the list. The code at the push invocation did not match the disassembly above. so as next step I restructured the code to look exactly like the disassembly, except for extra instructions to replace foreach_start and foreach_step - I considered them macros. So here is the second version. that meets that expectation:
proc foreachtest {x {i ""} {2 ""} {1 ""}} {asm { load x store 1 pop #foreach_start ;# push -1 ;# store 2 ;# pop ;# label L10 #foreach_step ;# incrImm 2 +1 ;# load 1 ;# load 2 ;# listIndex ;# store i ;# pop ;# load 1 ;# listLength ;# lt ;# jumpFalse L26 push puts load i invokeStk 2 pop jump L10 label L26 push {} }} foreachtest {e f g h}
The macros are comments, followed by their expansions also marked by a trailing comment. In the local variables, I accepted the numeric names
* "1" (for a local copy of the list to be iterated over, in case it was changed in the loop) and * "2" (for the index into the list),
which are valid in Tcl as well. "i" now holds the current list element, like it should. The llength can be checked at runtime, no variable needed for that.