https://github.com/akkartik/mu/blob/main/linux/braces.subx

      1 # Structured control flow using break/loop rather than jump.
      2 #
      3 # To run (on Linux):
      4 #   $ ./translate_subx init.linux [012]*.subx subx-params.subx braces.subx
      5 #   $ mv a.elf braces
      6 #
      7 # Example 1:
      8 #   $ cat x.subx
      9 #   {
     10 #     7c/jump-if-< break/disp8
     11 #     74/jump-if-= loop/disp8
     12 #   }
     13 #   $ cat x.subx |braces
     14 #   @loop1:
     15 #     7c/jump-if-< @break1/disp8
     16 #     74/jump-if-= @loop1/disp8
     17 #   @break1:
     18 #
     19 # Example 2:
     20 #   $ cat x.subx
     21 #   {
     22 #     7c/jump-if-< break/disp8
     23 #   }
     24 #   {
     25 #     74/jump-if-= loop/disp8
     26 #   }
     27 #   $ cat x.subx |braces
     28 #   @loop1:
     29 #     7c/jump-if-< @break1/disp8
     30 #   @break1:
     31 #   @loop2:
     32 #     74/jump-if-= @loop2/disp8
     33 #   @break2:
     34 #
     35 # Example 3:
     36 #   $ cat x.subx
     37 #   {
     38 #     {
     39 #       74/jump-if-= loop/disp8
     40 #     }
     41 #     7c/jump-if-< loop/disp8
     42 #   }
     43 #   $ cat x.subx |braces
     44 #   @loop1:
     45 #     @loop2:
     46 #       74/jump-if-= @loop2/disp8
     47 #     @break2:
     48 #     7c/jump-if-< @loop1/disp8
     49 #   @break1:
     50
     51 == code
     52
     53 Entry:  # run tests if necessary, a REPL if not
     54     # . prologue
     55     89/<- %ebp 4/r32/esp
     56     # initialize heap
     57     (new-segment *Heap-size Heap)
     58     # if (argc <= 1) goto interactive
     59     81 7/subop/compare *ebp 1/imm32
     60     7e/jump-if-<= $subx-braces-main:interactive/disp8
     61     # if (argv[1] != "test")) goto interactive
     62     (kernel-string-equal? *(ebp+8) "test")  # => eax
     63     3d/compare-eax-and 0/imm32/false
     64     74/jump-if-= $subx-braces-main:interactive/disp8
     65     #
     66     (run-tests)
     67     # syscall(exit, *Num-test-failures)
     68     8b/-> *Num-test-failures 3/r32/ebx
     69     eb/jump $subx-braces-main:end/disp8
     70 $subx-braces-main:interactive:
     71     (subx-braces Stdin Stdout)
     72     # syscall(exit, 0)
     73     bb/copy-to-ebx 0/imm32
     74 $subx-braces-main:end:
     75     e8/call syscall_exit/disp32
     76
     77 subx-braces:  # in: (addr buffered-file), out: (addr buffered-file)
     78     # pseudocode:
     79     #   var line: (stream byte 512)
     80     #   var label-stack: (stack int 32)  # at most 32 levels of nesting
     81     #   var next-label-id: int = 1
     82     #   while true
     83     #     clear-stream(line)
     84     #     read-line-buffered(in, line)
     85     #     if (line->write == 0) break                           # end of file
     86     #     skip-chars-matching-whitespace(line)
     87     #     if line->data[line->read] == '{'
     88     #       print(out, "@loop" next-label-id ":\n")
     89     #       push(label-stack, next-label-id)
     90     #       ++next-label-id
     91     #       continue
     92     #     if line->data[line->read] == '}'
     93     #       var top = pop(label-stack)
     94     #       print(out, "@break" top ":\n")
     95     #       continue
     96     #     while true
     97     #       var word-slice: (addr slice) = next-word-or-string(line)
     98     #       if slice-empty?(word-slice)                         # end of line
     99     #         break
    100     #       if slice-starts-with?(word-slice, "#")              # comment
    101     #         continue
    102     #       if slice-starts-with?(word-slice, "break/")
    103     #         var top = top(label-stack)
    104     #         print(out, "@break" top)
    105     #         word-slice->start += len("break")
    106     #       else if slice-starts-with?(word-slice, "loop/")
    107     #         var top = top(label-stack)
    108     #         print(out, "@loop" top)
    109     #         word-slice->start += len("loop")
    110     #       print(out, word-slice " ")
    111     #     print(out, "\n")
    112     #   flush(out)
    113     # . prologue
    114     55/push-ebp
    115     89/<- %ebp 4/r32/esp
    116     # . save registers
    117     50/push-eax
    118     51/push-ecx
    119     52/push-edx
    120     53/push-ebx
    121     56/push-esi
    122     57/push-edi
    123     # esi = in
    124     8b/-> *(ebp+8) 6/r32/esi
    125     # var line/ecx: (stream byte 512)
    126     81 5/subop/subtract %esp 0x200/imm32
    127     68/push 0x200/imm32/length
    128     68/push 0/imm32/read
    129     68/push 0/imm32/write
    130     89/<- %ecx 4/r32/esp
    131     # var label-stack/edx: (stack int 32)
    132     81 5/subop/subtract %esp 0x80/imm32
    133     68/push 0x80/imm32/length
    134     68/push 0/imm32/top
    135     89/<- %edx 4/r32/esp
    136     # var next-label-id/ebx: int = 1
    137     c7 0/subop/copy %ebx 1/imm32
    138     # var word-slice/edi: slice
    139     68/push 0/imm32/end
    140     68/push 0/imm32/start
    141     89/<- %edi 4/r32/esp
    142 $subx-braces:line-loop:
    143     (clear-stream %ecx)
    144     (read-line-buffered %esi %ecx)
    145 $subx-braces:check0:
    146     # if (line->write == 0) break
    147     81 7/subop/compare *ecx 0/imm32
    148     0f 84/jump-if-=  $subx-braces:break/disp32
    149     (skip-chars-matching-whitespace %ecx)
    150 $subx-braces:check-for-curly-open:
    151     # if (line->data[line->read] != '{') goto next check
    152     # . eax = line->data[line->read]
    153     8b/-> *(ecx+4) 0/r32/eax
    154     8a/copy-byte *(ecx+eax+0xc) 0/r32/AL
    155     25/and-eax-with 0xff/imm32
    156     # . if (eax != '{') continue
    157     3d/compare-eax-and 0x7b/imm32/open-curly
    158     0f 85/jump-if-!= $subx-braces:check-for-curly-closed/disp32
    159 $subx-braces:emit-curly-open:
    160     # print(out, "@loop" next-label-id ":")
    161     (write-buffered *(ebp+0xc) "@loop")
    162     (write-int32-hex-buffered *(ebp+0xc) %ebx)
    163     (write-buffered *(ebp+0xc) ":")
    164     # push(label-stack, next-label-id)
    165     (push %edx %ebx)
    166     # ++next-label-id
    167     ff 0/subop/increment %ebx
    168     # continue
    169     e9/jump  $subx-braces:next-line/disp32
    170 $subx-braces:check-for-curly-closed:
    171     # if (line->data[line->read] != '}') goto next check
    172     3d/compare-eax-and 0x7d/imm32/close-curly
    173     0f 85/jump-if-= $subx-braces:word-loop/disp32
    174 $subx-braces:emit-curly-closed:
    175     # eax = pop(label-stack)
    176     (pop %edx)
    177     # print(out, "@break" eax ":")
    178     (write-buffered *(ebp+0xc) "@break")
    179     (write-int32-hex-buffered *(ebp+0xc) %eax)
    180     (write-buffered *(ebp+0xc) ":")
    181     # continue
    182     e9/jump  $subx-braces:next-line/disp32
    183 $subx-braces:word-loop:
    184     (next-word-or-string %ecx %edi)
    185 $subx-braces:check1:
    186     # if (slice-empty?(word-slice)) break
    187     (slice-empty? %edi)
    188     3d/compare-eax-and 0/imm32/false
    189     0f 85/jump-if-!= $subx-braces:next-line/disp32
    190 $subx-braces:check-for-comment:
    191     # if (slice-starts-with?(word-slice, "#")) continue
    192     # . eax = *word-slice->start
    193     8b/-> *edi 0/r32/eax
    194     8a/copy-byte *eax 0/r32/AL
    195     25/and-eax-with 0xff/imm32
    196     # . if (eax == '#') continue
    197     3d/compare-eax-and 0x23/imm32/hash
    198     74/jump-if-= $subx-braces:word-loop/disp8
    199 $subx-braces:check-for-break:
    200     # if (!slice-starts-with?(word-slice, "break/")) goto next check
    201     # . eax = slice-starts-with?(word-slice, "break/")
    202     (slice-starts-with? %edi "break/")
    203     # . if (eax == false) goto next check
    204     3d/compare-eax-and 0/imm32/false
    205     74/jump-if-= $subx-braces:check-for-loop/disp8
    206 $subx-braces:emit-break:
    207     (top %edx)
    208     # print(out, "@break" eax)
    209     (write-buffered *(ebp+0xc) "@break")
    210     (write-int32-hex-buffered *(ebp+0xc) %eax)
    211     # word-slice->start += len("break")
    212     81 0/subop/add *edi 5/imm32/strlen
    213     # emit rest of word as usual
    214     eb/jump $subx-braces:emit-word-slice/disp8
    215 $subx-braces:check-for-loop:
    216     # if (!slice-starts-with?(word-slice, "loop/")) emit word
    217     # . eax = slice-starts-with?(word-slice, "loop/")
    218     (slice-starts-with? %edi "loop/")
    219     # . if (eax == false) goto next check
    220     3d/compare-eax-and 0/imm32/false
    221     74/jump-if-= $subx-braces:emit-word-slice/disp8
    222 $subx-braces:emit-loop:
    223     (top %edx)
    224     # print(out, "@loop" eax)
    225     (write-buffered *(ebp+0xc) "@loop")
    226     (write-int32-hex-buffered *(ebp+0xc) %eax)
    227     # word-slice->start += len("loop")
    228     81 0/subop/add *edi 4/imm32/strlen
    229     # fall through
    230 $subx-braces:emit-word-slice:
    231     # print(out, word-slice " ")
    232     (write-slice-buffered *(ebp+0xc) %edi)
    233     (write-buffered *(ebp+0xc) Space)
    234     # loop to next word
    235     e9/jump $subx-braces:word-loop/disp32
    236 $subx-braces:next-line:
    237     # print(out, "\n")
    238     (write-buffered *(ebp+0xc) Newline)
    239     # loop to next line
    240     e9/jump $subx-braces:line-loop/disp32
    241 $subx-braces:break:
    242     (flush *(ebp+0xc))
    243 $subx-braces:end:
    244     # . reclaim locals
    245     81 0/subop/add %esp 0x29c/imm32
    246     # . restore registers
    247     5f/pop-to-edi
    248     5e/pop-to-esi
    249     5b/pop-to-ebx
    250     5a/pop-to-edx
    251     59/pop-to-ecx
    252     58/pop-to-eax
    253     # . epilogue
    254     89/<- %esp 5/r32/ebp
    255     5d/pop-to-ebp
    256     c3/return
    257
    258 test-subx-braces-passes-most-words-through:
    259     # . prologue
    260     55/push-ebp
    261     89/<- %ebp 4/r32/esp
    262     # setup
    263     (clear-stream _test-input-stream)
    264     (clear-stream _test-output-stream)
    265     (clear-stream $_test-input-buffered-file->buffer)
    266     (clear-stream $_test-output-buffered-file->buffer)
    267     # test
    268     (write _test-input-stream "== abcd 0x1")
    269     (subx-braces _test-input-buffered-file _test-output-buffered-file)
    270     # check that the line just passed through
    271     (flush _test-output-buffered-file)
    272 +--  5 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------
    277     (check-stream-equal _test-output-stream "== abcd 0x1 \n" "F - test-subx-braces-passes-most-words-through")
    278     # . epilogue
    279     89/<- %esp 5/r32/ebp
    280     5d/pop-to-ebp
    281     c3/return
    282
    283 test-subx-braces-1:
    284     # input:
    285     #   {
    286     #   ab break/imm32
    287     #   cd loop/imm32
    288     #   }
    289     #
    290     # output:
    291     #   @loop1:
    292     #   ab @break1/imm32
    293     #   cd @loop1/imm32
    294     #   @break1:
    295     #
    296     # . prologue
    297     55/push-ebp
    298     89/<- %ebp 4/r32/esp
    299     # setup
    300     (clear-stream _test-input-stream)
    301     (clear-stream _test-output-stream)
    302     (clear-stream $_test-input-buffered-file->buffer)
    303     (clear-stream $_test-output-buffered-file->buffer)
    304     # test
    305     (write _test-input-stream "{\nab break/imm32\ncd loop/imm32\n}")
    306     (subx-braces _test-input-buffered-file _test-output-buffered-file)
    307     # check that the line just passed through
    308     (flush _test-output-buffered-file)
    309 +--  5 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------
    314     (check-stream-equal _test-output-stream "@loop0x00000001:\nab @break0x00000001/imm32 \ncd @loop0x00000001/imm32 \n@break0x00000001:\n" "F - test-subx-braces-1")
    315     # . epilogue
    316     89/<- %esp 5/r32/ebp
    317     5d/pop-to-ebp
    318     c3/return
    319
    320 test-subx-braces-2:
    321     # input:
    322     #   {
    323     #   {
    324     #   ab break/imm32
    325     #   }
    326     #   cd loop/imm32
    327     #   }
    328     #
    329     # output:
    330     #   @loop1:
    331     #   @loop2:
    332     #   ab @break2/imm32
    333     #   @break2:
    334     #   cd @loop1/imm32
    335     #   @break1:
    336     #
    337     # . prologue
    338     55/push-ebp
    339     89/<- %ebp 4/r32/esp
    340     # setup
    341     (clear-stream _test-input-stream)
    342     (clear-stream _test-output-stream)
    343     (clear-stream $_test-input-buffered-file->buffer)
    344     (clear-stream $_test-output-buffered-file->buffer)
    345     # test
    346     (write _test-input-stream "{\n{\nab break/imm32\n}\ncd loop/imm32\n}")
    347     (subx-braces _test-input-buffered-file _test-output-buffered-file)
    348     # check that the line just passed through
    349     (flush _test-output-buffered-file)
    350 +--  5 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------
    355     (check-stream-equal _test-output-stream "@loop0x00000001:\n@loop0x00000002:\nab @break0x00000002/imm32 \n@break0x00000002:\ncd @loop0x00000001/imm32 \n@break0x00000001:\n" "F - test-subx-braces-2")
    356     # . epilogue
    357     89/<- %esp 5/r32/ebp
    358     5d/pop-to-ebp
    359     c3/return