# 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