about summary refs log blame commit diff stats
path: root/apps/desugar.subx
blob: a6d3af7f691cbe2a4cb6155e03856e12fddc3451 (plain) (tree)
1
2
3
4
5
6
7
8


                                
                                                


                   
                                                


                   









                                                        









                                                                                    







                                                                                                                                                 
                                     

                       
                                                                                                                                                                       




                                                                                                                                                                  
                                                  






















                                                                                                                                                                                      
                            
                   




                           
                                                                                                                                                                  





                                

                                                                              
                                                                                 




                                      
                                                                       
                    
                                                          
                                                                       
                   
                                                                   
                      


                                                                         


                                                                            
                                                         





                                                   

















                                                                                                                                                                         
                   














                                                                                                                                                                     




                                                                                                                                                                    
                                               



                   
                                           

                                                                                                                                                                  
                
                                         






                                                                                                                                                                  
                           
                               










                                                                                                                                                                               
                               

                                                               
                                                                
                     













































                                                                                                                                                                     
                                       
                   
                                                                                                                                                                      

               
                                    
                      
                                                                                                                                                                  
              
                                      


                                                               
                                                                   
                       
















                                                                                                                                                                  









                                                                                                                                                                      







                                                          

















                                                                                                                                                                      

                                                                                                                                                                  
                                      

                               
                   
                          

                                                                                                                                                                      
                                  

                                                                                                                                                                  

                                      




















                                                                                                                                                                       






















































                                                                                                                                                                  
                         
                                                                          



                                                                                                                                                                       
               




                                                                                                                                                                            

                                                          
                              
                             
                                                                                                                                                                     
                            
              
                             
                      
                                                                                                                                                                  



                                                                                                                                                                      
              
                                  

                                                                                                                                                                  



                                                                                                                                                                      
              
                                        

                                                                                                                                                                  



                                                                                                                                                                      
              
                                  

                                                                                                                                                                  
                      
                         
                 



                                                                                                                                                                       
 
                      


                                                                                                                                                                       
















                                                                                                                                                                  

                                


                                                                                                                                                                             
                        


                                                                                                                                                                       
                                                       

                                             
               
              
                                    
                      
                                                                                                                                                                
                                         
                   
































                                                                                                                                                                     
                                                

                                                 
              
                                      

                                                                                                                                                                  




                                                                                                                                                                       
                        



















                                                                                                                                                                       

                                


                                                                                                                                                                             
                        


                                                                                                                                                                       
                                                           

                                             
               
              
                                    
                      
                                                                                                                                                                
                                         
                   
                                             
              






























                                                                                                                                                                     
                                                



                                                 





                                                                                                                                                                       
 










                                                                                                                                                                       


























































































                                                                                                                                                                                          

                             












































































































































































































































































































































                                                                                                                                                                                







                                                                                                                                                                       
                                                         



                                                                                    
                         
                                                                                              
































                                                             




                                                                                                                                                                       
               
                
                                                                                                                                                                            
                               
                                                                                                                                                                      
                         

                                                                


                                    
                                              



                                                                                                                                                                               
                                                                        












                                                                                                                                                                  
                                                                                                                                                                        


                                               

                                                                                                                                                                      
                     









                                                                                                                                                                        
                             








                                                                                                                                                                        






















                                            
                             

                                                                                                                                                                       
                         
                 





                                                                                                                                                                       







































                                                                                                                                                                            
























































                                                                                                                                                                             





























































                                                                                                                                                                             




                                                                                                                                                                       



                                                                                                       
                                                                            





























































































































































































































                                                                                                                                                                             

                          










































































                                                                                                                                                                              
                                          

                                                    
                    















































                                                                                                                                                                       
                                          

                                                                   
                    







































                                                                                                                                                                        
                                                        

                                                                          
                    


























                                                                                                                                                                             
                            
                                                                                                                                                                            
                                                                                      









                                                                                                                                                                               



























                                                                                                                                                                             
                                                            

                                                             
                    




























                                                                                                                                                                             
                                                            

                                                                            
                    












































                                                                                                                                                                             
















                                
# Desugar a few kinds of syntax.
#
# 1.
#   $ echo "ab %eax"  |  ./subx run apps/desugar
#   ab 3/mod 0/rm32
#
# 2.
#   $ echo "ab *eax"  |  ./subx run apps/desugar
#   ab 0/mod 0/rm32
#
# 3.
#   $ echo "ab *(eax+4)"  |  ./subx run apps/desugar
#   ab 2/mod 0/rm32 4/disp32
#
# 4.
#   $ echo "ab *(eax+ecx)"  |  ./subx run apps/desugar
#   ab 0/mod 4/rm32 0/base 1/index 0/scale
#
# 5.
#   $ echo "ab *(eax+ecx+4)"  |  ./subx run apps/desugar
#   ab 2/mod 4/rm32 0/base 1/index 0/scale 4/disp32
#
# 6.
#   $ echo "ab *(eax+ecx<<2+4)"  |  ./subx run apps/desugar
#   ab 2/mod 4/rm32 0/base 1/index 2/scale 4/disp32
#
# Addition isn't commutative here. Template must always be (base+index<<scale+disp),
# though some components are optional as described above.
#
# No metadata allowed inside '*(...)'.
# Whitespace inside '*(...)' is ok.

== code
#   instruction                     effective address                                                   register    displacement    immediate
# . op          subop               mod             rm32          base        index         scale       r32
# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes

Entry:  # run tests if necessary, convert stdin if not
    # initialize heap
    # . Heap = new-segment(Heap-size)
    # . . push args
    68/push  Heap/imm32
    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Heap-size/disp32                  # push *Heap-size
    # . . call
    e8/call  new-segment/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP

    # run tests if necessary, convert stdin if not
    # . prolog
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # - if argc > 1 and argv[1] == "test", then return run_tests()
    # . argc > 1
    81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0/disp8         1/imm32           # compare *EBP
    7e/jump-if-lesser-or-equal  $run-main/disp8
    # . argv[1] == "test"
    # . . push args
    68/push  "test"/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . call
    e8/call  kernel-string-equal?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . check result
    3d/compare-EAX-and  1/imm32
    75/jump-if-not-equal  $run-main/disp8
    # . run-tests()
    e8/call  run-tests/disp32
    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
    eb/jump  $main:end/disp8
$run-main:
    # - otherwise convert stdin
    # convert(Stdin, Stdout)
    # . . push args
    68/push  Stdout/imm32
    68/push  Stdin/imm32
    # . . call
    e8/call  convert/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x8/imm32         # add to ESP
    # . syscall(exit, 0)
    bb/copy-to-EBX  0/imm32
$main:end:
    b8/copy-to-EAX  1/imm32/exit
    cd/syscall  0x80/imm8

# error messages considered:
#   *x + 34                 -> error: base+disp addressing must be within '()'
convert:  # in : (address buffered-file), out : (address buffered-file) -> <void>
    # pseudocode:
    #   var line = new-stream(512, 1)
    #   while true
    #     clear-stream(line)
    #     read-line-buffered(in, line)
    #     if (line->write == 0) break                     # end of file
    #     while true
    #       var word-slice = next-word-or-expression(line)
    #       if slice-empty?(word-slice)                   # end of line
    #         break
    #       if slice-starts-with?(word-slice, "#")        # comment
    #         continue
    #       if slice-starts-with?(word-slice, '%')        # direct mode
    #         emit-direct-mode(word-slice, out)
    #       else if slice-starts-with?(word-slice, '*')   # indirect mode
    #         base, index, scale, disp = parse-effective-address(word-slice)
    #         emit-indirect-mode(out, base, index, scale, disp)
    #       else if slice-starts-with?(word-slice, '+')
    #         abort("'+' only permitted within '*(...)'")
    #       else
    #         write-slice-buffered(out, word-slice)
    #       write(out, " ")
    #     write(out, "\n")
    #   flush(out)
    #
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # . save registers
    50/push-EAX
    51/push-ECX
    52/push-EDX
    53/push-EBX
    # var line/ECX : (address stream byte) = stream(512)
    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x200/imm32       # subtract from ESP
    68/push  0x200/imm32/length
    68/push  0/imm32/read
    68/push  0/imm32/write
    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
    # var word-slice/EDX = {0, 0}
    68/push  0/imm32/end
    68/push  0/imm32/start
    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
$convert:line-loop:
    # clear-stream(line)
    # . . push args
    51/push-ECX
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # read-line-buffered(in, line)
    # . . push args
    51/push-ECX
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . call
    e8/call  read-line-buffered/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
$convert:check0:
    # if (line->write == 0) break
    81          7/subop/compare     0/mod/indirect  1/rm32/ECX    .           .             .           .           .               0/imm32           # compare *ECX
    0f 84/jump-if-equal  $convert:break/disp32
$convert:word-loop:
    # next-word-or-expression(line, word-slice)
    # . . push args
    52/push-EDX
    51/push-ECX
    # . . call
    e8/call  next-word-or-expression/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
$convert:check1:
    # if (slice-empty?(word-slice)) break
    # . EAX = slice-empty?(word-slice)
    # . . push args
    52/push-EDX
    # . . call
    e8/call  slice-empty?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # . if (EAX != 0) break
    3d/compare-EAX-and  0/imm32
    0f 85/jump-if-not-equal  $convert:next-line/disp32
$convert:check-for-comment:
    # if (slice-starts-with?(word-slice, "#")) continue
    # . start/EBX = word-slice->start
    8b/copy                         0/mod/indirect  2/rm32/EDX    .           .             .           3/r32/EBX   .               .                 # copy *EDX to EBX
    # . c/EAX = *start
    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
    8a/copy-byte                    0/mod/indirect  3/rm32/EBX    .           .             .           0/r32/AL    .               .                 # copy byte at *EBX to AL
    # . if (EAX == '#') continue
    3d/compare-EAX-and  0x23/imm32/hash
    74/jump-if-equal  $convert:word-loop/disp8
$convert:check-for-direct-mode:
    # if (!slice-starts-with?(word-slice, "%")) goto next check
    3d/compare-EAX-and  0x25/imm32/percent
    75/jump-if-not-equal  $convert:check-for-indirect-mode/disp8
$convert:direct-mode:
#?     # dump word-slice {{{
#?     # . write(2/stderr, "w: ")
#?     # . . push args
#?     68/push  "w: "/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # . clear-stream(Stderr+4)
#?     # . . save EAX
#?     50/push-EAX
#?     # . . push args
#?     b8/copy-to-EAX  Stderr/imm32
#?     05/add-to-EAX  4/imm32
#?     50/push-EAX
#?     # . . call
#?     e8/call  clear-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
#?     # . . restore EAX
#?     58/pop-to-EAX
#?     # . write-slice-buffered(Stderr, word-slice)
#?     # . . push args
#?     52/push-EDX
#?     68/push  Stderr/imm32
#?     # . . call
#?     e8/call  write-slice-buffered/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # . flush(Stderr)
#?     # . . push args
#?     68/push  Stderr/imm32
#?     # . . call
#?     e8/call  flush/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
#?     # . write(2/stderr, "$\n")
#?     # . . push args
#?     68/push  "$\n"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # }}}
    # emit-direct-mode(word-slice, out)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    52/push-EDX
    # . . call
    e8/call  emit-direct-mode/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # continue
    e9/jump  $convert:next-word/disp32
$convert:check-for-indirect-mode:
    # if (!slice-starts-with?(word-slice, "*")) goto next check
    3d/compare-EAX-and  0x2a/imm32/asterisk
    75/jump-if-not-equal  $convert:check-for-invalid-addition/disp8
$convert:indirect-mode:
    # spill registers
    50/push-EAX
    51/push-ECX
    52/push-EDX
    53/push-EBX
    # base/EAX, index/ECX, scale/EDX, disp/EBX = parse-effective-address(word-slice)
    # . . push args
    52/push-EDX
    # . . call
    e8/call  parse-effective-address/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # restore registers
    5b/pop-to-EBX
    5a/pop-to-EDX
    59/pop-to-ECX
    58/pop-to-EAX
    # emit-indirect-mode(word-slice, out)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    52/push-EDX
    # . . call
    e8/call  emit-indirect-mode/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # continue
    e9/jump  $convert:next-word/disp32
$convert:check-for-invalid-addition:
    # if (slice-starts-with?(word-slice, "+")) goto error1
    3d/compare-EAX-and  0x2b/imm32/plus
    74/jump-if-equal  $convert:error1/disp8
$convert:check-for-invalid-left-shift:
    # if (slice-starts-with?(word-slice, "<")) goto error1
    3d/compare-EAX-and  0x3c/imm32/less-than
    74/jump-if-equal  $convert:error1/disp8
$convert:regular-word:
    # write-slice-buffered(out, word-slice)
    # . . push args
    52/push-EDX
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    # . . call
    e8/call  write-slice-buffered/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # fall through
$convert:next-word:
    # write-buffered(out, " ")
    # . . push args
    68/push  " "/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    # . . call
    e8/call  write-buffered/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # loop
    e9/jump  $convert:word-loop/disp32
$convert:next-line:
    # write-buffered(out, "\n")
    # . . push args
    68/push  Newline/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    # . . call
    e8/call  write-buffered/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # loop
    e9/jump  $convert:line-loop/disp32
$convert:break:
    # flush(out)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    # . . call
    e8/call  flush/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
$convert:end:
    # . reclaim locals
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x214/imm32       # add to ESP
    # . restore registers
    5b/pop-to-EBX
    5a/pop-to-EDX
    59/pop-to-ECX
    58/pop-to-EAX
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

$convert:error1:
    # print(stderr, "error: '" EAX "' only permitted within '*(...)' in '" line "'")
    # . write-buffered(Stderr, "error: '")
    # . . push args
    68/push  "error: '"/imm32
    68/push  Stderr/imm32
    # . . call
    e8/call  write-buffered/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . write-byte-buffered(Stderr, EAX)
    # . . push args
    50/push-EAX
    68/push  Stderr/imm32
    # . . call
    e8/call  write-byte-buffered/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . write-buffered(Stderr, "' only permitted within '*(...)' in '")
    # . . push args
    68/push  "' only permitted within '*(...)' in '"/imm32
    68/push  Stderr/imm32
    # . . call
    e8/call  write-buffered/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . write-stream-data(Stderr, line)
    # . . push args
    51/push-ECX
    68/push  Stderr/imm32
    # . . call
    e8/call  write-stream-data/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . write-buffered(Stderr, "'")
    # . . push args
    68/push  "'"/imm32
    68/push  Stderr/imm32
    # . . call
    e8/call  write-buffered/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . flush(Stderr)
    # . . push args
    68/push  Stderr/imm32
    # . . call
    e8/call  flush/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # . syscall(exit, 1)
    bb/copy-to-EBX  1/imm32
    b8/copy-to-EAX  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

# beware: modifies 'word'
emit-direct-mode:  # word : (address slice), out : (address buffered-file)
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # . save registers
    50/push-EAX
    # ++word->start
    # . EAX = word
    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   8/disp8         .                 # copy *(EBP+8) to EAX
    # . ++(*EAX)
    ff          0/subop/increment   0/mod/indirect  0/rm32/EAX    .           .             .           .           .               .                 # increment *EAX
    # reg-num/EAX = get-slice(Registers, word, row-size=8)
    # . . push args
    68/push  "Registers"/imm32
    68/push  8/imm32/row-size
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    68/push  Registers/imm32
    # . . call
    e8/call  get-slice/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
    # write-buffered(out, "3/mod/direct ")
    # . . push args
    68/push  "3/mod/direct "/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    # . . call
    e8/call  write-buffered/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # print-int32-buffered(out, *EAX)
    # . . push args
    ff          6/subop/push        0/mod/indirect  0/rm32/EAX    .           .             .           .           .               .                 # push *EAX
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    # . . call
    e8/call  print-int32-buffered/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # write-buffered(out, "/rm32")
    # . . push args
    68/push  "/rm32"/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    # . . call
    e8/call  write-buffered/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
$emit-direct-mode:end:
    # . restore registers
    58/pop-to-EAX
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

test-emit-direct-mode:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # setup
    # . clear-stream(_test-output-stream)
    # . . push args
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # . clear-stream(_test-output-buffered-file+4)
    # . . push args
    b8/copy-to-EAX  _test-output-buffered-file/imm32
    05/add-to-EAX  4/imm32
    50/push-EAX
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # var slice/ECX = "%eax"
    b8/copy-to-EAX  "%eax"/imm32
    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  1/index/ECX   .           1/r32/ECX   4/disp8         .                 # copy EAX+ECX+4 to ECX
    05/add-to-EAX  4/imm32
    # . ECX = {EAX, ECX}
    51/push-ECX
    50/push-EAX
    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
    # emit-direct-mode(str, _test-output-buffered-file)
    # . . push args
    68/push  _test-output-buffered-file/imm32
    51/push-ECX
    # . . call
    e8/call  emit-direct-mode/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32         # add to ESP
    # . flush(_test-output-buffered-file)
    # . . push args
    68/push  _test-output-buffered-file/imm32
    # . . call
    e8/call  flush/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
#?     # dump output {{{
#?     # . write(2/stderr, "^")
#?     # . . push args
#?     68/push  "^"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # . write-stream(2/stderr, _test-output-stream)
#?     # . . push args
#?     68/push  _test-output-stream/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # . write(2/stderr, "$\n")
#?     # . . push args
#?     68/push  "$\n"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # }}}
    # check-stream-equal(_test-output-stream, "3/mod/direct 0/rm32", msg)
    # . . push args
    68/push  "F - test-emit-direct-mode/0"/imm32
    68/push  "3/mod/direct 0x00000000/rm32"/imm32
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  check-stream-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

test-emit-direct-mode-2:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # setup
    # . clear-stream(_test-output-stream)
    # . . push args
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # . clear-stream(_test-output-buffered-file+4)
    # . . push args
    b8/copy-to-EAX  _test-output-buffered-file/imm32
    05/add-to-EAX  4/imm32
    50/push-EAX
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # var slice/ECX = "%edi"
    b8/copy-to-EAX  "%edi"/imm32
    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  1/index/ECX   .           1/r32/ECX   4/disp8         .                 # copy EAX+ECX+4 to ECX
    05/add-to-EAX  4/imm32
    # . ECX = {EAX, ECX}
    51/push-ECX
    50/push-EAX
    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
    # emit-direct-mode(str/ECX, _test-output-buffered-file)
    # . . push args
    68/push  _test-output-buffered-file/imm32
    51/push-ECX
    # . . call
    e8/call  emit-direct-mode/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32         # add to ESP
    # . flush(_test-output-buffered-file)
    # . . push args
    68/push  _test-output-buffered-file/imm32
    # . . call
    e8/call  flush/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
#?     # dump output {{{
#?     # . write(2/stderr, "^")
#?     # . . push args
#?     68/push  "^"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # . write-stream(2/stderr, _test-output-stream)
#?     # . . push args
#?     68/push  _test-output-stream/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # . write(2/stderr, "$\n")
#?     # . . push args
#?     68/push  "$\n"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # }}}
    # check-stream-equal(_test-output-stream, "3/mod/direct 7/rm32", msg)
    # . . push args
    68/push  "F - test-emit-direct-mode/1"/imm32
    68/push  "3/mod/direct 0x00000007/rm32"/imm32
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  check-stream-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

# (re)compute the bounds of the next word or parenthetical expression in the line
# return empty string on reaching end of file
#
# error messages considered:
#   * ...                   -> error: no space after '*'
#   *(...                   -> error: *(...) expression must be all on a single line
next-word-or-expression:  # line : (address stream byte), out : (address slice)
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # . save registers
    50/push-EAX
    51/push-ECX
    56/push-ESI
    57/push-EDI
    # ESI = line
    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
    # EDI = out
    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   0xc/disp8       .                 # copy *(EBP+12) to EDI
    # skip-chars-matching(line, ' ')
    # . . push args
    68/push  0x20/imm32/space
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . call
    e8/call  skip-chars-matching/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
$next-word-or-expression:check0:
    # if (line->read >= line->write) clear out and return
    # . EAX = line->read
    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ESI+4) to EAX
    # . if (EAX < line->write) goto next check
    3b/compare                      0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # compare EAX with *ESI
    7c/jump-if-lesser  $next-word-or-expression:check-for-comment/disp8
    # . return out = {0, 0}
    c7          0/subop/copy        0/mod/direct    7/rm32/EDI    .           .             .           .           .               0/imm32           # copy to *EDI
    c7          0/subop/copy        1/mod/*+disp8   7/rm32/EDI    .           .             .           .           4/disp8         0/imm32           # copy to *(EDI+4)
    eb/jump  $next-word-or-expression:end/disp8
$next-word-or-expression:check-for-comment:
    # out->start = &line->data[line->read]
    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(ESI+4) to ECX
    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/ESI  1/index/ECX   .           0/r32/EAX   0xc/disp8       .                 # copy ESI+ECX+12 to EAX
    89/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           0/r32/EAX   .               .                 # copy EAX to *EDI
    # if (line->data[line->read] != '#') goto next check
    # . EAX = line->data[line->read]
    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
    8a/copy-byte                    1/mod/*+disp8   4/rm32/sib    6/base/ESI  1/index/ECX   .           0/r32/AL    0xc/disp8       .                 # copy byte at *(ESI+ECX+12) to AL
    # . compare
    3d/compare-EAX-and  0x23/imm32/pound
    75/jump-if-not-equal  $next-word-or-expression:check-for-paren/disp8
$next-word-or-expression:comment:
    # out->end = &line->data[line->write]
    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # copy *ESI to EAX
    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/ESI  0/index/EAX   .           0/r32/EAX   0xc/disp8       .                 # copy ESI+EAX+12 to EAX
    89/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(EDI+4)
    # line->read = line->write  # skip rest of line
    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # copy *ESI to EAX
    89/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(ESI+4)
    # return
    eb/jump  $next-word-or-expression:end/disp8
$next-word-or-expression:check-for-paren:
    # if (line->data[line->read] != '*') goto next check
    # . EAX = line->data[line->read]
    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
    8a/copy-byte                    1/mod/*+disp8   4/rm32/sib    6/base/ESI  1/index/ECX   .           0/r32/AL    0xc/disp8       .                 # copy byte at *(ESI+ECX+12) to AL
    # . compare
    3d/compare-EAX-and  0x2a/imm32/asterisk
    75/jump-if-not-equal  $next-word-or-expression:regular-word/disp8
    # if (line->data[line->read] != '(') goto error1
    8a/copy-byte                    1/mod/*+disp8   4/rm32/sib    6/base/ESI  1/index/ECX   .           0/r32/AL    0xd/disp8       .                 # copy byte at *(ESI+ECX+12+1) to AL
    # . compare
    3d/compare-EAX-and  0x28/imm32/open-paren
    75/jump-if-not-equal  $next-word-or-expression:error1/disp8
$next-word-or-expression:paren:
    # skip-until-close-paren(line)
    # . . push args
    56/push-ESI
    # . . call
    e8/call  skip-until-close-paren/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # if (line->data[line->read] != ')') goto error2
    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(ESI+4) to ECX
    8a/copy-byte                    1/mod/*+disp8   4/rm32/sib    6/base/ESI  1/index/ECX   .           0/r32/AL    0xc/disp8       .                 # copy byte at *(ESI+ECX+12) to AL
    # . compare
    3d/compare-EAX-and  0x29/imm32/close-paren
    75/jump-if-not-equal  $next-word-or-expression:error2/disp8
    # skip ')'
    ff          0/subop/increment   1/mod/*+disp8   6/rm32/ESI    .           .             .           .           4/disp8         .                 # increment *(ESI+4)
    # fall through
$next-word-or-expression:regular-word:
    # skip-chars-not-matching-whitespace(line)  # including trailing newline
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . call
    e8/call  skip-chars-not-matching-whitespace/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # out->end = &line->data[line->read]
    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(ESI+4) to ECX
    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/ESI  1/index/ECX   .           0/r32/EAX   0xc/disp8       .                 # copy ESI+ECX+12 to EAX
    89/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(EDI+4)
$next-word-or-expression:end:
    # . restore registers
    5f/pop-to-EDI
    5e/pop-to-ESI
    59/pop-to-ECX
    58/pop-to-EAX
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

$next-word-or-expression:error1:
    # print(stderr, "error: no space allowed after '*' in '" line "'")
    # . write-buffered(Stderr, "error: no space allowed after '*' in '")
    # . . push args
    68/push  "error: no space allowed after '*' in '"/imm32
    68/push  Stderr/imm32
    # . . call
    e8/call  write-buffered/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . write-stream-data(Stderr, line)
    # . . push args
    56/push-ESI
    68/push  Stderr/imm32
    # . . call
    e8/call  write-stream-data/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . write-buffered(Stderr, "'")
    # . . push args
    68/push  "'"/imm32
    68/push  Stderr/imm32
    # . . call
    e8/call  write-buffered/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . flush(Stderr)
    # . . push args
    68/push  Stderr/imm32
    # . . call
    e8/call  flush/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # . syscall(exit, 1)
    bb/copy-to-EBX  1/imm32
    b8/copy-to-EAX  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

$next-word-or-expression:error2:
    # print(stderr, "error: no space allowed after '*' in '" line "'")
    # . write-buffered(Stderr, "error: *(...) expression must be all on a single line in '")
    # . . push args
    68/push  "error: *(...) expression must be all on a single line in '"/imm32
    68/push  Stderr/imm32
    # . . call
    e8/call  write-buffered/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . write-stream-data(Stderr, line)
    # . . push args
    56/push-ESI
    68/push  Stderr/imm32
    # . . call
    e8/call  write-stream-data/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . write-buffered(Stderr, "'")
    # . . push args
    68/push  "'"/imm32
    68/push  Stderr/imm32
    # . . call
    e8/call  write-buffered/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . flush(Stderr)
    # . . push args
    68/push  Stderr/imm32
    # . . call
    e8/call  flush/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # . syscall(exit, 1)
    bb/copy-to-EBX  1/imm32
    b8/copy-to-EAX  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

test-next-word-or-expression:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # setup
    # . clear-stream(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # var slice/ECX = {0, 0}
    68/push  0/imm32/end
    68/push  0/imm32/start
    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
    # write(_test-input-stream, "  ab")
    # . . push args
    68/push  "  ab"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # next-word-or-expression(_test-input-stream, slice)
    # . . push args
    51/push-ECX
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  next-word-or-expression/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # check-ints-equal(_test-input-stream->read, 4, msg)
    # . . push args
    68/push  "F - test-next-word-or-expression/updates-stream-read-correctly"/imm32
    68/push  4/imm32
    b8/copy-to-EAX  _test-input-stream/imm32
    ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         .                 # push *(EAX+4)
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # check-ints-equal(slice->start - _test-input-stream->data, 2, msg)
    # . check-ints-equal(slice->start - _test-input-stream, 14, msg)
    # . . push args
    68/push  "F - test-next-word-or-expression: start"/imm32
    68/push  0xe/imm32
    # . . push slice->start - _test-input-stream
    8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy *ECX to EAX
    81          5/subop/subtract    3/mod/direct    0/rm32/EAX    .           .             .           .           .               _test-input-stream/imm32 # subtract from EAX
    50/push-EAX
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # check-ints-equal(slice->end - _test-input-stream->data, 4, msg)
    # . check-ints-equal(slice->end - _test-input-stream, 16, msg)
    # . . push args
    68/push  "F - test-next-word-or-expression: end"/imm32
    68/push  0x10/imm32
    # . . push slice->end - _test-input-stream
    8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ECX+4) to EAX
    81          5/subop/subtract    3/mod/direct    0/rm32/EAX    .           .             .           .           .               _test-input-stream/imm32 # subtract from EAX
    50/push-EAX
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

test-next-word-or-expression-returns-whole-comment:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # setup
    # . clear-stream(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # var slice/ECX = {0, 0}
    68/push  0/imm32/end
    68/push  0/imm32/start
    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
    # write(_test-input-stream, "  # a")
    # . . push args
    68/push  "  # a"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # next-word-or-expression(_test-input-stream, slice)
    # . . push args
    51/push-ECX
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  next-word-or-expression/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # check-ints-equal(_test-input-stream->read, 5, msg)
    # . . push args
    68/push  "F - test-next-word-or-expression-returns-whole-comment/updates-stream-read-correctly"/imm32
    68/push  5/imm32
    b8/copy-to-EAX  _test-input-stream/imm32
    ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         .                 # push *(EAX+4)
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # check-ints-equal(slice->start - _test-input-stream->data, 2, msg)
    # . check-ints-equal(slice->start - _test-input-stream, 14, msg)
    # . . push args
    68/push  "F - test-next-word-or-expression-returns-whole-comment: start"/imm32
    68/push  0xe/imm32
    # . . push slice->start - _test-input-stream
    8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy *ECX to EAX
    81          5/subop/subtract    3/mod/direct    0/rm32/EAX    .           .             .           .           .               _test-input-stream/imm32 # subtract from EAX
    50/push-EAX
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # check-ints-equal(slice->end - _test-input-stream->data, 5, msg)
    # . check-ints-equal(slice->end - _test-input-stream, 17, msg)
    # . . push args
    68/push  "F - test-next-word-or-expression-returns-whole-comment: end"/imm32
    68/push  0x11/imm32
    # . . push slice->end - _test-input-stream
    8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ECX+4) to EAX
    81          5/subop/subtract    3/mod/direct    0/rm32/EAX    .           .             .           .           .               _test-input-stream/imm32 # subtract from EAX
    50/push-EAX
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

test-next-word-or-expression-returns-empty-expression-on-eof:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # setup
    # . clear-stream(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # var slice/ECX = {0, 0}
    68/push  0/imm32/end
    68/push  0/imm32/start
    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
    # write nothing to _test-input-stream
    # next-word-or-expression(_test-input-stream, slice)
    # . . push args
    51/push-ECX
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  next-word-or-expression/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # check-ints-equal(slice->end - slice->start, 0, msg)
    # . . push args
    68/push  "F - test-next-word-or-expression-returns-empty-expression-on-eof"/imm32
    68/push  0/imm32
    # . . push slice->end - slice->start
    8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ECX+4) to EAX
    2b/subtract                     0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # subtract *ECX from EAX
    50/push-EAX
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

test-next-word-or-expression-returns-whole-expression:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # setup
    # . clear-stream(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # var slice/ECX = {0, 0}
    68/push  0/imm32/end
    68/push  0/imm32/start
    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
    # write(_test-input-stream, " *(a b)/imm32 ")
    # . . push args
    68/push  " *(a b)/imm32 "/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # next-word-or-expression(_test-input-stream, slice)
    # . . push args
    51/push-ECX
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  next-word-or-expression/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # check-ints-equal(slice->start - _test-input-stream->data, 1, msg)
    # . check-ints-equal(slice->start - _test-input-stream, 13, msg)
    # . . push args
    68/push  "F - test-next-word-or-expression-returns-whole-expression: start"/imm32
    68/push  0xd/imm32
    # . . push slice->start - _test-input-stream
    8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy *ECX to EAX
    81          5/subop/subtract    3/mod/direct    0/rm32/EAX    .           .             .           .           .               _test-input-stream/imm32 # subtract from EAX
    50/push-EAX
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # check-ints-equal(slice->end - _test-input-stream->data, 13, msg)
    # . check-ints-equal(slice->end - _test-input-stream, 25, msg)
    # . . push args
    68/push  "F - test-next-word-or-expression-returns-whole-expression: end"/imm32
    68/push  0x19/imm32
    # . . push slice->end - _test-input-stream
    8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ECX+4) to EAX
    81          5/subop/subtract    3/mod/direct    0/rm32/EAX    .           .             .           .           .               _test-input-stream/imm32 # subtract from EAX
    50/push-EAX
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

# Grammar:
#   *reg                    -> 0/mod reg/rm32
#   *(reg)                  -> 0/mod reg/rm32
#   *(reg+disp)             -> 2/mod reg/rm32 disp/disp32
#   *(reg1+reg2<<s)         -> 2/mod 4/rm32 reg1/base reg2/index s/scale 0/disp32
#   *(reg1+reg2<<s+disp)    -> 2/mod 4/rm32 reg1/base reg2/index s/scale disp/disp32
# Intermediate structure: base, index, scale, disp
# Default values: base: 0, index: 4 (none), scale: 0, disp: 0
# beware: modifies 'word'
parse-effective-address:  # word : (address slice) -> base/EAX, index/ECX, scale/EDX, disp/EBX
    # pseudocode:
    #   ++word->start to skip '*'
    #   initialize defaults: base=0, index=4, scale=0, disp=0
    #   if (*word->start != '(') {
    #     base = get-slice(Registers, word, row-size=8)
    #     return
    #   }
    #   # compound expressions
    #   skip whitespace
    #   read register into base
    #   skip whitespace
    #   if (*word->start == ')') goto end
    #   if (*word->start != '+') goto error2
    #   ++word->start to skip '+'
    #   skip whitespace
    #   read register into index
    #   skip whitespace
    #   if (*word->start == ')') goto end
    #   if (*word->start == '<') {
    #     ++word->start to skip '<'
    #     if (*word->start != '<') goto error3
    #     ++word->start to skip '<'
    #     skip whitespace
    #     read register into scale
    #     skip whitespace
    #     if (*word->start == ')') goto end
    #   }
    #   if (*word->start != '+') goto error4
    #   ++word->start to skip '+'
    #   skip whitespace
    #   read register into disp
    #   skip whitespace
    #   if (*word->start != ')') goto error5
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # . save registers
    56/push-ESI
    57/push-EDI
    # ESI = word
    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
    # ++word->start to skip '*'
    ff          0/subop/increment   0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # increment *ESI
    # initialize defaults
    # base is in EDI; we'll move it to EAX just before we return
    bf/copy-to-EDI  0/imm32
    b9/copy-to-ECX  4/imm32/no-index
    ba/copy-to-EDX  0/imm32/.scale
    bb/copy-to-EBX  0/imm32/disp
    # if (*word->start == '(') goto next check
    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # copy *ESI to EAX
    8a/copy-byte                    0/mod/indirect  0/rm32/EAX    .           .             .           0/r32/AL    .               .                 # copy byte at *EAX to AL
    81          4/subop/and         3/mod/direct    0/rm32/EAX    .           .             .           .           .               0xff/imm32        # bitwise and of EAX
    3d/compare-EAX-and  0x28/imm32/open-paren
    74/jump-if-equal  $parse-effective-address:compound-expression/disp8
$parse-effective-address:simple-register:
    # base = get-slice(Registers, word, row-size=8)
    # . EAX = get-slice(Registers, word, row-size=8)
    # . . push args
    68/push  "Registers"/imm32
    68/push  8/imm32/row-size
    56/push-ESI
    68/push  Registers/imm32
    # . . call
    e8/call  get-slice/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
    # . base = *EAX
    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           7/r32/EDI   .               .                 # copy *EAX to EDI
    # return
    eb/jump  $parse-effective-address:end/disp8
$parse-effective-address:compound-expression:
    # ++word->start to skip '('
    ff          0/subop/increment   0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # increment *ESI
    # skip whitespace
    # . EAX = skip-chars-matching-whitespace-in-slice(word->start, word->end)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   6/rm32/ESI    .           .             .           .           4/disp8         .                 # push *(ESI+4)
    ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
    # . . call
    e8/call  skip-chars-matching-whitespace-in-slice/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . word->start = EAX
    89/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # copy EAX to *ESI
    # read register into base
    # . EAX = next-register(word)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . call
    e8/call  next-register/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # . EDI = *EAX
    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           7/r32/EDI   .               .                 # copy *EAX to EAX
    # skip whitespace
    # if (*word->start == ')') goto end
    # if (*word->start != '+') goto error2
    # ++word->start
    # skip whitespace
    # read register into index
    # skip whitespace
    # if (*word->start == ')') goto end
    # if (*word->start == '<') {
    #   ++word->start to skip '<'
    #   if (*word->start != '<') goto error3
    #   ++word->start to skip '<'
    #   skip whitespace
    #   read register into scale
    #   skip whitespace
    #   if (*word->start == ')') goto end
    # }
    # if (*word->start != '+') goto error4
    # ++word->start to skip '+'
    # skip whitespace
    # read register into disp
    # skip whitespace
    # if (*word->start != ')') goto error5
$parse-effective-address:end:
    # return base in EAX
    89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           7/r32/EDI   .               .                 # copy EDI to EAX
    # . restore registers
    5f/pop-to-EDI
    5e/pop-to-ESI
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

# assumes 'in' starts with a register name, and returns its code
# side-effect: modifies 'in' to scan past the initial register name
next-register:  # in : (address slice) -> reg/EAX : int
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # . save registers
    51/push-ECX
    56/push-ESI
    # ESI = in
    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
    # var reg-slice/ECX : (address slice) = {in->start, in->start + 3}
    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # copy *ESI to EAX
    05/add-to-EAX  3/imm32
    50/push-EAX
    ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
    # in->start += 3
    81          0/subop/add         0/mod/indirect  6/rm32/ESI    .           .             .           .           .               3/imm32           # add to *ESI
    # EAX = get-slice(Registers, word, row-size=8)
    # . . push args
    68/push  "next-register"/imm32
    68/push  8/imm32/row-size
    51/push-ECX
    68/push  Registers/imm32
    # . . call
    e8/call  get-slice/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
$next-register:end:
    # reclaim locals
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . restore registers
    5e/pop-to-ESI
    59/pop-to-ECX
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

test-parse-effective-address-simple:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # var slice/ECX = "*esi"
    b8/copy-to-EAX  "*esi"/imm32
    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  1/index/ECX   .           1/r32/ECX   4/disp8         .                 # copy EAX+ECX+4 to ECX
    05/add-to-EAX  4/imm32
    # . ECX = {EAX, ECX}
    51/push-ECX
    50/push-EAX
    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
    # EAX, ECX, EDX, EBX = parse-effective-address(slice)
    # . . push args
    51/push-ECX
    # . . call
    e8/call  parse-effective-address/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # slice clobbered beyond this point
    # check-ints-equal(EAX, 6, msg)
    # . . push args
    68/push  "F - test-parse-effective-address-simple/base"/imm32
    68/push  6/imm32/ESI
    50/push-EAX
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # check-ints-equal(ECX, 4, msg)
    # . . push args
    68/push  "F - test-parse-effective-address-simple/index"/imm32
    68/push  4/imm32/none
    51/push-ECX
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # check-ints-equal(EDX, 0, msg)
    # . . push args
    68/push  "F - test-parse-effective-address-simple/scale"/imm32
    68/push  0/imm32/none
    52/push-EDX
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # check-ints-equal(EBX, 0, msg)
    # . . push args
    68/push  "F - test-parse-effective-address-simple/displacement"/imm32
    68/push  0/imm32/none
    53/push-EBX
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

test-parse-effective-address-base:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # var slice/ECX = "*(esi  )"
    b8/copy-to-EAX  "*(esi  )"/imm32
    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  1/index/ECX   .           1/r32/ECX   4/disp8         .                 # copy EAX+ECX+4 to ECX
    05/add-to-EAX  4/imm32
    # . ECX = {EAX, ECX}
    51/push-ECX
    50/push-EAX
    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
    # EAX, ECX, EDX, EBX = parse-effective-address(slice)
    # . . push args
    51/push-ECX
    # . . call
    e8/call  parse-effective-address/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # slice clobbered beyond this point
    # check-ints-equal(EAX, 6, msg)
    # . . push args
    68/push  "F - test-parse-effective-address-base/base"/imm32
    68/push  6/imm32/ESI
    50/push-EAX
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # check-ints-equal(ECX, 4, msg)
    # . . push args
    68/push  "F - test-parse-effective-address-base/index"/imm32
    68/push  4/imm32/none
    51/push-ECX
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # check-ints-equal(EDX, 0, msg)
    # . . push args
    68/push  "F - test-parse-effective-address-base/scale"/imm32
    68/push  0/imm32/none
    52/push-EDX
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # check-ints-equal(EBX, 0, msg)
    # . . push args
    68/push  "F - test-parse-effective-address-base/displacement"/imm32
    68/push  0/imm32/none
    53/push-EBX
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

# Code generation:
#   if index is none and disp is 0, then mod = 0 and rm32 = base
#   if index is none, then mod = 2 and rm32 = base and disp32 = disp
#   if index is not none, then mod = 2 and rm32 = 4 and base = base and index = index and disp32 = disp
emit-indirect-mode:  # word : (address slice), out : (address buffered-file)
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # . save registers
    50/push-EAX
    # ++word->start
    # . EAX = word
    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   8/disp8         .                 # copy *(EBP+8) to EAX
    # . ++(*EAX)
    ff          0/subop/increment   0/mod/indirect  0/rm32/EAX    .           .             .           .           .               .                 # increment *EAX
    # reg-num/EAX = get-slice(Registers, word, row-size=8)
    # . . push args
    68/push  "Registers"/imm32
    68/push  8/imm32/row-size
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    68/push  Registers/imm32
    # . . call
    e8/call  get-slice/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
    # write-buffered(out, "0/mod/indirect ")
    # . . push args
    68/push  "0/mod/indirect "/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    # . . call
    e8/call  write-buffered/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # print-int32-buffered(out, *EAX)
    # . . push args
    ff          6/subop/push        0/mod/indirect  0/rm32/EAX    .           .             .           .           .               .                 # push *EAX
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    # . . call
    e8/call  print-int32-buffered/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # write-buffered(out, "/rm32")
    # . . push args
    68/push  "/rm32"/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    # . . call
    e8/call  write-buffered/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
$emit-indirect-mode:end:
    # . restore registers
    58/pop-to-EAX
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

test-emit-indirect-mode:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # setup
    # . clear-stream(_test-output-stream)
    # . . push args
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # . clear-stream(_test-output-buffered-file+4)
    # . . push args
    b8/copy-to-EAX  _test-output-buffered-file/imm32
    05/add-to-EAX  4/imm32
    50/push-EAX
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # var slice/ECX = "*eax"
    b8/copy-to-EAX  "*eax"/imm32
    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  1/index/ECX   .           1/r32/ECX   4/disp8         .                 # copy EAX+ECX+4 to ECX
    05/add-to-EAX  4/imm32
    # . ECX = {EAX, ECX}
    51/push-ECX
    50/push-EAX
    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
    # emit-indirect-mode(str, _test-output-buffered-file)
    # . . push args
    68/push  _test-output-buffered-file/imm32
    51/push-ECX
    # . . call
    e8/call  emit-indirect-mode/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32         # add to ESP
    # . flush(_test-output-buffered-file)
    # . . push args
    68/push  _test-output-buffered-file/imm32
    # . . call
    e8/call  flush/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
#?     # dump output {{{
#?     # . write(2/stderr, "^")
#?     # . . push args
#?     68/push  "^"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # . write-stream(2/stderr, _test-output-stream)
#?     # . . push args
#?     68/push  _test-output-stream/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # . write(2/stderr, "$\n")
#?     # . . push args
#?     68/push  "$\n"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # }}}
    # check-stream-equal(_test-output-stream, "0/mod/indirect 0/rm32", msg)
    # . . push args
    68/push  "F - test-emit-indirect-mode/0"/imm32
    68/push  "0/mod/indirect 0x00000000/rm32"/imm32
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  check-stream-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

test-emit-indirect-mode-2:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # setup
    # . clear-stream(_test-output-stream)
    # . . push args
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # . clear-stream(_test-output-buffered-file+4)
    # . . push args
    b8/copy-to-EAX  _test-output-buffered-file/imm32
    05/add-to-EAX  4/imm32
    50/push-EAX
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # var slice/ECX = "*edi"
    b8/copy-to-EAX  "*edi"/imm32
    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  1/index/ECX   .           1/r32/ECX   4/disp8         .                 # copy EAX+ECX+4 to ECX
    05/add-to-EAX  4/imm32
    # . ECX = {EAX, ECX}
    51/push-ECX
    50/push-EAX
    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
    # emit-indirect-mode(str/ECX, _test-output-buffered-file)
    # . . push args
    68/push  _test-output-buffered-file/imm32
    51/push-ECX
    # . . call
    e8/call  emit-indirect-mode/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32         # add to ESP
    # . flush(_test-output-buffered-file)
    # . . push args
    68/push  _test-output-buffered-file/imm32
    # . . call
    e8/call  flush/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
#?     # dump output {{{
#?     # . write(2/stderr, "^")
#?     # . . push args
#?     68/push  "^"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # . write-stream(2/stderr, _test-output-stream)
#?     # . . push args
#?     68/push  _test-output-stream/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # . write(2/stderr, "$\n")
#?     # . . push args
#?     68/push  "$\n"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # }}}
    # check-stream-equal(_test-output-stream, "0/mod/indirect 7/rm32", msg)
    # . . push args
    68/push  "F - test-emit-indirect-mode/1"/imm32
    68/push  "0/mod/indirect 0x00000007/rm32"/imm32
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  check-stream-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

# update line->read to ')'
# line->read ends at ')'
skip-until-close-paren:  # line : (address stream)
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # . save registers
    50/push-EAX
    51/push-ECX
    52/push-EDX
    # ECX = line
    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         1/r32/ECX   8/disp8         .                 # copy *(EBP+8) to ECX
    # EAX = skip-until-close-paren-in-slice(&line->data[line->read], &line->data[line->write])
    # . . push &line->data[line->write]
    8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .                         2/r32/EDX   8/disp8         .                 # copy *(ECX+8) to EDX
    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    1/base/ECX  2/index/EDX   .           2/r32/EDX   0xc/disp8       .                 # copy ECX+EDX+12 to EDX
    52/push-EDX
    # . . push &line->data[line->read]
    8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .                         2/r32/EDX   4/disp8         .                 # copy *(ECX+4) to EDX
    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    1/base/ECX  2/index/EDX   .           2/r32/EDX   0xc/disp8       .                 # copy ECX+EDX+12 to EDX
    52/push-EDX
    # . . call
    e8/call  skip-until-close-paren-in-slice/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # line->read = EAX - line->data
    29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
    2d/subtract-from-EAX  0xc/imm32
    89/copy                         1/mod/*+disp8   1/rm32/ECX    .           .                         0/r32/EAX   4/disp8         .                 # copy EAX to *(ECX+4)
$skip-until-close-paren:end:
    # . restore registers
    5a/pop-to-EDX
    59/pop-to-ECX
    58/pop-to-EAX
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

test-skip-until-close-paren:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # setup
    # . clear-stream(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # . write(_test-input-stream, "*(abc) def")
    # .                   indices:  0123 45
    # . . push args
    68/push  "*(abc) def"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # precondition: line->read == 0
    # . . push args
    68/push  "F - test-skip-until-close-paren/precondition"/imm32
    68/push  0/imm32
    b8/copy-to-EAX  _test-input-stream/imm32
    ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         .                 # push *(EAX+4)
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # skip-until-close-paren(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  skip-until-close-paren/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # check-ints-equal(line->read, 5, msg)
    # . . push args
    68/push  "F - test-skip-until-close-paren"/imm32
    68/push  5/imm32
    b8/copy-to-EAX  _test-input-stream/imm32
    ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         .                 # push *(EAX+4)
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

test-skip-until-close-paren-ignores-spaces:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # setup
    # . clear-stream(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # . write(_test-input-stream, "*(a b)/yz")
    # . . push args
    68/push  "*(a b)/yz"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # precondition: line->read == 0
    # . . push args
    68/push  "F - test-skip-until-close-paren-ignores-spaces/precondition"/imm32
    68/push  0/imm32
    b8/copy-to-EAX  _test-input-stream/imm32
    ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         .                 # push *(EAX+4)
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # skip-until-close-paren(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  skip-until-close-paren/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # check-ints-equal(line->read, 5, msg)
    # . . push args
    68/push  "F - test-skip-until-close-paren-ignores-spaces"/imm32
    68/push  5/imm32
    b8/copy-to-EAX  _test-input-stream/imm32
    ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         .                 # push *(EAX+4)
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

test-skip-until-close-paren-works-from-mid-stream:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # setup
    # . clear-stream(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # . write(_test-input-stream, "0 *(a b)/yz")
    # . . push args
    68/push  "0 *(a b)/yz"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # precondition: _test-input-stream->read == 2
    c7          0/subop/copy        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         2/imm32           # copy to *(EAX+4)
    # skip-until-close-paren(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  skip-until-close-paren/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # check-ints-equal(_test-input-stream->read, 7, msg)
    # . . push args
    68/push  "F - test-skip-until-close-paren-works-from-mid-stream"/imm32
    68/push  7/imm32
    b8/copy-to-EAX  _test-input-stream/imm32
    ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         .                 # push *(EAX+4)
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

skip-until-close-paren-in-slice:  # curr : (address byte), end : (address byte) -> new_curr/EAX
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # . save registers
    51/push-ECX
    52/push-EDX
    # ECX = curr
    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         1/r32/ECX   8/disp8         .                 # copy *(EBP+8) to ECX
    # EDX = end
    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         2/r32/EDX   0xc/disp8         .               # copy *(EBP+12) to EDX
    # EAX = 0
    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
    # skip initial dquote
    41/increment-ECX
$skip-until-close-paren-in-slice:loop:
    # if (curr >= end) break
    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
    73/jump-if-greater-unsigned-or-equal  $skip-until-close-paren-in-slice:break/disp8
    # AL = *curr
    8a/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/AL    .               .                 # copy byte at *ECX to AL
$skip-until-close-paren-in-slice:check-close:
    # if (EAX == ')') break
    3d/compare-EAX-and  0x29/imm32/close-paren
    74/jump-if-equal  $skip-until-close-paren-in-slice:break/disp8
    # ++curr
    41/increment-ECX
    eb/jump  $skip-until-close-paren-in-slice:loop/disp8
$skip-until-close-paren-in-slice:break:
    # return curr
    89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy ECX to EAX
$skip-until-close-paren-in-slice:end:
    # . restore registers
    5a/pop-to-EDX
    59/pop-to-ECX
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

test-skip-until-close-paren-in-slice:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # setup: (EAX..ECX) = "*(abc) def"
    b8/copy-to-EAX  "*(abc) def"/imm32
    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  1/index/ECX   .           1/r32/ECX   4/disp8         .                 # copy EAX+ECX+4 to ECX
    05/add-to-EAX  4/imm32
    # EAX = skip-until-close-paren-in-slice(EAX, ECX)
    # . . push args
    51/push-ECX
    50/push-EAX
    # . . call
    e8/call  skip-until-close-paren-in-slice/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # check-ints-equal(ECX-EAX, 5, msg)  # EAX is at the ')'
    # . . push args
    68/push  "F - test-skip-until-close-paren-in-slice"/imm32
    68/push  5/imm32
    # . . push ECX-EAX
    29/subtract                     3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # subtract EAX from ECX
    51/push-ECX
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

test-skip-until-close-paren-in-slice-ignores-spaces:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # setup: (EAX..ECX) = "*(a b)/yz"
    b8/copy-to-EAX  "*(a b)/yz"/imm32
    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  1/index/ECX   .           1/r32/ECX   4/disp8         .                 # copy EAX+ECX+4 to ECX
    05/add-to-EAX  4/imm32
    # EAX = skip-until-close-paren-in-slice(EAX, ECX)
    # . . push args
    51/push-ECX
    50/push-EAX
    # . . call
    e8/call  skip-until-close-paren-in-slice/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # check-ints-equal(ECX-EAX, 4, msg)  # EAX is at the ')'
    # . . push args
    68/push  "F - test-skip-until-close-paren-in-slice-ignores-spaces"/imm32
    68/push  4/imm32
    # . . push ECX-EAX
    29/subtract                     3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # subtract EAX from ECX
    51/push-ECX
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

test-skip-until-close-paren-in-slice-stops-at-end:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # setup: (EAX..ECX) = "*(abc"  # unbalanced dquote
    b8/copy-to-EAX  "*(abc"/imm32
    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  1/index/ECX   .           1/r32/ECX   4/disp8         .                 # copy EAX+ECX+4 to ECX
    05/add-to-EAX  4/imm32
    # EAX = skip-until-close-paren-in-slice(EAX, ECX)
    # . . push args
    51/push-ECX
    50/push-EAX
    # . . call
    e8/call  skip-until-close-paren-in-slice/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # check-ints-equal(ECX-EAX, 0, msg)  # skipped to end of slice
    # . . push args
    68/push  "F - test-skip-until-close-paren-in-slice-stops-at-end"/imm32
    68/push  0/imm32
    # . . push ECX-EAX
    29/subtract                     3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # subtract EAX from ECX
    51/push-ECX
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

== data
Registers:  # (table string int)
  # a table is a stream
  0x40/imm32/write
  0/imm32/read
  0x40/imm32/length
  # data
  "eax"/imm32  0/imm32
  "ecx"/imm32  1/imm32
  "edx"/imm32  2/imm32
  "ebx"/imm32  3/imm32
  "esp"/imm32  4/imm32
  "ebp"/imm32  5/imm32
  "esi"/imm32  6/imm32
  "edi"/imm32  7/imm32

# . . vim:nowrap:textwidth=0