Version 0 of dis2asm does macros

Updated 2013-11-30 10:54:56 by suchenwi

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.