# Structured control flow using break/loop rather than jump.
#
# To run (on Linux):
# $ ./translate_subx init.linux [012]*.subx subx-params.subx braces.subx
# $ mv a.elf braces
#
# Example 1:
# $ cat x.subx
# {
# 7c/jump-if-< break/disp8
# 74/jump-if-= loop/disp8
# }
# $ cat x.subx |braces
# @loop1:
# 7c/jump-if-< @break1/disp8
# 74/jump-if-= @loop1/disp8
# @break1:
#
# Example 2:
# $ cat x.subx
# {
# 7c/jump-if-< break/disp8
# }
# {
# 74/jump-if-= loop/disp8
# }
# $ cat x.subx |braces
# @loop1:
# 7c/jump-if-< @break1/disp8
# @break1:
# @loop2:
# 74/jump-if-= @loop2/disp8
# @break2:
#
# Example 3:
# $ cat x.subx
# {
# {
# 74/jump-if-= loop/disp8
# }
# 7c/jump-if-< loop/disp8
# }
# $ cat x.subx |braces
# @loop1:
# @loop2:
# 74/jump-if-= @loop2/disp8
# @break2:
# 7c/jump-if-< @loop1/disp8
# @break1:
== code
Entry: # run tests if necessary, a REPL if not
# . prologue
89/<- %ebp 4/r32/esp
# initialize heap
(new-segment *Heap-size Heap)
# if (argc <= 1) goto interactive
81 7/subop/compare *ebp 1/imm32
7e/jump-if-<= $subx-braces-main:interactive/disp8
# if (argv[1] != "test")) goto interactive
(kernel-string-equal? *(ebp+8) "test") # => eax
3d/compare-eax-and 0/imm32/false
74/jump-if-= $subx-braces-main:interactive/disp8
#
(run-tests)
# syscall_exit(*Num-test-failures)
8b/-> *Num-test-failures 3/r32/ebx
eb/jump $subx-braces-main:end/disp8
$subx-braces-main:interactive:
(subx-braces Stdin Stdout)
# syscall_exit(0)
bb/copy-to-ebx 0/imm32
$subx-braces-main:end:
e8/call syscall_exit/disp32
subx-braces: # in: (addr buffered-file), out: (addr buffered-file)
# pseudocode:
# var line: (stream byte 512)
# var label-stack: (stack int 32) # at most 32 levels of nesting
# var next-label-id: int = 1
# while true
# clear-stream(line)
# read-line-buffered(in, line)
# if (line->write == 0) break # end of file
# skip-chars-matching-whitespace(line)
# if line->data[line->read] == '{'
# print(out, "@loop" next-label-id ":\n")
# push(label-stack, next-label-id)
# ++next-label-id
# continue
# if line->data[line->read] == '}'
# var top = pop(label-stack)
# print(out, "@break" top ":\n")
# continue
# while true
# var word-slice: (addr slice) = next-word-or-string(line)
# if slice-empty?(word-slice) # end of line
# break
# if slice-starts-with?(word-slice, "#") # comment
# continue
# if slice-starts-with?(word-slice, "break/")
# var top = top(label-stack)
# print(out, "@break" top)
# word-slice->start += len("break")
# else if slice-starts-with?(word-slice, "loop/")
# var top = top(label-stack)
# print(out, "@loop" top)
# word-slice->start += len("loop")
# print(out, word-slice " ")
# print(out, "\n")
# flush(out)
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# . save registers
50/push-eax
51/push-ecx
52/push-edx
53/push-ebx
56/push-esi
57/push-edi
# esi = in
8b/-> *(ebp+8) 6/r32/esi
# var line/ecx: (stream byte 512)
81 5/subop/subtract %esp 0x200/imm32
68/push 0x200/imm32/length
68/push 0/imm32/read
68/push 0/imm32/write
89/<- %ecx 4/r32/esp
# var label-stack/edx: (stack int 32)
81 5/subop/subtract %esp 0x80/imm32
68/push 0x80/imm32/length
68/push 0/imm32/top
89/<- %edx 4/r32/esp
# var next-label-id/ebx: int = 1
c7 0/subop/copy %ebx 1/imm32
# var word-slice/edi: slice
68/push 0/imm32/end
68/push 0/imm32/start
89/<- %edi 4/r32/esp
$subx-braces:line-loop:
(clear-stream %ecx)
(read-line-buffered %esi %ecx)
$subx-braces:check0:
# if (line->write == 0) break
81 7/subop/compare *ecx 0/imm32
0f 84/jump-if-= $subx-braces:break/disp32
(skip-chars-matching-whitespace %ecx)
$subx-braces:check-for-curly-open:
# if (line->data[line->read] != '{') goto next check
# . eax = line->data[line->read]
8b/-> *(ecx+4) 0/r32/eax
8a/copy-byte *(ecx+eax+0xc) 0/r32/AL
25/and-eax-with 0xff/imm32
# . if (eax != '{') continue
3d/compare-eax-and 0x7b/imm32/open-curly
0f 85/jump-if-!= $subx-braces:check-for-curly-closed/disp32
$subx-braces:emit-curly-open:
# print(out, "@loop" next-label-id ":")
(write-buffered *(ebp+0xc) "@loop")
(write-int32-hex-buffered *(ebp+0xc) %ebx)
(write-buffered *(ebp+0xc) ":")
# push(label-stack, next-label-id)
(push %edx %ebx)
# ++next-label-id
ff 0/subop/increment %ebx
# continue
e9/jump $subx-braces:next-line/disp32
$subx-braces:check-for-curly-closed:
# if (line->data[line->read] != '}') goto next check
3d/compare-eax-and 0x7d/imm32/close-curly
0f 85/jump-if-= $subx-braces:word-loop/disp32
$subx-braces:emit-curly-closed:
# eax = pop(label-stack)
(pop %edx)
# print(out, "@break" eax ":")
(write-buffered *(ebp+0xc) "@break")
(write-int32-hex-buffered *(ebp+0xc) %eax)
(write-buffered *(ebp+0xc) ":")
# continue
e9/jump $subx-braces:next-line/disp32
$subx-braces:word-loop:
(next-word-or-string %ecx %edi)
$subx-braces:check1:
# if (slice-empty?(word-slice)) break
(slice-empty? %edi)
3d/compare-eax-and 0/imm32/false
0f 85/jump-if-!= $subx-braces:next-line/disp32
$subx-braces:check-for-comment:
# if (slice-starts-with?(word-slice, "#")) continue
# . eax = *word-slice->start
8b/-> *edi 0/r32/eax
8a/copy-byte *eax 0/r32/AL
25/and-eax-with 0xff/imm32
# . if (eax == '#') continue
3d/compare-eax-and 0x23/imm32/hash
74/jump-if-= $subx-braces:word-loop/disp8
$subx-braces:check-for-break:
# if (!slice-starts-with?(word-slice, "break/")) goto next check
# . eax = slice-starts-with?(word-slice, "break/")
(slice-starts-with? %edi "break/")
# . if (eax == false) goto next check
3d/compare-eax-and 0/imm32/false
74/jump-if-= $subx-braces:check-for-loop/disp8
$subx-braces:emit-break:
(top %edx)
# print(out, "@break" eax)
(write-buffered *(ebp+0xc) "@break")
(write-int32-hex-buffered *(ebp+0xc) %eax)
# word-slice->start += len("break")
81 0/subop/add *edi 5/imm32/strlen
# emit rest of word as usual
eb/jump $subx-braces:emit-word-slice/disp8
$subx-braces:check-for-loop:
# if (!slice-starts-with?(word-slice, "loop/")) emit word
# . eax = slice-starts-with?(word-slice, "loop/")
(slice-starts-with? %edi "loop/")
# . if (eax == false) goto next check
3d/compare-eax-and 0/imm32/false
74/jump-if-= $subx-braces:emit-word-slice/disp8
$subx-braces:emit-loop:
(top %edx)
# print(out, "@loop" eax)
(write-buffered *(ebp+0xc) "@loop")
(write-int32-hex-buffered *(ebp+0xc) %eax)
# word-slice->start += len("loop")
81 0/subop/add *edi 4/imm32/strlen
# fall through
$subx-braces:emit-word-slice:
# print(out, word-slice " ")
(write-slice-buffered *(ebp+0xc) %edi)
(write-buffered *(ebp+0xc) Space)
# loop to next word
e9/jump $subx-braces:word-loop/disp32
$subx-braces:next-line:
# print(out, "\n")
(write-buffered *(ebp+0xc) Newline)
# loop to next line
e9/jump $subx-braces:line-loop/disp32
$subx-braces:break:
(flush *(ebp+0xc))
$subx-braces:end:
# . reclaim locals
81 0/subop/add %esp 0x29c/imm32
# . restore registers
5f/pop-to-edi
5e/pop-to-esi
5b/pop-to-ebx
5a/pop-to-edx
59/pop-to-ecx
58/pop-to-eax
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
test-subx-braces-passes-most-words-through:
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# setup
(clear-stream _test-input-stream)
(clear-stream _test-output-stream)
(clear-stream $_test-input-buffered-file->buffer)
(clear-stream $_test-output-buffered-file->buffer)
# test
(write _test-input-stream "== abcd 0x1")
(subx-braces _test-input-buffered-file _test-output-buffered-file)
# check that the line just passed through
(flush _test-output-buffered-file)
#? # dump _test-output-stream {{{
#? (write 2 "^")
#? (write-stream 2 _test-output-stream)
#? (write 2 "$\n")
#? # }}}
(check-stream-equal _test-output-stream "== abcd 0x1 \n" "F - test-subx-braces-passes-most-words-through")
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
test-subx-braces-1:
# input:
# {
# ab break/imm32
# cd loop/imm32
# }
#
# output:
# @loop1:
# ab @break1/imm32
# cd @loop1/imm32
# @break1:
#
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# setup
(clear-stream _test-input-stream)
(clear-stream _test-output-stream)
(clear-stream $_test-input-buffered-file->buffer)
(clear-stream $_test-output-buffered-file->buffer)
# test
(write _test-input-stream "{\nab break/imm32\ncd loop/imm32\n}")
(subx-braces _test-input-buffered-file _test-output-buffered-file)
# check that the line just passed through
(flush _test-output-buffered-file)
#? # dump _test-output-stream {{{
#? (write 2 "^")
#? (write-stream 2 _test-output-stream)
#? (write 2 "$\n")
#? # }}}
(check-stream-equal _test-output-stream "@loop0x00000001:\nab @break0x00000001/imm32 \ncd @loop0x00000001/imm32 \n@break0x00000001:\n" "F - test-subx-braces-1")
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
test-subx-braces-2:
# input:
# {
# {
# ab break/imm32
# }
# cd loop/imm32
# }
#
# output:
# @loop1:
# @loop2:
# ab @break2/imm32
# @break2:
# cd @loop1/imm32
# @break1:
#
# . prologue
55/push-ebp
89/<- %ebp 4/r32/esp
# setup
(clear-stream _test-input-stream)
(clear-stream _test-output-stream)
(clear-stream $_test-input-buffered-file->buffer)
(clear-stream $_test-output-buffered-file->buffer)
# test
(write _test-input-stream "{\n{\nab break/imm32\n}\ncd loop/imm32\n}")
(subx-braces _test-input-buffered-file _test-output-buffered-file)
# check that the line just passed through
(flush _test-output-buffered-file)
#? # dump _test-output-stream {{{
#? (write 2 "^")
#? (write-stream 2 _test-output-stream)
#? (write 2 "$\n")
#? # }}}
(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")
# . epilogue
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return