https://github.com/akkartik/mu/blob/main/linux/calls.subx

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