about summary refs log tree commit diff stats
path: root/linux/braces.subx
diff options
context:
space:
mode:
Diffstat (limited to 'linux/braces.subx')
-rw-r--r--linux/braces.subx359
1 files changed, 359 insertions, 0 deletions
diff --git a/linux/braces.subx b/linux/braces.subx
new file mode 100644
index 00000000..d6b8f0f4
--- /dev/null
+++ b/linux/braces.subx
@@ -0,0 +1,359 @@
+# Structured control flow using break/loop rather than jump.
+#
+# To run (on Linux):
+#   $ ./translate_subx init.linux [012]*.subx apps/subx-params.subx apps/braces.subx
+#   $ mv a.elf apps/braces
+#
+# Example 1:
+#   $ cat x.subx
+#   {
+#     7c/jump-if-< break/disp8
+#     74/jump-if-= loop/disp8
+#   }
+#   $ cat x.subx |apps/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 |apps/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 |apps/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
+    81 4/subop/and %eax 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
+    81 4/subop/and %eax 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
inst = get(Recipe, r).steps.at(i); if (inst.is_label) continue; for (int j = 0; j < SIZE(inst.products); ++j) { if (is_literal(inst.products.at(j))) continue; if (inst.products.at(j).name != "0") continue; if (!is_mu_space(inst.products.at(j))) { raise << "slot 0 should always have type address:array:location, but is '" << to_string(inst.products.at(j)) << "'\n" << end(); continue; } string_tree* s = property(inst.products.at(j), "names"); if (!s) { raise << "slot 0 requires a /names property in recipe '" << get(Recipe, r).name << "'\n" << end(); continue; } if (!s->atom) raise << "slot 0 should have a single value in /names, but got '" << to_string(inst.products.at(j)) << "'\n" << end(); const string& surrounding_recipe_name = s->value; if (surrounding_recipe_name.empty()) { raise << "slot 0 doesn't initialize its /names property in recipe '" << get(Recipe, r).name << "'\n" << end(); continue; } if (contains_key(Surrounding_space, r) && get(Surrounding_space, r) != get(Recipe_ordinal, surrounding_recipe_name)) { raise << "recipe '" << get(Recipe, r).name << "' can have only one 'surrounding' recipe but has '" << get(Recipe, get(Surrounding_space, r)).name << "' and '" << surrounding_recipe_name << "'\n" << end(); continue; } trace(9993, "name") << "lexically surrounding space for recipe " << get(Recipe, r).name << " comes from " << surrounding_recipe_name << end(); if (!contains_key(Recipe_ordinal, surrounding_recipe_name)) { raise << "can't find recipe providing surrounding space for '" << get(Recipe, r).name << "'; looking for '" << surrounding_recipe_name << "'\n" << end(); continue; } put(Surrounding_space, r, get(Recipe_ordinal, surrounding_recipe_name)); } } } //: Once surrounding spaces are available, transform_names uses them to handle //: /space properties. :(replace{} "int lookup_name(const reagent& r, const recipe_ordinal default_recipe)") int lookup_name(const reagent& x, const recipe_ordinal default_recipe) { if (!has_property(x, "space")) { if (Name[default_recipe].empty()) raise << "name not found: " << x.name << '\n' << end(); return Name[default_recipe][x.name]; } string_tree* p = property(x, "space"); if (!p || !p->atom) raise << "/space property should have exactly one (non-negative integer) value\n" << end(); int n = to_integer(p->value); assert(n >= 0); recipe_ordinal surrounding_recipe = lookup_surrounding_recipe(default_recipe, n); if (surrounding_recipe == -1) return -1; set<recipe_ordinal> done; vector<recipe_ordinal> path; return lookup_name(x, surrounding_recipe, done, path); } // If the recipe we need to lookup this name in doesn't have names done yet, // recursively call transform_names on it. int lookup_name(const reagent& x, const recipe_ordinal r, set<recipe_ordinal>& done, vector<recipe_ordinal>& path) { if (!Name[r].empty()) return Name[r][x.name]; if (contains_key(done, r)) { raise << "can't compute address of '" << to_string(x) << "' because\n" << end(); for (int i = 1; i < SIZE(path); ++i) { raise << path.at(i-1) << " requires computing names of " << path.at(i) << '\n' << end(); } raise << path.at(SIZE(path)-1) << " requires computing names of " << r << "..ad infinitum\n" << end(); return -1; } done.insert(r); path.push_back(r); transform_names(r); // Not passing 'done' through. Might this somehow cause an infinite loop? assert(!Name[r].empty()); return Name[r][x.name]; } recipe_ordinal lookup_surrounding_recipe(const recipe_ordinal r, int n) { if (n == 0) return r; if (!contains_key(Surrounding_space, r)) { raise << "don't know surrounding recipe of '" << get(Recipe, r).name << "'\n" << end(); return -1; } assert(contains_key(Surrounding_space, r)); return lookup_surrounding_recipe(get(Surrounding_space, r), n-1); } //: weaken use-before-set detection just a tad :(replace{} "bool already_transformed(const reagent& r, const map<string, int>& names)") bool already_transformed(const reagent& r, const map<string, int>& names) { if (has_property(r, "space")) { string_tree* p = property(r, "space"); if (!p || !p->atom) { raise << "/space property should have exactly one (non-negative integer) value in '" << r.original_string << "'\n" << end(); return false; } if (p->value != "0") return true; } return contains_key(names, r.name); } :(scenario missing_surrounding_space) % Hide_errors = true; def f [ local-scope x:num/space:1 <- copy 34 ] +error: don't know surrounding recipe of 'f' +error: f: can't find a place to store 'x' //: extra test for try_reclaim_locals() from previous layers :(scenario local_scope_ignores_nonlocal_spaces) def new-scope [ local-scope x:&:num <- new number:type *x:&:num <- copy 34 return default-space:space ] def use-scope [ local-scope outer:space/names:new-scope <- next-ingredient 0:space/names:new-scope <- copy outer:space return *x:&:num/space:1 ] def main [ 1:space/raw <- new-scope 3:num/raw <- use-scope 1:space/raw ] +mem: storing 34 in location 3 :(scenario recursive_transform_names) def foo [ local-scope x:num <- copy 0 return default-space:space/names:foo ] def main [ local-scope 0:space/names:foo <- foo x:num/space:1 <- copy 34 ] $error: 0