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&qu
:(before "End Globals")
// Arithmetic ops.
const int ADD = 2;
:(before "End Primitive Recipe Numbers")
Recipe_number["add"] = ADD;
assert(Next_recipe_number == ADD);
Next_recipe_number++;
:(before "End Primitive Recipe Implementations")
case ADD: {
  trace("run") << "ingredient 0 is " << instructions[pc].ingredients[0].name;
  vector<int> arg0 = read_memory(instructions[pc].ingredients[0]);
  assert(arg0.size() == 1);
  trace("run") << "ingredient 1 is " << instructions[pc].ingredients[1].name;
  vector<int> arg1 = read_memory(instructions[pc].ingredients[1]);
  assert(arg1.size() == 1);
  vector<int> result;
  result.push_back(arg0[0] + arg1[0]);
  trace("run") << "product 0 is " << result[0];
  write_memory(instructions[pc].products[0], result);
  break;
}

:(scenario "add_literal")
recipe main [
  1:integer <- add 23:literal, 34:literal
]
+run: instruction main/0
+run: ingredient 0 is 23
+run: ingredient 1 is 34
+run: product 0 is 57
+mem: storing 57 in location 1

:(scenario "add")
recipe main [
  1:integer <- copy 23:literal
  2:integer <- copy 34:literal
  3:integer <- add 1:integer, 2:integer
]
+run: instruction main/2
+run: ingredient 0 is 1
+mem: location 1 is 23
+run: ingredient 1 is 2
+mem: location 2 is 34
+run: product 0 is 57
+mem: storing 57 in location 3

:(before "End Globals")
const int SUBTRACT = 3;
:(before "End Primitive Recipe Numbers")
Recipe_number["subtract"] = SUBTRACT;
assert(Next_recipe_number == SUBTRACT);
Next_recipe_number++;
:(before "End Primitive Recipe Implementations")
case SUBTRACT: {
  trace("run") << "ingredient 0 is " << instructions[pc].ingredients[0].name;
  vector<int> arg0 = read_memory(instructions[pc].ingredients[0]);
  assert(arg0.size() == 1);
  trace("run") << "ingredient 1 is " << instructions[pc].ingredients[1].name;
  vector<int> arg1 = read_memory(instructions[pc].ingredients[1]);
  assert(arg1.size() == 1);
  vector<int> result;
  result.push_back(arg0[0] - arg1[0]);
  trace("run") << "product 0 is " << result[0];
  write_memory(instructions[pc].products[0], result);
  break;
}

:(scenario "subtract_literal")
recipe main [
  1:integer <- subtract 5:literal, 2:literal
]
+run: instruction main/0
+run: ingredient 0 is 5
+run: ingredient 1 is 2
+run: product 0 is 3
+mem: storing 3 in location 1

:(scenario "subtract")
recipe main [
  1:integer <- copy 23:literal
  2:integer <- copy 34:literal
  3:integer <- subtract 1:integer, 2:integer
]
+run: instruction main/2
+run: ingredient 0 is 1
+mem: location 1 is 23
+run: ingredient 1 is 2
+mem: location 2 is 34
+run: product 0 is -11
+mem: storing -11 in location 3

:(before "End Globals")
const int MULTIPLY = 4;
:(before "End Primitive Recipe Numbers")
Recipe_number["multiply"] = MULTIPLY;
assert(Next_recipe_number == MULTIPLY);
Next_recipe_number++;
:(before "End Primitive Recipe Implementations")
case MULTIPLY: {
  trace("run") << "ingredient 0 is " << instructions[pc].ingredients[0].name;
  vector<int> arg0 = read_memory(instructions[pc].ingredients[0]);
  assert(arg0.size() == 1);
  trace("run") << "ingredient 1 is " << instructions[pc].ingredients[1].name;
  vector<int> arg1 = read_memory(instructions[pc].ingredients[1]);
  assert(arg1.size() == 1);
  trace("run") << "ingredient 1 is " << arg1[0];
  vector<int> result;
  result.push_back(arg0[0] * arg1[0]);
  trace("run") << "product 0 is " << result[0];
  write_memory(instructions[pc].products[0], result);
  break;
}

:(scenario "multiply_literal")
recipe main [
  1:integer <- multiply 2:literal, 3:literal
]
+run: instruction main/0
+run: ingredient 0 is 2
+run: ingredient 1 is 3
+run: product 0 is 6
+mem: storing 6 in location 1

:(scenario "multiply")
recipe main [
  1:integer <- copy 4:literal
  2:integer <- copy 6:literal
  3:integer <- multiply 1:integer, 2:integer
]
+run: instruction main/2
+run: ingredient 0 is 1
+mem: location 1 is 4
+run: ingredient 1 is 2
+mem: location 2 is 6
+run: product 0 is 24
+mem: storing 24 in location 3

:(before "End Globals")
const int DIVIDE = 5;
:(before "End Primitive Recipe Numbers")
Recipe_number["divide"] = DIVIDE;
assert(Next_recipe_number == DIVIDE);
Next_recipe_number++;
:(before "End Primitive Recipe Implementations")
case DIVIDE: {
  trace("run") << "ingredient 0 is " << instructions[pc].ingredients[0].name;
  vector<int> arg0 = read_memory(instructions[pc].ingredients[0]);
  assert(arg0.size() == 1);
  trace("run") << "ingredient 1 is " << instructions[pc].ingredients[1].name;
  vector<int> arg1 = read_memory(instructions[pc].ingredients[1]);
  assert(arg1.size() == 1);
  trace("run") << "ingredient 1 is " << arg1[0];
  vector<int> result;
  result.push_back(arg0[0] / arg1[0]);
  trace("run") << "product 0 is " << result[0];
  write_memory(instructions[pc].products[0], result);
  break;
}

:(scenario "divide_literal")
recipe main [
  1:integer <- divide 8:literal, 2:literal
]
+run: instruction main/0
+run: ingredient 0 is 8
+run: ingredient 1 is 2
+run: product 0 is 4
+mem: storing 4 in location 1

:(scenario "divide")
recipe main [
  1:integer <- copy 27:literal
  2:integer <- copy 3:literal
  3:integer <- divide 1:integer, 2:integer
]
+run: instruction main/2
+run: ingredient 0 is 1
+mem: location 1 is 27
+run: ingredient 1 is 2
+mem: location 2 is 3
+run: product 0 is 9
+mem: storing 9 in location 3

:(before "End Globals")
const int DIVIDE_WITH_REMAINDER = 6;
:(before "End Primitive Recipe Numbers")
Recipe_number["divide_with_remainder"] = DIVIDE_WITH_REMAINDER;
assert(Next_recipe_number == DIVIDE_WITH_REMAINDER);
Next_recipe_number++;
:(before "End Primitive Recipe Implementations")
case DIVIDE_WITH_REMAINDER: {
  trace("run") << "ingredient 0 is " << instructions[pc].ingredients[0].name;
  vector<int> arg0 = read_memory(instructions[pc].ingredients[0]);
  assert(arg0.size() == 1);
  trace("run") << "ingredient 1 is " << instructions[pc].ingredients[1].name;
  vector<int> arg1 = read_memory(instructions[pc].ingredients[1]);
  assert(arg1.size() == 1);
  vector<int> result0;
  result0.push_back(arg0[0] / arg1[0]);
  trace("run") << "product 0 is " << result0[0];
  write_memory(instructions[pc].products[0], result0);
  vector<int> result1;
  result1.push_back(arg0[0] % arg1[0]);
  trace("run") << "product 1 is " << result1[0];
  write_memory(instructions[pc].products[1], result1);
  break;
}

:(scenario "divide_with_remainder_literal")
recipe main [
  1:integer, 2:integer <- divide_with_remainder 9:literal, 2:literal
]
+run: instruction main/0
+run: ingredient 0 is 9
+run: ingredient 1 is 2
+run: product 0 is 4
+mem: storing 4 in location 1
+run: product 1 is 1
+mem: storing 1 in location 2

:(scenario "divide_with_remainder")
recipe main [
  1:integer <- copy 27:literal
  2:integer <- copy 11:literal
  3:integer, 4:integer <- divide_with_remainder 1:integer, 2:integer
]
+run: instruction main/2
+run: ingredient 0 is 1
+mem: location 1 is 27
+run: ingredient 1 is 2
+mem: location 2 is 11
+run: product 0 is 2
+mem: storing 2 in location 3
+run: product 1 is 5
+mem: storing 5 in location 4
="LineNr">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