https://github.com/akkartik/mu/blob/master/apps/calls.subx
   1 # Function calls in a single line.
   2 #
   3 # To run (on Linux):
   4 #   $ ./ntranslate init.linux 0*.subx apps/subx-params.subx apps/calls.subx
   5 #   $ mv a.elf apps/calls
   6 #
   7 # Example 1:
   8 #   $ echo '(foo %eax)'                         |  apps/calls
   9 #   # . (foo %eax)                                      # output has comments
  10 #   ff 6/subop/push %eax                                # push
  11 #   e8/call foo/disp32                                  # call
  12 #   81 0/subop/add %esp 4/imm32                         # undo push
  13 #
  14 # Example 2:
  15 #   $ echo '(foo Var1 *(eax + 4) "blah")'       |  apps/calls
  16 #   # . (foo Var1 *(eax + 4) "blah")
  17 #   68/push "blah"/imm32
  18 #   ff 6/subop/push *(eax + 4)                          # push args in..
  19 #   68/push Var1/imm32                                  # ..reverse order
  20 #   e8/call foo/disp32
  21 #   81 0/subop/add %esp 4/imm32                         # undo pushes
  22 #
  23 # Calls always begin with '(' as the first non-whitespace on a line.
  24 
  25 == code
  26 
  27 Entry:  # run tests if necessary, convert stdin if not
  28     # . prologue
  29     89/<- %ebp 4/r32/esp
  30 
  31     # initialize heap
  32     # . Heap = new-segment(Heap-size)
  33     # . . push args
  34     68/push Heap/imm32
  35     ff 6/subop/push *Heap-size
  36     # . . call
  37     e8/call new-segment/disp32
  38     # . . discard args
  39     81 0/subop/add %esp 8/imm32
  40 
  41     # - if argc > 1 and argv[1] == "test", then return run_tests()
  42     # if (argc <= 1) goto run-main
  43     81 7/subop/compare *ebp 1/imm32
  44     7e/jump-if-lesser-or-equal $subx-calls-main:interactive/disp8
  45     # if (!kernel-string-equal?(argv[1], "test")) goto run-main
  46     # . eax = kernel-string-equal?(argv[1], "test")
  47     # . . push args
  48     68/push "test"/imm32
  49     ff 6/subop/push *(ebp+8)
  50     # . . call
  51     e8/call kernel-string-equal?/disp32
  52     # . . discard args
  53     81 0/subop/add %esp 8/imm32
  54     # . if (eax == 0) goto run-main
  55     3d/compare-eax-and 0/imm32
  56     74/jump-if-equal $subx-calls-main:interactive/disp8
  57     # run-tests()
  58     e8/call run-tests/disp32
  59     # syscall(exit, *Num-test-failures)
  60     8b/-> *Num-test-failures 3/r32/ebx
  61     eb/jump $subx-calls-main:end/disp8
  62 $subx-calls-main:interactive:
  63     # - otherwise convert stdin
  64     # subx-calls(Stdin, Stdout)
  65     # . . push args
  66     68/push Stdout/imm32
  67     68/push Stdin/imm32
  68     # . . call
  69     e8/call subx-calls/disp32
  70     # . . discard args
  71     81 0/subop/add %esp 8/imm32
  72     # syscall(exit, 0)
  73     bb/copy-to-ebx 0/imm32
  74 $subx-calls-main:end:
  75     b8/copy-to-eax 1/imm32/exit
  76     cd/syscall 0x80/imm8
  77 
  78 subx-calls:  # in : (address buffered-file), out : (address buffered-file)
  79     # pseudocode:
  80     #   var line = new-stream(512, 1)
  81     #   var words : (address stream slice) = new-stream(16, 8)  # at most function name and 15 args
  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     #       write-stream-data(out, line)
  89     #       continue
  90     #     # emit comment
  91     #     write-buffered(out, "# . ")
  92     #     write-stream-data(out, line)
  93     #     # emit code
  94     #     ++line->read to skip '('
  95     #     clear-stream(words)
  96     #     words = parse-line(line)
  97     #     emit-call(out, words)
  98     #   flush(out)
  99     #
 100     # . prologue
 101     55/push-ebp
 102     89/<- %ebp 4/r32/esp
 103     # . save registers
 104     50/push-eax
 105     51/push-ecx
 106     52/push-edx
 107     56/push-esi
 108     # var line/esi : (address stream byte) = stream(512)
 109     81 5/subop/subtract %esp 0x200/imm32
 110     68/push 0x200/imm32/length
 111     68/push 0/imm32/read
 112     68/push 0/imm32/write
 113     89/<- %esi 4/r32/esp
 114     # var words/edx : (address stream slice) = stream(16, 8)
 115     81 5/subop/subtract %esp 0x80/imm32
 116     68/push 0x80/imm32/length
 117     68/push 0/imm32/read
 118     68/push 0/imm32/write
 119     89/<- %edx 4/r32/esp
 120 $subx-calls:loop:
 121     # clear-stream(line)
 122     # . . push args
 123     56/push-esi
 124     # . . call
 125     e8/call clear-stream/disp32
 126     # . . discard args
 127     81 0/subop/add %esp 4/imm32
 128     # read-line-buffered(in, line)
 129     # . . push args
 130     56/push-esi
 131     ff 6/subop/push *(ebp+8)
 132     # . . call
 133     e8/call read-line-buffered/disp32
 134     # . . discard args
 135     81 0/subop/add %esp 8/imm32
 136 $subx-calls:check0:
 137     # if (line->write == 0) break
 138     81 7/subop/compare *esi 0/imm32
 139     0f 84/jump-if-equal $subx-calls:break/disp32
 140     # skip-chars-matching-whitespace(line)
 141     # . . push args
 142     56/push-esi
 143     # . . call
 144     e8/call skip-chars-matching-whitespace/disp32
 145     # . . discard args
 146     81 0/subop/add %esp 4/imm32
 147     # if (line->data[line->read] == '(') goto convert-call
 148     # . ecx = line->read
 149     8b/-> *(esi+4) 1/r32/ecx
 150     # . eax = line->data[line->read]
 151     31/xor %eax 0/r32/eax
 152     8a/copy-byte *(esi+ecx+0xc) 0/r32/AL
 153     # . if (eax == '(') goto convert-call
 154     3d/compare-eax-and 0x28/imm32/open-paren
 155     74/jump-if-equal $subx-calls:convert-call/disp8
 156 $subx-calls:pass-through:
 157     # write-stream-data(out, line)
 158     # . . push args
 159     56/push-esi
 160     ff 6/subop/push *(ebp+0xc)
 161     # . . call
 162     e8/call write-stream-data/disp32
 163     # . . discard args
 164     81 0/subop/add %esp 8/imm32
 165     # continue
 166     eb/jump $subx-calls:loop/disp8
 167 $subx-calls:convert-call:
 168     # - emit comment
 169     # write-buffered(out, "# . ")
 170     # . . push args
 171     68/push "# . "/imm32
 172     ff 6/subop/push *(ebp+0xc)
 173     # . . call
 174     e8/call write-buffered/disp32
 175     # . . discard args
 176     81 0/subop/add %esp 8/imm32
 177     # write-stream-data(out, line)
 178     # . . push args
 179     56/push-esi
 180     ff 6/subop/push *(ebp+0xc)
 181     # . . call
 182     e8/call write-stream-data/disp32
 183     # . . discard args
 184     81 0/subop/add %esp 8/imm32
 185     # - emit code
 186     # ++line->read to skip '('
 187     ff 0/subop/increment *(esi+4)
 188     # clear-stream(words)
 189     # . . push args
 190     52/push-edx
 191     # . . call
 192     e8/call clear-stream/disp32
 193     # . . discard args
 194     81 0/subop/add %esp 4/imm32
 195     # words = parse-line(line)
 196     # . . push args
 197     52/push-edx
 198     56/push-esi
 199     # . . call
 200     e8/call parse-line/disp32
 201     # . . discard args
 202     81 0/subop/add %esp 8/imm32
 203     # emit-call(out, words)
 204     # . . push args
 205     52/push-edx
 206     ff 6/subop/push *(ebp+0xc)
 207     # . . call
 208     e8/call emit-call/disp32
 209     # . . discard args
 210     81 0/subop/add %esp 8/imm32
 211     # loop
 212     e9/jump $subx-calls:loop/disp32
 213 $subx-calls:break:
 214     # flush(out)
 215     # . . push args
 216     ff 6/subop/push *(ebp+0xc)
 217     # . . call
 218     e8/call flush/disp32
 219     # . . discard args
 220     81 0/subop/add %esp 4/imm32
 221 $subx-calls:end:
 222     # . reclaim locals
 223     81 0/subop/add %esp 0x298/imm32  # 0x20c + 0x8c
 224     # . restore registers
 225     5e/pop-to-esi
 226     5a/pop-to-edx
 227     59/pop-to-ecx
 228     58/pop-to-eax
 229     # . epilogue
 230     89/<- %esp 5/r32/ebp
 231     5d/pop-to-ebp
 232     c3/return
 233 
 234 parse-line:  # line : (address stream byte), words : (address stream slice)
 235     # pseudocode:
 236     #   var word-slice : (address slice)
 237     #   while true
 238     #     word-slice = next-word-string-or-expression-without-metadata(line)
 239     #     if slice-empty?(word-slice)
 240     #       break                                 # end of line
 241     #     write-int(words, word-slice->start)
 242     #     write-int(words, word-slice->end)
 243     #
 244     # . prologue
 245     55/push-ebp
 246     89/<- %ebp 4/r32/esp
 247     # . save registers
 248     51/push-ecx
 249     # var word-slice/ecx : (address slice) = {0, 0}
 250     68/push 0/imm32/end
 251     68/push 0/imm32/start
 252     89/<- %ecx 4/r32/esp
 253 $parse-line:loop:
 254     # word-slice = next-word-string-or-expression-without-metadata(line)
 255     # . . push args
 256     51/push-ecx
 257     ff 6/subop/push *(ebp+8)
 258     # . . call
 259     e8/call next-word-string-or-expression-without-metadata/disp32
 260     # . . discard args
 261     81 0/subop/add %esp 8/imm32
 262 $parse-line:check1:
 263     # if (slice-empty?(word-slice)) break
 264     # . eax = slice-empty?(word-slice)
 265     # . . push args
 266     51/push-ecx
 267     # . . call
 268     e8/call slice-empty?/disp32
 269     # . . discard args
 270     81 0/subop/add %esp 4/imm32
 271     # . if (eax != 0) break
 272     3d/compare-eax-and 0/imm32
 273     0f 85/jump-if-not-equal $parse-line:end/disp32
 274 +-- 46 lines: #?     # dump word-slice -----------------------------------------------------------------------------------------------------------------------
 320 $parse-line:write-word:
 321     # write-int(words, word-slice->start)
 322     # . . push args
 323     ff 6/subop/push *ecx
 324     ff 6/subop/push *(ebp+0xc)
 325     # . . call
 326     e8/call write-int/disp32
 327     # . . discard args
 328     81 0/subop/add %esp 8/imm32
 329     # write-int(words, word-slice->end)
 330     # . . push args
 331     ff 6/subop/push *(ecx+4)
 332     ff 6/subop/push *(ebp+0xc)
 333     # . . call
 334     e8/call write-int/disp32
 335     # . . discard args
 336     81 0/subop/add %esp 8/imm32
 337     # loop
 338     e9/jump $parse-line:loop/disp32
 339 $parse-line:end:
 340     # . reclaim locals
 341     81 0/subop/add %esp 8/imm32
 342     # . restore registers
 343     59/pop-to-ecx
 344     # . epilogue
 345     89/<- %esp 5/r32/ebp
 346     5d/pop-to-ebp
 347     c3/return
 348 
 349 emit-call:  # out : (address buffered-file), words : (address stream slice)
 350     # pseudocode:
 351     #   if (words->write < 8) abort
 352     #   curr = &words->data[words->write-8]
 353     #   min = words->data
 354     #   # emit pushes
 355     #   while true
 356     #     if (curr <= min) break
 357     #     if *curr->start in '%' '*'
 358     #       write-buffered(out, "ff 6/subop/push ")
 359     #       write-slice-buffered(out, curr)
 360     #       write-buffered(out, "\n")
 361     #     else
 362     #       write-buffered(out, "68/push ")
 363     #       write-slice-buffered(out, curr)
 364     #       write-buffered(out, "/imm32\n")
 365     #     curr -= 8
 366     #   # emit call
 367     #   write-buffered(out, "e8/call ")
 368     #   write-slice-buffered(out, curr)
 369     #   write-buffered(out, "/disp32\n")
 370     #   # emit pops
 371     #   write-buffered(out, "81 0/subop/add %esp ")
 372     #   print-int32-buffered(out, words->write >> 1 - 4)
 373     #   write-buffered(out, "/imm32\n")
 374     #
 375     # . prologue
 376     55/push-ebp
 377     89/<- %ebp 4/r32/esp
 378     # . save registers
 379     50/push-eax
 380     51/push-ecx
 381     52/push-edx
 382     56/push-esi
 383     # esi = words
 384     8b/-> *(ebp+0xc) 6/r32/esi
 385     # if (words->write < 8) abort
 386     # . ecx = words->write - 8
 387     8b/-> *esi 1/r32/ecx
 388     81 5/subop/subtract %ecx 8/imm32
 389     0f 8c/jump-if-lesser $emit-call:error1/disp32
 390     # curr/ecx = &words->data[words->write-8]
 391     8d/copy-address *(esi+ecx+0xc) 1/r32/ecx
 392     # min/edx = words->data
 393     8d/copy-address *(esi+0xc) 2/r32/edx
 394     # - emit pushes
 395 $emit-call:push-loop:
 396     # if (curr <= min) break
 397     39/compare %ecx 2/r32/edx
 398     0f 8e/jump-if-lesser-or-equal $emit-call:call-instruction/disp32
 399     # if (*curr->start in '%' '*') goto push-rm32
 400     # . eax = curr->start
 401     8b/-> *ecx 0/r32/eax
 402     # . eax = (byte)*eax
 403     8b/-> *eax 0/r32/eax
 404     81 4/subop/and %eax 0xff/imm32
 405     # . if (eax == '%') goto push-rm32
 406     3d/compare-eax-and 0x25/imm32/percent
 407     74/jump-if-equal $emit-call:push-rm32/disp8
 408     # . if (eax == '*') goto push-rm32
 409     3d/compare-eax-and 0x2a/imm32/asterisk
 410     74/jump-if-equal $emit-call:push-rm32/disp8
 411 $emit-call:push-imm32:
 412     # write-buffered(out, "68/push ")
 413     68/push "68/push "/imm32
 414     ff 6/subop/push *(ebp+8)
 415     # . . call
 416     e8/call write-buffered/disp32
 417     # . . discard args
 418     81 0/subop/add %esp 8/imm32
 419     # write-slice-buffered(out, curr)
 420     # . . push args
 421     51/push-ecx
 422     ff 6/subop/push *(ebp+8)
 423     # . . call
 424     e8/call write-slice-buffered/disp32
 425     # . . discard args
 426     81 0/subop/add %esp 8/imm32
 427     # write-buffered(out, "/imm32\n")
 428     68/push "/imm32\n"/imm32
 429     ff 6/subop/push *(ebp+8)
 430     # . . call
 431     e8/call write-buffered/disp32
 432     # . . discard args
 433     81 0/subop/add %esp 8/imm32
 434     # continue
 435     eb/jump $emit-call:next-push/disp8
 436 $emit-call:push-rm32:
 437     # write-buffered(out, "ff 6/subop/push ")
 438     # . . push args
 439     68/push "ff 6/subop/push "/imm32
 440     ff 6/subop/push *(ebp+8)
 441     # . . call
 442     e8/call write-buffered/disp32
 443     # . . discard args
 444     81 0/subop/add %esp 8/imm32
 445     # write-slice-buffered(out, curr)
 446     # . . push args
 447     51/push-ecx
 448     ff 6/subop/push *(ebp+8)
 449     # . . call
 450     e8/call write-slice-buffered/disp32
 451     # . . discard args
 452     81 0/subop/add %esp 8/imm32
 453     # write-buffered(out, "\n")
 454     68/push Newline/imm32
 455     ff 6/subop/push *(ebp+8)
 456     # . . call
 457     e8/call write-buffered/disp32
 458     # . . discard args
 459     81 0/subop/add %esp 8/imm32
 460 $emit-call:next-push:
 461     # curr -= 8
 462     81 5/subop/subtract %ecx 8/imm32
 463     # loop
 464     e9/jump $emit-call:push-loop/disp32
 465 $emit-call:call-instruction:
 466     # write-buffered(out, "e8/call ")
 467     68/push "e8/call "/imm32
 468     ff 6/subop/push *(ebp+8)
 469     # . . call
 470     e8/call write-buffered/disp32
 471     # . . discard args
 472     81 0/subop/add %esp 8/imm32
 473     # write-slice-buffered(out, curr)
 474     # . . push args
 475     51/push-ecx
 476     ff 6/subop/push *(ebp+8)
 477     # . . call
 478     e8/call write-slice-buffered/disp32
 479     # . . discard args
 480     81 0/subop/add %esp 8/imm32
 481     # write-buffered(out, "/disp32\n")
 482     68/push "/disp32\n"/imm32
 483     ff 6/subop/push *(ebp+8)
 484     # . . call
 485     e8/call write-buffered/disp32
 486     # . . discard args
 487     81 0/subop/add %esp 8/imm32
 488 $emit-call:pop-instruction:
 489     # write-buffered(out, "81 0/subop/add %esp ")
 490     68/push "81 0/subop/add %esp "/imm32
 491     ff 6/subop/push *(ebp+8)
 492     # . . call
 493     e8/call write-buffered/disp32
 494     # . . discard args
 495     81 0/subop/add %esp 8/imm32
 496     # print-int32-buffered(out, words->write >> 1 - 4)
 497     # . . push args
 498     8b/-> *esi 0/r32/eax
 499     c1/shift 7/subop/arith-right %eax 1/imm8
 500     2d/subtract-from-eax 4/imm32
 501     50/push-eax
 502     ff 6/subop/push *(ebp+8)
 503     # . . call
 504     e8/call print-int32-buffered/disp32
 505     # . . discard args
 506     81 0/subop/add %esp 8/imm32
 507     # write-buffered(out, "/imm32\n")
 508     68/push "/imm32\n"/imm32
 509     ff 6/subop/push *(ebp+8)
 510     # . . call
 511     e8/call write-buffered/disp32
 512     # . . discard args
 513     81 0/subop/add %esp 8/imm32
 514 $emit-call:end:
 515     # . restore registers
 516     5e/pop-to-esi
 517     5a/pop-to-edx
 518     59/pop-to-ecx
 519     58/pop-to-eax
 520     # . epilogue
 521     89/<- %esp 5/r32/ebp
 522     5d/pop-to-ebp
 523     c3/return
 524 
 525 $emit-call:error1:
 526     # print(stderr, "error: calls.subx: '()' is not a valid call")
 527     # . write-buffered(Stderr, "error: calls.subx: '()' is not a valid call")
 528     # . . push args
 529     68/push "error: calls.subx: '()' is not a valid call"/imm32
 530     68/push Stderr/imm32
 531     # . . call
 532     e8/call write-buffered/disp32
 533     # . . discard args
 534     81 0/subop/add %esp 8/imm32
 535     # . flush(Stderr)
 536     # . . push args
 537     68/push Stderr/imm32
 538     # . . call
 539     e8/call flush/disp32
 540     # . . discard args
 541     81 0/subop/add %esp 4/imm32
 542     # . syscall(exit, 1)
 543     bb/copy-to-ebx 1/imm32
 544     b8/copy-to-eax 1/imm32/exit
 545     cd/syscall 0x80/imm8
 546     # never gets here
 547 
 548 test-subx-calls-passes-most-lines-through:
 549     # . prologue
 550     55/push-ebp
 551     89/<- %ebp 4/r32/esp
 552     # setup
 553     # . clear-stream(_test-input-stream)
 554     # . . push args
 555     68/push _test-input-stream/imm32
 556     # . . call
 557     e8/call clear-stream/disp32
 558     # . . discard args
 559     81 0/subop/add %esp 4/imm32
 560     # . clear-stream(_test-input-buffered-file+4)
 561     # . . push args
 562     b8/copy-to-eax _test-input-buffered-file/imm32
 563     05/add-to-eax 4/imm32
 564     50/push-eax
 565     # . . call
 566     e8/call clear-stream/disp32
 567     # . . discard args
 568     81 0/subop/add %esp 4/imm32
 569     # . clear-stream(_test-output-stream)
 570     # . . push args
 571     68/push _test-output-stream/imm32
 572     # . . call
 573     e8/call clear-stream/disp32
 574     # . . discard args
 575     81 0/subop/add %esp 4/imm32
 576     # . clear-stream(_test-output-buffered-file+4)
 577     # . . push args
 578     b8/copy-to-eax _test-output-buffered-file/imm32
 579     05/add-to-eax 4/imm32
 580     50/push-eax
 581     # . . call
 582     e8/call clear-stream/disp32
 583     # . . discard args
 584     81 0/subop/add %esp 4/imm32
 585     # . write(_test-input-stream, "== abcd 0x1\n")
 586     # . . push args
 587     68/push "== abcd 0x1\n"/imm32
 588     68/push _test-input-stream/imm32
 589     # . . call
 590     e8/call write/disp32
 591     # . . discard args
 592     81 0/subop/add %esp 8/imm32
 593     # subx-calls(_test-input-buffered-file, _test-output-buffered-file)
 594     # . . push args
 595     68/push _test-output-buffered-file/imm32
 596     68/push _test-input-buffered-file/imm32
 597     # . . call
 598     e8/call subx-calls/disp32
 599     # . . discard args
 600     81 0/subop/add %esp 8/imm32
 601     # check that the line just passed through
 602     # . flush(_test-output-buffered-file)
 603     # . . push args
 604     68/push _test-output-buffered-file/imm32
 605     # . . call
 606     e8/call flush/disp32
 607     # . . discard args
 608     81 0/subop/add %esp 4/imm32
 609     # . check-stream-equal(_test-output-stream, "== abcd 0x1\n", msg)
 610     # . . push args
 611     68/push "F - test-subx-calls-passes-most-lines-through"/imm32
 612     68/push "== abcd 0x1\n"/imm32
 613     68/push _test-output-stream/imm32
 614     # . . call
 615     e8/call check-stream-equal/disp32
 616     # . . discard args
 617     81 0/subop/add %esp 0xc/imm32
 618     # . epilogue
 619     89/<- %esp 5/r32/ebp
 620     5d/pop-to-ebp
 621     c3/return
 622 
 623 test-subx-calls-processes-calls:
 624     # . prologue
 625     55/push-ebp
 626     89/<- %ebp 4/r32/esp
 627     # setup
 628     # . clear-stream(_test-input-stream)
 629     # . . push args
 630     68/push _test-input-stream/imm32
 631     # . . call
 632     e8/call clear-stream/disp32
 633     # . . discard args
 634     81 0/subop/add %esp 4/imm32
 635     # . clear-stream(_test-input-buffered-file+4)
 636     # . . push args
 637     b8/copy-to-eax _test-input-buffered-file/imm32
 638     05/add-to-eax 4/imm32
 639     50/push-eax
 640     # . . call
 641     e8/call clear-stream/disp32
 642     # . . discard args
 643     81 0/subop/add %esp 4/imm32
 644     # . clear-stream(_test-output-stream)
 645     # . . push args
 646     68/push _test-output-stream/imm32
 647     # . . call
 648     e8/call clear-stream/disp32
 649     # . . discard args
 650     81 0/subop/add %esp 4/imm32
 651     # . clear-stream(_test-output-buffered-file+4)
 652     # . . push args
 653     b8/copy-to-eax _test-output-buffered-file/imm32
 654     05/add-to-eax 4/imm32
 655     50/push-eax
 656     # . . call
 657     e8/call clear-stream/disp32
 658     # . . discard args
 659     81 0/subop/add %esp 4/imm32
 660     # . write(_test-input-stream, "(foo %eax)\n")
 661     # . . push args
 662     68/push "(foo %eax)\n"/imm32
 663     68/push _test-input-stream/imm32
 664     # . . call
 665     e8/call write/disp32
 666     # . . discard args
 667     81 0/subop/add %esp 8/imm32
 668     # subx-calls(_test-input-buffered-file, _test-output-buffered-file)
 669     # . . push args
 670     68/push _test-output-buffered-file/imm32
 671     68/push _test-input-buffered-file/imm32
 672     # . . call
 673     e8/call subx-calls/disp32
 674     # . . discard args
 675     81 0/subop/add %esp 8/imm32
 676     # check that the line just passed through
 677     # . flush(_test-output-buffered-file)
 678     # . . push args
 679     68/push _test-output-buffered-file/imm32
 680     # . . call
 681     e8/call flush/disp32
 682     # . . discard args
 683     81 0/subop/add %esp 4/imm32
 684 +-- 33 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
 717     # . check-next-stream-line-equal(_test-output-stream, "# . (foo %eax)", msg)
 718     # . . push args
 719     68/push "F - test-subx-calls-processes-calls: comment"/imm32
 720     68/push "# . (foo %eax)"/imm32
 721     68/push _test-output-stream/imm32
 722     # . . call
 723     e8/call check-next-stream-line-equal/disp32
 724     # . . discard args
 725     81 0/subop/add %esp 0xc/imm32
 726     # . check-next-stream-line-equal(_test-output-stream, "ff 6/subop/push %eax", msg)
 727     # . . push args
 728     68/push "F - test-subx-calls-processes-calls: arg 0"/imm32
 729     68/push "ff 6/subop/push %eax"/imm32
 730     68/push _test-output-stream/imm32
 731     # . . call
 732     e8/call check-next-stream-line-equal/disp32
 733     # . . discard args
 734     81 0/subop/add %esp 0xc/imm32
 735     # . check-next-stream-line-equal(_test-output-stream, "e8/call foo/disp32", msg)
 736     # . . push args
 737     68/push "F - test-subx-calls-processes-calls: call"/imm32
 738     68/push "e8/call foo/disp32"/imm32
 739     68/push _test-output-stream/imm32
 740     # . . call
 741     e8/call check-next-stream-line-equal/disp32
 742     # . . discard args
 743     81 0/subop/add %esp 0xc/imm32
 744     # . check-next-stream-line-equal(_test-output-stream, "81 0/subop/add %esp 4/imm32", msg)
 745     # . . push args
 746     68/push "F - test-subx-calls-processes-calls: pops"/imm32
 747     68/push "81 0/subop/add %esp 0x00000004/imm32"/imm32
 748     68/push _test-output-stream/imm32
 749     # . . call
 750     e8/call check-next-stream-line-equal/disp32
 751     # . . discard args
 752     81 0/subop/add %esp 0xc/imm32
 753     # . epilogue
 754     89/<- %esp 5/r32/ebp
 755     5d/pop-to-ebp
 756     c3/return
 757 
 758 next-word-string-or-expression-without-metadata:  # line : (address stream), out : (address slice)
 759     # pseudocode:
 760     #   skip-chars-matching(line, ' ')
 761     #   if line->read >= line->write              # end of line
 762     #     out = {0, 0}
 763     #     return
 764     #   out->start = &line->data[line->read]
 765     #   if line->data[line->read] == '#'          # comment
 766     #     out->end = &line->data[line->write]     # skip to end of line
 767     #     return
 768     #   if line->data[line->read] == '"'          # string literal
 769     #     skip-string(line)
 770     #     out->end = &line->data[line->read]        # no metadata
 771     #     return
 772     #   if line->data[line->read] == '*'          # expression
 773     #     if line->data[line->read + 1] == ' '
 774     #       abort
 775     #     if line->data[line->read + 1] == '('
 776     #       skip-until-close-paren(line)
 777     #       if (line->data[line->read] != ')'
 778     #         abort
 779     #       ++line->data[line->read] to skip ')'
 780     #     out->end = &line->data[line->read]
 781     #     return
 782     #   if line->data[line->read] == ')'
 783     #     ++line->read to skip ')'
 784     #     # make sure there's nothing else of importance
 785     #     if line->read >= line->write
 786     #       out = {0, 0}
 787     #       return
 788     #     if line->data[line->read] != ' '
 789     #       abort
 790     #     skip-chars-matching-whitespace(line)
 791     #     if line->read >= line->write
 792     #       out = {0, 0}
 793     #       return
 794     #     if line->data[line->read] == '#'        # only thing permitted after ')' is a comment
 795     #       out = {0, 0}
 796     #       return
 797     #     abort
 798     #   # default case: read a word -- but no metadata
 799     #   while true
 800     #     if line->read >= line->write
 801     #       break
 802     #     if line->data[line->read] == ' '
 803     #       break
 804     #     if line->data[line->read] == ')'
 805     #       break
 806     #     if line->data[line->read] == '/'
 807     #       abort
 808     #     ++line->read
 809     #   out->end = &line->data[line->read]
 810     #
 811     # registers:
 812     #   ecx: often line->read
 813     #   eax: often line->data[line->read]
 814     #
 815     # . prologue
 816     55/push-ebp
 817     89/<- %ebp 4/r32/esp
 818     # . save registers
 819     50/push-eax
 820     51/push-ecx
 821     56/push-esi
 822     57/push-edi
 823     # esi = line
 824     8b/-> *(ebp+8) 6/r32/esi
 825     # edi = out
 826     8b/-> *(ebp+0xc) 7/r32/edi
 827     # skip-chars-matching(line, ' ')
 828     # . . push args
 829     68/push 0x20/imm32/space
 830     ff 6/subop/push *(ebp+8)
 831     # . . call
 832     e8/call skip-chars-matching/disp32
 833     # . . discard args
 834     81 0/subop/add %esp 8/imm32
 835 $next-word-string-or-expression-without-metadata:check0:
 836     # if (line->read >= line->write) return out = {0, 0}
 837     # . ecx = line->read
 838     8b/-> *(esi+4) 1/r32/ecx
 839     # . if (ecx >= line->write) return out = {0, 0}
 840     3b/compare *esi 1/r32/ecx
 841     0f 8d/jump-if-greater-or-equal $next-word-string-or-expression-without-metadata:return-eol/disp32
 842 $next-word-string-or-expression-without-metadata:check-for-comment:
 843     # out->start = &line->data[line->read]
 844     8d/copy-address *(esi+ecx+0xc) 0/r32/eax
 845     89/<- *edi 0/r32/eax
 846     # if (line->data[line->read] != '#') goto next check
 847     # . eax = line->data[line->read]
 848     31/xor %eax 0/r32/eax
 849     8a/copy-byte *(esi+ecx+0xc) 0/r32/AL
 850     # . if (eax != '#') goto next check
 851     3d/compare-eax-and 0x23/imm32/pound
 852     75/jump-if-not-equal $next-word-string-or-expression-without-metadata:check-for-string-literal/disp8
 853 $next-word-string-or-expression-without-metadata:comment:
 854     # out->end = &line->data[line->write]
 855     8b/-> *esi 0/r32/eax
 856     8d/copy-address *(esi+eax+0xc) 0/r32/eax
 857     89/<- *(edi+4) 0/r32/eax
 858     # line->read = line->write  # skip rest of line
 859     8b/-> *esi 0/r32/eax
 860     89/<- *(esi+4) 0/r32/eax
 861     # return
 862     e9/jump $next-word-string-or-expression-without-metadata:end/disp32
 863 $next-word-string-or-expression-without-metadata:check-for-string-literal:
 864     # if (line->data[line->read] != '"') goto next check
 865     3d/compare-eax-and 0x22/imm32/dquote
 866     75/jump-if-not-equal $next-word-string-or-expression-without-metadata:check-for-expression/disp8
 867 $next-word-string-or-expression-without-metadata:string-literal:
 868     # skip-string(line)
 869     # . . push args
 870     56/push-esi
 871     # . . call
 872     e8/call skip-string/disp32
 873     # . . discard args
 874     81 0/subop/add %esp 4/imm32
 875     # out->end = &line->data[line->read]
 876     8b/-> *(esi+4) 1/r32/ecx
 877     8d/copy-address *(esi+ecx+0xc) 0/r32/eax
 878     89/<- *(edi+4) 0/r32/eax
 879     # return
 880     e9/jump $next-word-string-or-expression-without-metadata:end/disp32
 881 $next-word-string-or-expression-without-metadata:check-for-expression:
 882     # if (line->data[line->read] != '*') goto next check
 883     3d/compare-eax-and 0x2a/imm32/asterisk
 884     75/jump-if-not-equal $next-word-string-or-expression-without-metadata:check-for-end-of-call/disp8
 885     # if (line->data[line->read + 1] == ' ') goto error1
 886     8a/copy-byte *(esi+ecx+0xd) 0/r32/AL
 887     3d/compare-eax-and 0x20/imm32/space
 888     0f 84/jump-if-equal $next-word-string-or-expression-without-metadata:error1/disp32
 889     # if (line->data[line->read + 1] != '(') goto regular-word
 890     3d/compare-eax-and 0x28/imm32/open-paren
 891     0f 85/jump-if-not-equal $next-word-string-or-expression-without-metadata:regular-word-without-metadata/disp32
 892 $next-word-string-or-expression-without-metadata:paren:
 893     # skip-until-close-paren(line)
 894     # . . push args
 895     56/push-esi
 896     # . . call
 897     e8/call skip-until-close-paren/disp32
 898     # . . discard args
 899     81 0/subop/add %esp 4/imm32
 900     # if (line->data[line->read] != ')') goto error2
 901     # . eax = line->data[line->read]
 902     8b/-> *(esi+4) 1/r32/ecx
 903     8a/copy-byte *(esi+ecx+0xc) 0/r32/AL
 904     # . if (eax != ')') goto error2
 905     3d/compare-eax-and 0x29/imm32/close-paren
 906     0f 85/jump-if-not-equal $next-word-string-or-expression-without-metadata:error2/disp32
 907     # ++line->read to skip ')'
 908     ff 0/subop/increment *(esi+4)
 909     # out->end = &line->data[line->read]
 910     8b/-> *(esi+4) 1/r32/ecx
 911     8d/copy-address *(esi+ecx+0xc) 0/r32/eax
 912     89/<- *(edi+4) 0/r32/eax
 913     # return
 914     e9/jump $next-word-string-or-expression-without-metadata:end/disp32
 915 $next-word-string-or-expression-without-metadata:check-for-end-of-call:
 916     # if (line->data[line->read] != ')') goto next check
 917     3d/compare-eax-and 0x29/imm32/close-paren
 918     75/jump-if-not-equal $next-word-string-or-expression-without-metadata:regular-word-without-metadata/disp8
 919     # ++line->read to skip ')'
 920     ff 0/subop/increment *(esi+4)
 921     # - error checking: make sure there's nothing else of importance on the line
 922     # if (line->read >= line->write) return out = {0, 0}
 923     # . ecx = line->read
 924     8b/-> *(esi+4) 1/r32/ecx
 925     # . if (ecx >= line->write) return {0, 0}
 926     3b/compare *esi 1/r32/ecx
 927     0f 8d/jump-if-greater-or-equal $next-word-string-or-expression-without-metadata:return-eol/disp32
 928     # if (line->data[line->read] == '/') goto error3
 929     # . eax = line->data[line->read]
 930     8a/copy-byte *(esi+ecx+0xc) 0/r32/AL
 931     # . if (eax == '/') goto error3
 932     3d/compare-eax-and 0x2f/imm32/slash
 933     0f 84/jump-if-equal $next-word-string-or-expression-without-metadata:error3/disp32
 934     # skip-chars-matching-whitespace(line)
 935     # . . push args
 936     56/push-esi
 937     # . . call
 938     e8/call skip-chars-matching-whitespace/disp32
 939     # . . discard args
 940     81 0/subop/add %esp 4/imm32
 941     # if (line->read >= line->write) return out = {0, 0}
 942     # . ecx = line->read
 943     8b/-> *(esi+4) 1/r32/ecx
 944     # . if (ecx >= line->write) return {0, 0}
 945     3b/compare *esi 1/r32/ecx
 946     0f 8d/jump-if-greater-or-equal $next-word-string-or-expression-without-metadata:return-eol/disp32
 947     # if (line->data[line->read] == '#') return out = {0, 0}
 948     # . eax = line->data[line->read]
 949     8b/-> *(esi+4) 1/r32/ecx
 950     8a/copy-byte *(esi+ecx+0xc) 0/r32/AL
 951     # . if (eax == '#') return out = {0, 0}
 952     3d/compare-eax-and 0x23/imm32/pound
 953     74/jump-if-equal $next-word-string-or-expression-without-metadata:return-eol/disp8
 954     # otherwise goto error4
 955     e9/jump $next-word-string-or-expression-without-metadata:error4/disp32
 956 $next-word-string-or-expression-without-metadata:regular-word-without-metadata:
 957     # if (line->read >= line->write) break
 958     # . ecx = line->read
 959     8b/-> *(esi+4) 1/r32/ecx
 960     # . if (ecx >= line->write) break
 961     3b/compare *esi 1/r32/ecx
 962     7d/jump-if-greater-or-equal $next-word-string-or-expression-without-metadata:regular-word-break/disp8
 963     # if (line->data[line->read] == ' ') break
 964     # . eax = line->data[line->read]
 965     8b/-> *(esi+4) 1/r32/ecx
 966     8a/copy-byte *(esi+ecx+0xc) 0/r32/AL
 967     # . if (eax == ' ') break
 968     3d/compare-eax-and 0x20/imm32/space
 969     74/jump-if-equal $next-word-string-or-expression-without-metadata:regular-word-break/disp8
 970     # if (line->data[line->read] == ')') break
 971     3d/compare-eax-and 0x29/imm32/close-paren
 972     0f 84/jump-if-equal $next-word-string-or-expression-without-metadata:regular-word-break/disp32
 973     # if (line->data[line->read] == '/') goto error5
 974     3d/compare-eax-and 0x2f/imm32/slash
 975     0f 84/jump-if-equal $next-word-string-or-expression-without-metadata:error5/disp32
 976     # ++line->read
 977     ff 0/subop/increment *(esi+4)
 978     # loop
 979     e9/jump $next-word-string-or-expression-without-metadata:regular-word-without-metadata/disp32
 980 $next-word-string-or-expression-without-metadata:regular-word-break:
 981     # out->end = &line->data[line->read]
 982     8b/-> *(esi+4) 1/r32/ecx
 983     8d/copy-address *(esi+ecx+0xc) 0/r32/eax
 984     89/<- *(edi+4) 0/r32/eax
 985     eb/jump $next-word-string-or-expression-without-metadata:end/disp8
 986 $next-word-string-or-expression-without-metadata:return-eol:
 987     # return out = {0, 0}
 988     c7 0/subop/copy *edi 0/imm32
 989     c7 0/subop/copy *(edi+4) 0/imm32
 990 $next-word-string-or-expression-without-metadata:end:
 991     # . restore registers
 992     5f/pop-to-edi
 993     5e/pop-to-esi
 994     59/pop-to-ecx
 995     58/pop-to-eax
 996     # . epilogue
 997     89/<- %esp 5/r32/ebp
 998     5d/pop-to-ebp
 999     c3/return
1000 
1001 $next-word-string-or-expression-without-metadata:error1:
1002     # print(stderr, "error: no space allowed after '*' in '" line "'")
1003     # . write-buffered(Stderr, "error: no space allowed after '*' in '")
1004     # . . push args
1005     68/push "error: no space allowed after '*' in '"/imm32
1006     68/push Stderr/imm32
1007     # . . call
1008     e8/call write-buffered/disp32
1009     # . . discard args
1010     81 0/subop/add %esp 8/imm32
1011     # . write-stream-data(Stderr, line)
1012     # . . push args
1013     56/push-esi
1014     68/push Stderr/imm32
1015     # . . call
1016     e8/call write-stream-data/disp32
1017     # . . discard args
1018     81 0/subop/add %esp 8/imm32
1019     # . write-buffered(Stderr, "'")
1020     # . . push args
1021     68/push "'"/imm32
1022     68/push Stderr/imm32
1023     # . . call
1024     e8/call write-buffered/disp32
1025     # . . discard args
1026     81 0/subop/add %esp 8/imm32
1027     # . flush(Stderr)
1028     # . . push args
1029     68/push Stderr/imm32
1030     # . . call
1031     e8/call flush/disp32
1032     # . . discard args
1033     81 0/subop/add %esp 4/imm32
1034     # . syscall(exit, 1)
1035     bb/copy-to-ebx 1/imm32
1036     b8/copy-to-eax 1/imm32/exit
1037     cd/syscall 0x80/imm8
1038     # never gets here
1039 
1040 $next-word-string-or-expression-without-metadata:error2:
1041     # print(stderr, "error: *(...) expression must be all on a single line in '" line "'")
1042     # . write-buffered(Stderr, "error: *(...) expression must be all on a single line in '")
1043     # . . push args
1044     68/push "error: *(...) expression must be all on a single line in '"/imm32
1045     68/push Stderr/imm32
1046     # . . call
1047     e8/call write-buffered/disp32
1048     # . . discard args
1049     81 0/subop/add %esp 8/imm32
1050     # . write-stream-data(Stderr, line)
1051     # . . push args
1052     56/push-esi
1053     68/push Stderr/imm32
1054     # . . call
1055     e8/call write-stream-data/disp32
1056     # . . discard args
1057     81 0/subop/add %esp 8/imm32
1058     # . write-buffered(Stderr, "'")
1059     # . . push args
1060     68/push "'"/imm32
1061     68/push Stderr/imm32
1062     # . . call
1063     e8/call write-buffered/disp32
1064     # . . discard args
1065     81 0/subop/add %esp 8/imm32
1066     # . flush(Stderr)
1067     # . . push args
1068     68/push Stderr/imm32
1069     # . . call
1070     e8/call flush/disp32
1071     # . . discard args
1072     81 0/subop/add %esp 4/imm32
1073     # . syscall(exit, 1)
1074     bb/copy-to-ebx 1/imm32
1075     b8/copy-to-eax 1/imm32/exit
1076     cd/syscall 0x80/imm8
1077     # never gets here
1078 
1079 $next-word-string-or-expression-without-metadata:error3:
1080     # print(stderr, "error: no metadata after calls; just use a comment (in '" line "')")
1081     # . write-buffered(Stderr, "error: no metadata after calls; just use a comment (in '")
1082     # . . push args
1083     68/push "error: no metadata after calls; just use a comment (in '"/imm32
1084     68/push Stderr/imm32
1085     # . . call
1086     e8/call write-buffered/disp32
1087     # . . discard args
1088     81 0/subop/add %esp 8/imm32
1089     # . write-stream-data(Stderr, line)
1090     # . . push args
1091     56/push-esi
1092     68/push Stderr/imm32
1093     # . . call
1094     e8/call write-stream-data/disp32
1095     # . . discard args
1096     81 0/subop/add %esp 8/imm32
1097     # . write-buffered(Stderr, "')")
1098     # . . push args
1099     68/push "')"/imm32
1100     68/push Stderr/imm32
1101     # . . call
1102     e8/call write-buffered/disp32
1103     # . . discard args
1104     81 0/subop/add %esp 8/imm32
1105     # . flush(Stderr)
1106     # . . push args
1107     68/push Stderr/imm32
1108     # . . call
1109     e8/call flush/disp32
1110     # . . discard args
1111     81 0/subop/add %esp 4/imm32
1112     # . syscall(exit, 1)
1113     bb/copy-to-ebx 1/imm32
1114     b8/copy-to-eax 1/imm32/exit
1115     cd/syscall 0x80/imm8
1116     # never gets here
1117 
1118 $next-word-string-or-expression-without-metadata:error4:
1119     # print(stderr, "error: unexpected text after end of call in '" line "'")
1120     # . write-buffered(Stderr, "error: unexpected text after end of call in '")
1121     # . . push args
1122     68/push "error: unexpected text after end of call in '"/imm32
1123     68/push Stderr/imm32
1124     # . . call
1125     e8/call write-buffered/disp32
1126     # . . discard args
1127     81 0/subop/add %esp 8/imm32
1128     # . write-stream-data(Stderr, line)
1129     # . . push args
1130     56/push-esi
1131     68/push Stderr/imm32
1132     # . . call
1133     e8/call write-stream-data/disp32
1134     # . . discard args
1135     81 0/subop/add %esp 8/imm32
1136     # . write-buffered(Stderr, "'")
1137     # . . push args
1138     68/push "'"/imm32
1139     68/push Stderr/imm32
1140     # . . call
1141     e8/call write-buffered/disp32
1142     # . . discard args
1143     81 0/subop/add %esp 8/imm32
1144     # . flush(Stderr)
1145     # . . push args
1146     68/push Stderr/imm32
1147     # . . call
1148     e8/call flush/disp32
1149     # . . discard args
1150     81 0/subop/add %esp 4/imm32
1151     # . syscall(exit, 1)
1152     bb/copy-to-ebx 1/imm32
1153     b8/copy-to-eax 1/imm32/exit
1154     cd/syscall 0x80/imm8
1155     # never gets here
1156 
1157 $next-word-string-or-expression-without-metadata:error5:
1158     # print(stderr, "error: no metadata anywhere in calls (in '" line "')")
1159     # . write-buffered(Stderr, "error: no metadata anywhere in calls (in '")
1160     # . . push args
1161     68/push "error: no metadata anywhere in calls (in '"/imm32
1162     68/push Stderr/imm32
1163     # . . call
1164     e8/call write-buffered/disp32
1165     # . . discard args
1166     81 0/subop/add %esp 8/imm32
1167     # . write-stream-data(Stderr, line)
1168     # . . push args
1169     56/push-esi
1170     68/push Stderr/imm32
1171     # . . call
1172     e8/call write-stream-data/disp32
1173     # . . discard args
1174     81 0/subop/add %esp 8/imm32
1175     # . write-buffered(Stderr, "')")
1176     # . . push args
1177     68/push "')"/imm32
1178     68/push Stderr/imm32
1179     # . . call
1180     e8/call write-buffered/disp32
1181     # . . discard args
1182     81 0/subop/add %esp 8/imm32
1183     # . flush(Stderr)
1184     # . . push args
1185     68/push Stderr/imm32
1186     # . . call
1187     e8/call flush/disp32
1188     # . . discard args
1189     81 0/subop/add %esp 4/imm32
1190     # . syscall(exit, 1)
1191     bb/copy-to-ebx 1/imm32
1192     b8/copy-to-eax 1/imm32/exit
1193     cd/syscall 0x80/imm8
1194     # never gets here
1195 
1196 test-next-word-string-or-expression-without-metadata:
1197     # . prologue
1198     55/push-ebp
1199     89/<- %ebp 4/r32/esp
1200     # setup
1201     # . clear-stream(_test-input-stream)
1202     # . . push args
1203     68/push _test-input-stream/imm32
1204     # . . call
1205     e8/call clear-stream/disp32
1206     # . . discard args
1207     81 0/subop/add %esp 4/imm32
1208     # var slice/ecx = {0, 0}
1209     68/push 0/imm32/end
1210     68/push 0/imm32/start
1211     89/<- %ecx 4/r32/esp
1212     # write(_test-input-stream, "  ab")
1213     # . . push args
1214     68/push "  ab"/imm32
1215     68/push _test-input-stream/imm32
1216     # . . call
1217     e8/call write/disp32
1218     # . . discard args
1219     81 0/subop/add %esp 8/imm32
1220     # next-word-string-or-expression-without-metadata(_test-input-stream, slice)
1221     # . . push args
1222     51/push-ecx
1223     68/push _test-input-stream/imm32
1224     # . . call
1225     e8/call next-word-string-or-expression-without-metadata/disp32
1226     # . . discard args
1227     81 0/subop/add %esp 8/imm32
1228     # check-ints-equal(_test-input-stream->read, 4, msg)
1229     # . . push args
1230     68/push "F - test-next-word-string-or-expression-without-metadata/updates-stream-read-correctly"/imm32
1231     68/push 4/imm32
1232     b8/copy-to-eax _test-input-stream/imm32
1233     ff 6/subop/push *(eax+4)
1234     # . . call
1235     e8/call check-ints-equal/disp32
1236     # . . discard args
1237     81 0/subop/add %esp 0xc/imm32
1238     # check-ints-equal(slice->start - _test-input-stream->data, 2, msg)
1239     # . check-ints-equal(slice->start - _test-input-stream, 14, msg)
1240     # . . push args
1241     68/push "F - test-next-word-string-or-expression-without-metadata: start"/imm32
1242     68/push 0xe/imm32
1243     # . . push slice->start - _test-input-stream
1244     8b/-> *ecx 0/r32/eax
1245     81 5/subop/subtract %eax _test-input-stream/imm32
1246     50/push-eax
1247     # . . call
1248     e8/call check-ints-equal/disp32
1249     # . . discard args
1250     81 0/subop/add %esp 0xc/imm32
1251     # check-ints-equal(slice->end - _test-input-stream->data, 4, msg)
1252     # . check-ints-equal(slice->end - _test-input-stream, 16, msg)
1253     # . . push args
1254     68/push "F - test-next-word-string-or-expression-without-metadata: end"/imm32
1255     68/push 0x10/imm32
1256     # . . push slice->end - _test-input-stream
1257     8b/-> *(ecx+4) 0/r32/eax
1258     81 5/subop/subtract %eax _test-input-stream/imm32
1259     50/push-eax
1260     # . . call
1261     e8/call check-ints-equal/disp32
1262     # . . discard args
1263     81 0/subop/add %esp 0xc/imm32
1264     # . epilogue
1265     89/<- %esp 5/r32/ebp
1266     5d/pop-to-ebp
1267     c3/return
1268 
1269 test-next-word-string-or-expression-without-metadata-returns-whole-comment:
1270     # . prologue
1271     55/push-ebp
1272     89/<- %ebp 4/r32/esp
1273     # setup
1274     # . clear-stream(_test-input-stream)
1275     # . . push args
1276     68/push _test-input-stream/imm32
1277     # . . call
1278     e8/call clear-stream/disp32
1279     # . . discard args
1280     81 0/subop/add %esp 4/imm32
1281     # var slice/ecx = {0, 0}
1282     68/push 0/imm32/end
1283     68/push 0/imm32/start
1284     89/<- %ecx 4/r32/esp
1285     # write(_test-input-stream, "  # a")
1286     # . . push args
1287     68/push "  # a"/imm32
1288     68/push _test-input-stream/imm32
1289     # . . call
1290     e8/call write/disp32
1291     # . . discard args
1292     81 0/subop/add %esp 8/imm32
1293     # next-word-string-or-expression-without-metadata(_test-input-stream, slice)
1294     # . . push args
1295     51/push-ecx
1296     68/push _test-input-stream/imm32
1297     # . . call
1298     e8/call next-word-string-or-expression-without-metadata/disp32
1299     # . . discard args
1300     81 0/subop/add %esp 8/imm32
1301     # check-ints-equal(_test-input-stream->read, 5, msg)
1302     # . . push args
1303     68/push "F - test-next-word-string-or-expression-without-metadata-returns-whole-comment/updates-stream-read-correctly"/imm32
1304     68/push 5/imm32
1305     b8/copy-to-eax _test-input-stream/imm32
1306     ff 6/subop/push *(eax+4)
1307     # . . call
1308     e8/call check-ints-equal/disp32
1309     # . . discard args
1310     81 0/subop/add %esp 0xc/imm32
1311     # check-ints-equal(slice->start - _test-input-stream->data, 2, msg)
1312     # . check-ints-equal(slice->start - _test-input-stream, 14, msg)
1313     # . . push args
1314     68/push "F - test-next-word-string-or-expression-without-metadata-returns-whole-comment: start"/imm32
1315     68/push 0xe/imm32
1316     # . . push slice->start - _test-input-stream
1317     8b/-> *ecx 0/r32/eax
1318     81 5/subop/subtract %eax _test-input-stream/imm32
1319     50/push-eax
1320     # . . call
1321     e8/call check-ints-equal/disp32
1322     # . . discard args
1323     81 0/subop/add %esp 0xc/imm32
1324     # check-ints-equal(slice->end - _test-input-stream->data, 5, msg)
1325     # . check-ints-equal(slice->end - _test-input-stream, 17, msg)
1326     # . . push args
1327     68/push "F - test-next-word-string-or-expression-without-metadata-returns-whole-comment: end"/imm32
1328     68/push 0x11/imm32
1329     # . . push slice->end - _test-input-stream
1330     8b/-> *(ecx+4) 0/r32/eax
1331     81 5/subop/subtract %eax _test-input-stream/imm32
1332     50/push-eax
1333     # . . call
1334     e8/call check-ints-equal/disp32
1335     # . . discard args
1336     81 0/subop/add %esp 0xc/imm32
1337     # . epilogue
1338     89/<- %esp 5/r32/ebp
1339     5d/pop-to-ebp
1340     c3/return
1341 
1342 test-next-word-string-or-expression-without-metadata-returns-empty-slice-on-eof:
1343     # . prologue
1344     55/push-ebp
1345     89/<- %ebp 4/r32/esp
1346     # setup
1347     # . clear-stream(_test-input-stream)
1348     # . . push args
1349     68/push _test-input-stream/imm32
1350     # . . call
1351     e8/call clear-stream/disp32
1352     # . . discard args
1353     81 0/subop/add %esp 4/imm32
1354     # var slice/ecx = {0, 0}
1355     68/push 0/imm32/end
1356     68/push 0/imm32/start
1357     89/<- %ecx 4/r32/esp
1358     # write nothing to _test-input-stream
1359     # next-word-string-or-expression-without-metadata(_test-input-stream, slice)
1360     # . . push args
1361     51/push-ecx
1362     68/push _test-input-stream/imm32
1363     # . . call
1364     e8/call next-word-string-or-expression-without-metadata/disp32
1365     # . . discard args
1366     81 0/subop/add %esp 8/imm32
1367     # check-ints-equal(slice->end - slice->start, 0, msg)
1368     # . . push args
1369     68/push "F - test-next-word-string-or-expression-without-metadata-returns-empty-expression-on-eof"/imm32
1370     68/push 0/imm32
1371     # . . push slice->end - slice->start
1372     8b/-> *(ecx+4) 0/r32/eax
1373     2b/subtract-> *ecx 0/r32/eax  # subtract *ecx from eax
1374     50/push-eax
1375     # . . call
1376     e8/call check-ints-equal/disp32
1377     # . . discard args
1378     81 0/subop/add %esp 0xc/imm32
1379     # . epilogue
1380     89/<- %esp 5/r32/ebp
1381     5d/pop-to-ebp
1382     c3/return
1383 
1384 test-next-word-string-or-expression-without-metadata-returns-string-literal:
1385     # . prologue
1386     55/push-ebp
1387     89/<- %ebp 4/r32/esp
1388     # setup
1389     # . clear-stream(_test-input-stream)
1390     # . . push args
1391     68/push _test-input-stream/imm32
1392     # . . call
1393     e8/call clear-stream/disp32
1394     # . . discard args
1395     81 0/subop/add %esp 4/imm32
1396     # var slice/ecx = {0, 0}
1397     68/push 0/imm32/end
1398     68/push 0/imm32/start
1399     89/<- %ecx 4/r32/esp
1400     # write(_test-input-stream, " \"a b\" ")
1401     # . . push args
1402     68/push " \"a b\" "/imm32
1403     68/push _test-input-stream/imm32
1404     # . . call
1405     e8/call write/disp32
1406     # . . discard args
1407     81 0/subop/add %esp 8/imm32
1408     # next-word-string-or-expression-without-metadata(_test-input-stream, slice)
1409     # . . push args
1410     51/push-ecx
1411     68/push _test-input-stream/imm32
1412     # . . call
1413     e8/call next-word-string-or-expression-without-metadata/disp32
1414     # . . discard args
1415     81 0/subop/add %esp 8/imm32
1416     # check-ints-equal(slice->start - _test-input-stream->data, 1, msg)
1417     # . check-ints-equal(slice->start - _test-input-stream, 13, msg)
1418     # . . push args
1419     68/push "F - test-next-word-string-or-expression-without-metadata-returns-string-literal: start"/imm32
1420     68/push 0xd/imm32
1421     # . . push slice->start - _test-input-stream
1422     8b/-> *ecx 0/r32/eax
1423     81 5/subop/subtract %eax _test-input-stream/imm32
1424     50/push-eax
1425     # . . call
1426     e8/call check-ints-equal/disp32
1427     # . . discard args
1428     81 0/subop/add %esp 0xc/imm32
1429     # check-ints-equal(slice->end - _test-input-stream->data, 6, msg)
1430     # . check-ints-equal(slice->end - _test-input-stream, 18, msg)
1431     # . . push args
1432     68/push "F - test-next-word-string-or-expression-without-metadata-returns-string-literal: end"/imm32
1433     68/push 0x12/imm32
1434     # . . push slice->end - _test-input-stream
1435     8b/-> *(ecx+4) 0/r32/eax
1436     81 5/subop/subtract %eax _test-input-stream/imm32
1437     50/push-eax
1438     # . . call
1439     e8/call check-ints-equal/disp32
1440     # . . discard args
1441     81 0/subop/add %esp 0xc/imm32
1442     # . epilogue
1443     89/<- %esp 5/r32/ebp
1444     5d/pop-to-ebp
1445     c3/return
1446 
1447 test-next-word-string-or-expression-without-metadata-returns-string-with-escapes:
1448     # . prologue
1449     55/push-ebp
1450     89/<- %ebp 4/r32/esp
1451     # setup
1452     # . clear-stream(_test-input-stream)
1453     # . . push args
1454     68/push _test-input-stream/imm32
1455     # . . call
1456     e8/call clear-stream/disp32
1457     # . . discard args
1458     81 0/subop/add %esp 4/imm32
1459     # var slice/ecx = {0, 0}
1460     68/push 0/imm32/end
1461     68/push 0/imm32/start
1462     89/<- %ecx 4/r32/esp
1463     # write(_test-input-stream, " \"a\\\"b\"")
1464     # . . push args
1465     68/push " \"a\\\"b\""/imm32
1466     68/push _test-input-stream/imm32
1467     # . . call
1468     e8/call write/disp32
1469     # . . discard args
1470     81 0/subop/add %esp 8/imm32
1471     # next-word-string-or-expression-without-metadata(_test-input-stream, slice)
1472     # . . push args
1473     51/push-ecx
1474     68/push _test-input-stream/imm32
1475     # . . call
1476     e8/call next-word-string-or-expression-without-metadata/disp32
1477     # . . discard args
1478     81 0/subop/add %esp 8/imm32
1479     # check-ints-equal(slice->start - _test-input-stream->data, 1, msg)
1480     # . check-ints-equal(slice->start - _test-input-stream, 13, msg)
1481     # . . push args
1482     68/push "F - test-next-word-string-or-expression-without-metadata-returns-string-with-escapes: start"/imm32
1483     68/push 0xd/imm32
1484     # . . push slice->start - _test-input-stream
1485     8b/-> *ecx 0/r32/eax
1486     81 5/subop/subtract %eax _test-input-stream/imm32
1487     50/push-eax
1488     # . . call
1489     e8/call check-ints-equal/disp32
1490     # . . discard args
1491     81 0/subop/add %esp 0xc/imm32
1492     # check-ints-equal(slice->end - _test-input-stream->data, 7, msg)
1493     # . check-ints-equal(slice->end - _test-input-stream, 19, msg)
1494     # . . push args
1495     68/push "F - test-next-word-string-or-expression-without-metadata-returns-string-with-escapes: end"/imm32
1496     68/push 0x13/imm32
1497     # . . push slice->end - _test-input-stream
1498     8b/-> *(ecx+4) 0/r32/eax
1499     81 5/subop/subtract %eax _test-input-stream/imm32
1500     50/push-eax
1501     # . . call
1502     e8/call check-ints-equal/disp32
1503     # . . discard args
1504     81 0/subop/add %esp 0xc/imm32
1505     # . epilogue
1506     89/<- %esp 5/r32/ebp
1507     5d/pop-to-ebp
1508     c3/return
1509 
1510 test-next-word-string-or-expression-without-metadata-returns-whole-expression:
1511     # . prologue
1512     55/push-ebp
1513     89/<- %ebp 4/r32/esp
1514     # setup
1515     # . clear-stream(_test-input-stream)
1516     # . . push args
1517     68/push _test-input-stream/imm32
1518     # . . call
1519     e8/call clear-stream/disp32
1520     # . . discard args
1521     81 0/subop/add %esp 4/imm32
1522     # var slice/ecx = {0, 0}
1523     68/push 0/imm32/end
1524     68/push 0/imm32/start
1525     89/<- %ecx 4/r32/esp
1526     # write(_test-input-stream, " *(a b) ")
1527     # . . push args
1528     68/push " *(a b) "/imm32
1529     68/push _test-input-stream/imm32
1530     # . . call
1531     e8/call write/disp32
1532     # . . discard args
1533     81 0/subop/add %esp 8/imm32
1534     # next-word-string-or-expression-without-metadata(_test-input-stream, slice)
1535     # . . push args
1536     51/push-ecx
1537     68/push _test-input-stream/imm32
1538     # . . call
1539     e8/call next-word-string-or-expression-without-metadata/disp32
1540     # . . discard args
1541     81 0/subop/add %esp 8/imm32
1542     # check-ints-equal(slice->start - _test-input-stream->data, 1, msg)
1543     # . check-ints-equal(slice->start - _test-input-stream, 13, msg)
1544     # . . push args
1545     68/push "F - test-next-word-string-or-expression-without-metadata-returns-whole-expression: start"/imm32
1546     68/push 0xd/imm32
1547     # . . push slice->start - _test-input-stream
1548     8b/-> *ecx 0/r32/eax
1549     81 5/subop/subtract %eax _test-input-stream/imm32
1550     50/push-eax
1551     # . . call
1552     e8/call check-ints-equal/disp32
1553     # . . discard args
1554     81 0/subop/add %esp 0xc/imm32
1555     # check-ints-equal(slice->end - _test-input-stream->data, 7, msg)
1556     # . check-ints-equal(slice->end - _test-input-stream, 19, msg)
1557     # . . push args
1558     68/push "F - test-next-word-string-or-expression-without-metadata-returns-whole-expression: end"/imm32
1559     68/push 0x13/imm32
1560     # . . push slice->end - _test-input-stream
1561     8b/-> *(ecx+4) 0/r32/eax
1562     81 5/subop/subtract %eax _test-input-stream/imm32
1563     50/push-eax
1564     # . . call
1565     e8/call check-ints-equal/disp32
1566     # . . discard args
1567     81 0/subop/add %esp 0xc/imm32
1568     # . epilogue
1569     89/<- %esp 5/r32/ebp
1570     5d/pop-to-ebp
1571     c3/return
1572 
1573 test-next-word-string-or-expression-without-metadata-returns-eol-on-trailing-close-paren:
1574     # . prologue
1575     55/push-ebp
1576     89/<- %ebp 4/r32/esp
1577     # setup
1578     # . clear-stream(_test-input-stream)
1579     # . . push args
1580     68/push _test-input-stream/imm32
1581     # . . call
1582     e8/call clear-stream/disp32
1583     # . . discard args
1584     81 0/subop/add %esp 4/imm32
1585     # var slice/ecx = {0, 0}
1586     68/push 0/imm32/end
1587     68/push 0/imm32/start
1588     89/<- %ecx 4/r32/esp
1589     # write(_test-input-stream, " ) ")
1590     # . . push args
1591     68/push " ) "/imm32
1592     68/push _test-input-stream/imm32
1593     # . . call
1594     e8/call write/disp32
1595     # . . discard args
1596     81 0/subop/add %esp 8/imm32
1597     # next-word-string-or-expression-without-metadata(_test-input-stream, slice)
1598     # . . push args
1599     51/push-ecx
1600     68/push _test-input-stream/imm32
1601     # . . call
1602     e8/call next-word-string-or-expression-without-metadata/disp32
1603     # . . discard args
1604     81 0/subop/add %esp 8/imm32
1605     # check-ints-equal(slice->start, 0, msg)
1606     # . . push args
1607     68/push "F - test-next-word-string-or-expression-without-metadata-returns-eol-on-trailing-close-paren: start"/imm32
1608     68/push 0/imm32
1609     ff 6/subop/push *ecx
1610     # . . call
1611     e8/call check-ints-equal/disp32
1612     # . . discard args
1613     81 0/subop/add %esp 0xc/imm32
1614     # check-ints-equal(slice->end, 0, msg)
1615     # . . push args
1616     68/push "F - test-next-word-string-or-expression-without-metadata-returns-eol-on-trailing-close-paren: end"/imm32
1617     68/push 0/imm32
1618     ff 6/subop/push *(ecx+4)
1619     # . . call
1620     e8/call check-ints-equal/disp32
1621     # . . discard args
1622     81 0/subop/add %esp 0xc/imm32
1623     # . epilogue
1624     89/<- %esp 5/r32/ebp
1625     5d/pop-to-ebp
1626     c3/return
1627 
1628 test-next-word-string-or-expression-without-metadata-handles-comment-after-trailing-close-paren:
1629     # . prologue
1630     55/push-ebp
1631     89/<- %ebp 4/r32/esp
1632     # setup
1633     # . clear-stream(_test-input-stream)
1634     # . . push args
1635     68/push _test-input-stream/imm32
1636     # . . call
1637     e8/call clear-stream/disp32
1638     # . . discard args
1639     81 0/subop/add %esp 4/imm32
1640     # var slice/ecx = {0, 0}
1641     68/push 0/imm32/end
1642     68/push 0/imm32/start
1643     89/<- %ecx 4/r32/esp
1644     # write(_test-input-stream, " ) # abc ")
1645     # . . push args
1646     68/push " ) # abc "/imm32
1647     68/push _test-input-stream/imm32
1648     # . . call
1649     e8/call write/disp32
1650     # . . discard args
1651     81 0/subop/add %esp 8/imm32
1652     # next-word-string-or-expression-without-metadata(_test-input-stream, slice)
1653     # . . push args
1654     51/push-ecx
1655     68/push _test-input-stream/imm32
1656     # . . call
1657     e8/call next-word-string-or-expression-without-metadata/disp32
1658     # . . discard args
1659     81 0/subop/add %esp 8/imm32
1660     # check-ints-equal(slice->start, 0, msg)
1661     # . . push args
1662     68/push "F - test-next-word-string-or-expression-without-metadata-handles-comment-after-trailing-close-paren: start"/imm32
1663     68/push 0/imm32
1664     ff 6/subop/push *ecx
1665     # . . call
1666     e8/call check-ints-equal/disp32
1667     # . . discard args
1668     81 0/subop/add %esp 0xc/imm32
1669     # check-ints-equal(slice->end, 0, msg)
1670     # . . push args
1671     68/push "F - test-next-word-string-or-expression-without-metadata-handles-comment-after-trailing-close-paren: end"/imm32
1672     68/push 0/imm32
1673     ff 6/subop/push *(ecx+4)
1674     # . . call
1675     e8/call check-ints-equal/disp32
1676     # . . discard args
1677     81 0/subop/add %esp 0xc/imm32
1678     # . epilogue
1679     89/<- %esp 5/r32/ebp
1680     5d/pop-to-ebp
1681     c3/return
1682 
1683 test-next-word-string-or-expression-without-metadata-handles-newline-after-trailing-close-paren:
1684     # . prologue
1685     55/push-ebp
1686     89/<- %ebp 4/r32/esp
1687     # setup
1688     # . clear-stream(_test-input-stream)
1689     # . . push args
1690     68/push _test-input-stream/imm32
1691     # . . call
1692     e8/call clear-stream/disp32
1693     # . . discard args
1694     81 0/subop/add %esp 4/imm32
1695     # var slice/ecx = {0, 0}
1696     68/push 0/imm32/end
1697     68/push 0/imm32/start
1698     89/<- %ecx 4/r32/esp
1699     # write(_test-input-stream, " )\n")
1700     # . . push args
1701     68/push " )\n"/imm32
1702     68/push _test-input-stream/imm32
1703     # . . call
1704     e8/call write/disp32
1705     # . . discard args
1706     81 0/subop/add %esp 8/imm32
1707     # next-word-string-or-expression-without-metadata(_test-input-stream, slice)
1708     # . . push args
1709     51/push-ecx
1710     68/push _test-input-stream/imm32
1711     # . . call
1712     e8/call next-word-string-or-expression-without-metadata/disp32
1713     # . . discard args
1714     81 0/subop/add %esp 8/imm32
1715     # check-ints-equal(slice->start, 0, msg)
1716     # . . push args
1717     68/push "F - test-next-word-string-or-expression-without-metadata-handles-newline-after-trailing-close-paren: start"/imm32
1718     68/push 0/imm32
1719     ff 6/subop/push *ecx
1720     # . . call
1721     e8/call check-ints-equal/disp32
1722     # . . discard args
1723     81 0/subop/add %esp 0xc/imm32
1724     # check-ints-equal(slice->end, 0, msg)
1725     # . . push args
1726     68/push "F - test-next-word-string-or-expression-without-metadata-handles-newline-after-trailing-close-paren: end"/imm32
1727     68/push 0/imm32
1728     ff 6/subop/push *(ecx+4)
1729     # . . call
1730     e8/call check-ints-equal/disp32
1731     # . . discard args
1732     81 0/subop/add %esp 0xc/imm32
1733     # . epilogue
1734     89/<- %esp 5/r32/ebp
1735     5d/pop-to-ebp
1736     c3/return
1737 
1738 test-next-word-string-or-expression-without-metadata-stops-at-close-paren:
1739     # . prologue
1740     55/push-ebp
1741     89/<- %ebp 4/r32/esp
1742     # setup
1743     # . clear-stream(_test-input-stream)
1744     # . . push args
1745     68/push _test-input-stream/imm32
1746     # . . call
1747     e8/call clear-stream/disp32
1748     # . . discard args
1749     81 0/subop/add %esp 4/imm32
1750     # var slice/ecx = {0, 0}
1751     68/push 0/imm32/end
1752     68/push 0/imm32/start
1753     89/<- %ecx 4/r32/esp
1754     # write(_test-input-stream, " abc) # def")
1755     # . . push args
1756     68/push " abc) # def"/imm32
1757     68/push _test-input-stream/imm32
1758     # . . call
1759     e8/call write/disp32
1760     # . . discard args
1761     81 0/subop/add %esp 8/imm32
1762     # next-word-string-or-expression-without-metadata(_test-input-stream, slice)
1763     # . . push args
1764     51/push-ecx
1765     68/push _test-input-stream/imm32
1766     # . . call
1767     e8/call next-word-string-or-expression-without-metadata/disp32
1768     # . . discard args
1769     81 0/subop/add %esp 8/imm32
1770     # check-ints-equal(slice->start - _test-input-stream->data, 1, msg)
1771     # . check-ints-equal(slice->start - _test-input-stream, 13, msg)
1772     # . . push args
1773     68/push "F - test-next-word-string-or-expression-without-metadata-stops-at-close-paren: start"/imm32
1774     68/push 0xd/imm32
1775     # . . push slice->start - _test-input-stream
1776     8b/-> *ecx 0/r32/eax
1777     81 5/subop/subtract %eax _test-input-stream/imm32
1778     50/push-eax
1779     # . . call
1780     e8/call check-ints-equal/disp32
1781     # . . discard args
1782     81 0/subop/add %esp 0xc/imm32
1783     # check-ints-equal(slice->end - _test-input-stream->data, 4, msg)
1784     # . check-ints-equal(slice->end - _test-input-stream, 16, msg)
1785     # . . push args
1786     68/push "F - test-next-word-string-or-expression-without-metadata-stops-at-close-paren: end"/imm32
1787     68/push 0x10/imm32
1788     # . . push slice->end - _test-input-stream
1789     8b/-> *(ecx+4) 0/r32/eax
1790     81 5/subop/subtract %eax _test-input-stream/imm32
1791     50/push-eax
1792     # . . call
1793     e8/call check-ints-equal/disp32
1794     # . . discard args
1795     81 0/subop/add %esp 0xc/imm32
1796     # . epilogue
1797     89/<- %esp 5/r32/ebp
1798     5d/pop-to-ebp
1799     c3/return