about summary refs log blame commit diff stats
path: root/apps/desugar.subx
blob: c100520ee623c4c118a9a61101ff722a4dc068c3 (plain) (tree)
1
2
3
4
5
6
7
8
9
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778


                                                     

    
                                                


                   
                                                


                   









                                                        








                                                                                    
                                                                             







                                                                                                                                                 
                                     

                       
                                                                                                                                                                       




                                                                                                                                                                  
                                                  






















                                                                                                                                                                                      
                            
                   




                           
                                                                                                                                                                  





                                

                                                                              
                                                                                 




                                      
                                                                       
                    
                                                          
                                                                       
                   
                                                                   
                      


                                                                         


                                                                            
                                                         





                                                   

















                                                                                                                                                                         
                   














                                                                                                                                                                     




                                                                                                                                                                    
                                               



                   
                                           

                                                                                                                                                                  
                
                                         






                                                                                                                                                                  
                           
                               










                                                                                                                                                                               
                               

                                                               
                                                                
                     













































                                                                                                                                                                     
                                       
                   
                                                                                                                                                                      

               
                                    
                      
                                                                                                                                                                  
              
                                      


                                                               
                                                                   
                       











                                                                                                                                                                  
                                                       
                   
               
               


                                                                                                                                                                      


                                      





                                                                                                                                                                  

                                      







                                                          

















                                                                                                                                                                      

                                                                                                                                                                  
                                      

                               
                   
                          

                                                                                                                                                                      
                                  

                                                                                                                                                                  

                                      




















                                                                                                                                                                       






















































                                                                                                                                                                  











































































































































































































                                                                                                                                                                       





























































































































































































                                                                                                                                                                       
                                                                                   






                                                                        






































































































































































































                                                                                                                                                                       
                                                                 




                                                                                                                        





                                                                                                                                                                       





































































































                                                                                                                                                                       
                         
                                                                          



                                                                                                                                                                       
               




                                                                                                                                                                            

                                                          
                              
                             
                                                                                                                                                                     
                            
              
                             
                      
                                                                                                                                                                  



                                                                                                                                                                      
              
                                  

                                                                                                                                                                  



                                                                                                                                                                      
              
                                        

                                                                                                                                                                  



                                                                                                                                                                      
              
                                  

                                                                                                                                                                  
                      
                         
                 



                                                                                                                                                                       
 
                      


                                                                                                                                                                       
















                                                                                                                                                                  

                                


                                                                                                                                                                             
                        


                                                                                                                                                                       
                                                       

                                             
               
              
                                    
                      
                                                                                                                                                                
                                         
                   
































                                                                                                                                                                     
                                                

                                                 
              
                                      

                                                                                                                                                                  




                                                                                                                                                                       
                        



















                                                                                                                                                                       

                                


                                                                                                                                                                             
                        


                                                                                                                                                                       
                                                           

                                             
               
              
                                    
                      
                                                                                                                                                                
                                         
                   
                                             
              






























                                                                                                                                                                     
                                                



                                                 





                                                                                                                                                                       
 










                                                                                                                                                                       
























































                                                                                                                                                                                        





                                                                                                                                                                                          


                                                                                                                                                                                          
                                                                     




























                                                                                                                                                                                        

                             












































































































































































































































































































































                                                                                                                                                                                







                                                                                                                                                                       
                                                         



                                                                                    
                         
                                                                                              











                                                             
                                                  
                                            

                                 
                                                                     




                                         
                                              

                                   
                                 


                                           
                                                    

                              
                       
                                            




                                                                                                                                                                       
               
                
                                                                                                                                                                            
                               
                                                                                                                                                                      
                         

                                                                


                                    

                                                       



                                                                                                                                                                               
                                                                        












                                                                                                                                                                  
                                                                                                                                                                        
            
                                                
                                             

                                                                                                                                                                      
                     









                                                                                                                                                                        
                             







                                                                                                                                                                     
                                                                                                                                                                        
                     









                                                                                                                                                                        
                                       


                                                                                                                                                                               
                                                            


                                                                     


                                                                   


                                                                                                                                                                      
                     








































                                                                                                                                                                        
                              








                                                                                                                                                                        
                     









                                                                                                                                                                        
                                       


                                                                                                                                                                               
                                                            
                                         


                                                                               
                               
                                                                                                                                                                      





                                                                                                                                                                               
                               
                                                                                                                                                                      
                     









                                                                                                                                                                        
                               









                                                                                                                                                                       
                     









                                                                                                                                                                        
                                       



                                                                                                                                                                               
                                                





                                                                 

                                      







                                                                                                                                                                     
                                                                                                                                                                       
                     









                                                                                                                                                                        




                                                                                                                                                                               
                             

                                                                                                                                                                       
                         
                 





                                                                                                                                                                       



























































































































































                                                                                                                                                                  
                                                                           






































                                                                                                                                                                            
























































                                                                                                                                                                             





























































                                                                                                                                                                             




                                                                                                                                                                       
























































                                                                                                                                                                             





























































                                                                                                                                                                             




                                                                                                                                                                       





























































                                                                                                                                                                             


























































































































                                                                                                                                                                             
 



                                                                                                       
                                                                                                      


                                                                                                                                                                       
































































































































                                                                                                                                                                         

                                    
                                                                                                                                                                     



                                                                                                                                                                  
                                       
                   
                                                                                                                                                                      
                                                                                                                                                                     



                                                                                                                                                                  
                                    

                          
                                                                                                                                                                     




                                                                                                                                                                  

























                                                                                                                                                                       





                                                                     
                                             


                                      
                                                                                                                                                                  


































                                                                                                                                                                     
                                                































                                                                                                                                                                       





                                                                     
                                             


                                      
                                                                                                                                                                  


































                                                                                                                                                                     
                                                  





                                                                                                                                                                  



















































































                                                                                                                                                                       














































































                                                                                                                                                                       









































































                                                                                                                                                                       




                                                                                                                                                                       

                          










































































                                                                                                                                                                              
                                          

                                                    
                    















































                                                                                                                                                                       
                                          

                                                                   
                    







































                                                                                                                                                                        
                                                        

                                                                          
                    


























                                                                                                                                                                             
                            
                                                                                                                                                                            
                                                                                      









                                                                                                                                                                               



























                                                                                                                                                                             
                                                            

                                                             
                    




























                                                                                                                                                                             
                                                            

                                                                            
                    












































                                                                                                                                                                             
                                                                                            


































                                                                                                                                                                               
                                                             
                             















































































































































                                                                                                                                                                                

                               































                                                                                                                                                                             

                                  































                                                                                                                                                                             

                                  













                                                                                                                                                                             
                                      
















                                                                                                                                                                       

                               













                                                                                                                                                                             
                                   
















                                                                                                                                                                       

                                 













                                                                                                                                                                             
                                   

                                                   
                    













                                                                                                                                                                       

                                













                                                                                                                                                                             
                                    

                                                   
                     













                                                                                                                                                                       

                                 













                                                                                                                                                                             
                                    

                                                              
                     









                                                                                                                                                                       



                                                            


                                                                                                                                                                       
























































































                                                                                                                                                                                






                                                                                                                                                                             
                                        


                   
                                         

                                                                                                                                                                  



























                                                                                                                                                                             
                   














































































































                                                                                                                                                                             









                                                                                                                                                                       
















                                
# Experimental syntax sugar for SubX programs.
#
# We're experimenting with the following expressions:
#
# 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. But not immediately after the '*' or '%'.

== 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
    # emit-indirect-mode(out, base, index, scale, disp)
    # . . push args
    53/push-EBX
    52/push-EDX
    51/push-ECX
    50/push-EAX
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    # . . call
    e8/call  emit-indirect-mode/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
    # restore registers
    5b/pop-to-EBX
    5a/pop-to-EDX
    59/pop-to-ECX
    58/pop-to-EAX
    # 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

test-convert-passes-most-words-through:
    # . 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
    # . clear-stream(_test-input-buffered-file+4)
    # . . push args
    b8/copy-to-EAX  _test-input-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
    # . 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
    # initialize input
    # . write(_test-input-stream, "== abcd 0x1")
    # . . push args
    68/push  "== abcd 0x1"/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
    # convert(_test-input-buffered-file, _test-output-buffered-file)
    # . . push args
    68/push  _test-output-buffered-file/imm32
    68/push  _test-input-buffered-file/imm32
    # . . call
    e8/call  convert/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # check that the line just passed through
    # . 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 _test-output-stream {{{
#?     # . 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, "== abcd 0x1 \n", msg)
    # . . push args
    68/push  "F - test-convert-passes-most-words-through"/imm32
    68/push  "== abcd 0x1 \n"/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-convert-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-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
    # . clear-stream(_test-input-buffered-file+4)
    # . . push args
    b8/copy-to-EAX  _test-input-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
    # . 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
    # initialize input
    # . write(_test-input-stream, "ab %ecx")
    # . . push args
    68/push  "ab %ecx"/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
    # convert(_test-input-buffered-file, _test-output-buffered-file)
    # . . push args
    68/push  _test-output-buffered-file/imm32
    68/push  _test-input-buffered-file/imm32
    # . . call
    e8/call  convert/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # check that the line just passed through
    # . 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 _test-output-stream {{{
#?     # . 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, "ab 3/mod/direct 0x00000001/rm32 \n", msg)
    # . . push args
    68/push  "F - test-convert-direct-mode"/imm32
    68/push  "ab 3/mod/direct 0x00000001/rm32 \n"/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-convert-register-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-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
    # . clear-stream(_test-input-buffered-file+4)
    # . . push args
    b8/copy-to-EAX  _test-input-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
    # . 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
    # initialize input
    # . write(_test-input-stream, "ab *ecx")
    # . . push args
    68/push  "ab *ecx"/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
    # convert(_test-input-buffered-file, _test-output-buffered-file)
    # . . push args
    68/push  _test-output-buffered-file/imm32
    68/push  _test-input-buffered-file/imm32
    # . . call
    e8/call  convert/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # check that the line just passed through
    # . 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 _test-output-stream {{{
#?     # . 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, "ab 0/mod/indirect 0x00000001/rm32 \n", msg)
    # . . push args
    68/push  "F - test-convert-indirect-mode"/imm32
    68/push  "ab 0/mod/indirect 0x00000001/rm32 \n"/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-convert-register-indirect-mode-without-displacement:
    # . 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
    # . clear-stream(_test-input-buffered-file+4)
    # . . push args
    b8/copy-to-EAX  _test-input-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
    # . 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
    # initialize input
    # . write(_test-input-stream, "ab *(ecx)")
    # . . push args
    68/push  "ab *(ecx)"/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
    # convert(_test-input-buffered-file, _test-output-buffered-file)
    # . . push args
    68/push  _test-output-buffered-file/imm32
    68/push  _test-input-buffered-file/imm32
    # . . call
    e8/call  convert/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # check that the line just passed through
    # . 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 _test-output-stream {{{
#?     # . 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, "ab 0/mod/indirect 1/rm32 \n", msg)
    # . . push args
    68/push  "F - test-convert-indirect-mode-without-displacement"/imm32
    68/push  "ab 0/mod/indirect 0x00000001/rm32 \n"/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-convert-register-indirect-mode-with-displacement:
    # . 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
    # . clear-stream(_test-input-buffered-file+4)
    # . . push args
    b8/copy-to-EAX  _test-input-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
    # . 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
    # initialize input
    # . write(_test-input-stream, "ab *(ecx+4)")
    # . . push args
    68/push  "ab *(ecx+4)"/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
    # convert(_test-input-buffered-file, _test-output-buffered-file)
    # . . push args
    68/push  _test-output-buffered-file/imm32
    68/push  _test-input-buffered-file/imm32
    # . . call
    e8/call  convert/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # check that the line just passed through
    # . 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 _test-output-stream {{{
#?     # . 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, "ab 2/mod/*+disp32 1/rm32 4/disp32 \n", msg)
    # . . push args
    68/push  "F - test-convert-indirect-mode-with-displacement"/imm32
    68/push  "ab 2/mod/*+disp32 0x00000001/rm32 0x00000004/disp32 \n"/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

# boss level
test-convert-register-indirect-mode-with-sib-byte:
    # . 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
    # . clear-stream(_test-input-buffered-file+4)
    # . . push args
    b8/copy-to-EAX  _test-input-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
    # . 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
    # initialize input
    # . write(_test-input-stream, "ab *(ecx + edx<<3 + 4)")
    # . . push args
    68/push  "ab *(ecx + edx<<3 + 4)"/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
    # convert(_test-input-buffered-file, _test-output-buffered-file)
    # . . push args
    68/push  _test-output-buffered-file/imm32
    68/push  _test-input-buffered-file/imm32
    # . . call
    e8/call  convert/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # check that the line just passed through
    # . 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 _test-output-stream {{{
#?     # . 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, "ab 2/mod/*+disp32 4/rm32/sib 1/base 2/index 3/scale 4/disp32 \n", msg)
    # . . push args
    68/push  "F - test-convert-indirect-mode-with-sib-byte"/imm32
    68/push  "ab 2/mod/*+disp32 4/rm32/sib 0x00000001/base 0x00000002/index 0x00000003/scale 0x00000004/disp32 \n"/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-convert-register-indirect-mode-with-sib-byte-negative-displacement:
    # . 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
    # . clear-stream(_test-input-buffered-file+4)
    # . . push args
    b8/copy-to-EAX  _test-input-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
    # . 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
    # initialize input
    # . write(_test-input-stream, "ab *(ecx + edx<<3 - 4)")
    # . . push args
    68/push  "ab *(ecx + edx<<3 - 4)"/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
    # convert(_test-input-buffered-file, _test-output-buffered-file)
    # . . push args
    68/push  _test-output-buffered-file/imm32
    68/push  _test-input-buffered-file/imm32
    # . . call
    e8/call  convert/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # check that the line just passed through
    # . 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 _test-output-stream {{{
#?     # . 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, "ab 2/mod/*+disp32 4/rm32/sib 1/base 2/index 3/scale -4/disp32 \n", msg)
    # . . push args
    68/push  "F - test-convert-indirect-mode-with-sib-byte-negative-displacement"/imm32
    68/push  "ab 2/mod/*+disp32 4/rm32/sib 0x00000001/base 0x00000002/index 0x00000003/scale 0xfffffffc/disp32 \n"/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

# 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  0x20/imm32/space
    74/jump-if-equal  $next-word-or-expression:error1/disp8
    # if (line->data[line->read] != '(') goto regular word
    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:regular-word/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 displacement
    #   if (*word->start != '+') goto error1
    #   ++word->start to skip '+'
    #   skip whitespace
    #   if next 3 characters don't make a register, goto displacement
    #   read register into index
    #   skip whitespace
    #   if (*word->start == ')') goto end
    #   if (*word->start == '<') {
    #     ++word->start to skip '<'
    #     if (*word->start != '<') goto error2
    #     ++word->start to skip '<'
    #     skip whitespace
    #     read integer into scale
    #     skip whitespace
    #     if (*word->start == ')') goto end
    #   }
    #   if (*word->start not in '+' '-') goto error3
    # displacement:
    #   read integer into disp
    #   skip whitespace
    #   if (*word->start != ')') goto error4
    # . 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
$parse-effective-address:check-for-simple-register:
    # if (*word->start == '(') goto compound expression
    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
    e9/jump  $parse-effective-address:end/disp32
$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 EDI
    # 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
    # if (*word->start == ')') goto end
    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  0x29/imm32/close-paren
    0f 84/jump-if-equal  $parse-effective-address:end/disp32
    # if (*word->start == '-') goto displacement
    3d/compare-EAX-and  0x2d/imm32/minus
    0f 84/jump-if-equal  $parse-effective-address:displacement/disp32
    # if (*word->start != '+') goto error1
    3d/compare-EAX-and  0x2b/imm32/plus
    0f 85/jump-if-not-equal  $parse-effective-address:error1/disp32
$parse-effective-address:check-for-index:
    # ++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
$parse-effective-address:resolve-ambiguity:
    # if next 3 characters don't make a register, goto displacement
    # . spill ECX
    51/push-ECX
    # . var tmp/ECX = {word->start, word->start+3}
    # . . ECX = word->start
    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy EAX to ECX
    # . . EAX = word->start+3
    05/add-to-EAX  3/imm32
    # . . push
    50/push-EAX
    51/push-ECX
    # . . copy ESP to ECX
    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
    # . EAX = maybe-get-slice(Register, tmp, row-size=8)
    # . . push args
    68/push  8/imm32/row-size
    51/push-ECX
    68/push  Registers/imm32
    # . . call
    e8/call  maybe-get-slice/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . reclaim tmp
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . restore ECX
    59/pop-to-ECX
    # . if (EAX == 0) goto displacement
    3d/compare-EAX-and  0/imm32
    0f 84/jump-if-equal  $parse-effective-address:displacement/disp32
$parse-effective-address:index:
    # read register into index
    # . 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
    # . ECX = *EAX
    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
    # 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
    # if (*word->start == ')') goto end
    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  0x29/imm32/close-paren
    0f 84/jump-if-equal  $parse-effective-address:end/disp32
$parse-effective-address:check-for-scale:
    # if (*word->start != '<') goto next check
    3d/compare-EAX-and  0x3c/imm32/less-than
    75/jump-if-not-equal  $parse-effective-address:check-for-displacement/disp8
    # ++word->start to skip '<'
    ff          0/subop/increment   0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # increment *ESI
    # if (*word->start != '<') goto error2
    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  0x3c/imm32/less-than
    0f 85/jump-if-not-equal  $parse-effective-address:error2/disp32
    # ++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
$parse-effective-address:scale:
    # read positive integer into scale
    # . EAX = next-positive-hex-int(word)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . call
    e8/call  next-positive-hex-int/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # . EDX = EAX
    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EDX
    # 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
    # if (*word->start == ')') goto end
    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  0x29/imm32/close-paren
    74/jump-if-equal  $parse-effective-address:end/disp8
$parse-effective-address:check-for-displacement:
    # if (*word->start not in '+' '-') goto error3
    3d/compare-EAX-and  0x2b/imm32/plus
    74/jump-if-equal  $parse-effective-address:displacement/disp8
    3d/compare-EAX-and  0x2d/imm32/minus
    74/jump-if-equal  $parse-effective-address:displacement/disp8
    e9/jump  $parse-effective-address:error3/disp32
$parse-effective-address:displacement:
    # read integer into disp
    # . EAX = next-hex-int(word)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . call
    e8/call  next-hex-int/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # . EBX = EAX
    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EBX
    # 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
    # if (*word->start != ')') goto error4
    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  0x29/imm32/close-paren
    0f 85/jump-if-not-equal  $parse-effective-address:error4/disp32
$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

$parse-effective-address:error1:
    # print(stderr, "error: unexpected character: " EAX "\n")
    # . write-buffered(Stderr, "error: unexpected character: ")
    # . . push args
    68/push  "error: unexpected character: "/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
    # . print-int32-buffered(out, EAX)
    # . . push args
    50/push-EAX
    68/push  Stderr/imm32
    # . . 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(Stderr, "\n")
    # . . push args
    68/push  "\n"/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

$parse-effective-address:error2:
    # print(stderr, "error: '<' can only be followed by '<' but got: " EAX "\n")
    # . write-buffered(Stderr, "error: '<' can only be followed by '<' but got: ")
    # . . push args
    68/push  "error: '<' can only be followed by '<' but got: "/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
    # . print-int32-buffered(out, EAX)
    # . . push args
    50/push-EAX
    68/push  Stderr/imm32
    # . . 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(Stderr, "\n")
    # . . push args
    68/push  "\n"/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

$parse-effective-address:error3:
    # print(stderr, "error: unexpected character before displacement: " EAX "\n")
    # . write-buffered(Stderr, "error: unexpected character before displacement: ")
    # . . push args
    68/push  "error: unexpected character before displacement: "/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
    # . print-int32-buffered(out, EAX)
    # . . push args
    50/push-EAX
    68/push  Stderr/imm32
    # . . 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(Stderr, "\n")
    # . . push args
    68/push  "\n"/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

$parse-effective-address:error4:
    # print(stderr, "error: unexpected character after displacement: " EAX "; expected ')' to wrap up\n")
    # . write-buffered(Stderr, "error: unexpected character after displacement: ")
    # . . push args
    68/push  "error: unexpected character after displacement: "/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
    # . print-int32-buffered(out, EAX)
    # . . push args
    50/push-EAX
    68/push  Stderr/imm32
    # . . 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(Stderr, "; expected ')' to wrap up\n")
    # . . push args
    68/push  "; expected ')' to wrap up\n"/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

# assumes 'in' starts with a register name, and returns pointer to 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

test-parse-effective-address-base-displacement:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # var slice/ECX = "*(esi+3)"
    b8/copy-to-EAX  "*(esi+3)"/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-displacement/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-displacement/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-displacement/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, 3, msg)
    # . . push args
    68/push  "F - test-parse-effective-address-base-displacement/displacement"/imm32
    68/push  3/imm32
    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-negative-displacement:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # var slice/ECX = "*(esi-3)"
    b8/copy-to-EAX  "*(esi-3)"/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-negative-displacement/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-negative-displacement/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-negative-displacement/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, -3, msg)
    # . . push args
    68/push  "F - test-parse-effective-address-base-negative-displacement/displacement"/imm32
    68/push  -3/imm32
    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-index:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # var slice/ECX = "*(esi+ecx)"
    b8/copy-to-EAX  "*(esi+ecx)"/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-index/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, 1, msg)
    # . . push args
    68/push  "F - test-parse-effective-address-base-index/index"/imm32
    68/push  1/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-index/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-index/displacement"/imm32
    68/push  0/imm32
    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-index-scale:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # var slice/ECX = "*(esi+ecx<<2)"
    b8/copy-to-EAX  "*(esi+ecx<<2)"/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-index-scale/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, 1, msg)
    # . . push args
    68/push  "F - test-parse-effective-address-base-index-scale/index"/imm32
    68/push  1/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, 2, msg)
    # . . push args
    68/push  "F - test-parse-effective-address-base-index-scale/scale"/imm32
    68/push  2/imm32
    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-index-scale/displacement"/imm32
    68/push  0/imm32
    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-index-scale-displacement:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # var slice/ECX = "*(esi + ecx<<2 - 0x34)"
    b8/copy-to-EAX  "*(esi + ecx<<2 - 0x34)"/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-index-scale/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, 1, msg)
    # . . push args
    68/push  "F - test-parse-effective-address-base-index-scale/index"/imm32
    68/push  1/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, 2, msg)
    # . . push args
    68/push  "F - test-parse-effective-address-base-index-scale/scale"/imm32
    68/push  2/imm32
    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, -0x34, msg)
    # . . push args
    68/push  "F - test-parse-effective-address-base-index-scale/displacement"/imm32
    68/push  -0x34/imm32
    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:  # out : (address buffered-file), base : int, index : int, scale : int, disp : int
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
$emit-indirect-mode:check-for-sib:
    # if (index == 4/none) goto next check
    81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      4/imm32           # compare *(EBP+16)
    0f 84/jump-if-equal  $emit-indirect-mode:check-for-disp/disp32
$emit-indirect-mode:emit-sib:
    # emit(out, "2/mod/indirect 4/rm32/sib " base "/base " index "/index " scale "/scale " disp "/disp32")
    # . write-buffered(out, "2/mod/*+disp32 4/rm32/sib ")
    # . . push args
    68/push  "2/mod/*+disp32 4/rm32/sib "/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . 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, base)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . 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, "/base ")
    # . . push args
    68/push  "/base "/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . 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, index)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . 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, "/index ")
    # . . push args
    68/push  "/index "/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . 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, scale)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . 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, "/scale ")
    # . . push args
    68/push  "/scale "/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . 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, disp)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x18/disp8      .                 # push *(EBP+24)
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . 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, "/disp32")
    # . . push args
    68/push  "/disp32"/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . call
    e8/call  write-buffered/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    e9/jump  $emit-indirect-mode:end/disp32
$emit-indirect-mode:check-for-disp:
    # if (disp == 0) goto next check
    81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x18/disp8      0/imm32           # compare *(EBP+24)
    74/jump-if-equal  $emit-indirect-mode:emit-indirect/disp8
$emit-indirect-mode:emit-disp:
    # emit(out, "2/mod/*+disp32 " base "/rm32 " disp "/disp32")
    # . write-buffered(out, "2/mod/*+disp32 ")
    # . . push args
    68/push  "2/mod/*+disp32 "/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . 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, base)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . 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    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . 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, disp)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x18/disp8      .                 # push *(EBP+24)
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . 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, "/disp32")
    # . . push args
    68/push  "/disp32"/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . call
    e8/call  write-buffered/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    eb/jump  $emit-indirect-mode:end/disp8
$emit-indirect-mode:emit-indirect:
    # emit(out, "0/mod/indirect " base "/rm32")
    # . write-buffered(out, "0/mod/indirect ")
    # . . push args
    68/push  "0/mod/indirect "/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . 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, base)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . 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    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . 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:
    # . 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
    # emit-indirect-mode(_test-output-buffered-file, 0, 4/none, 0, 0)
    # . . write args
    68/push  0/imm32/.disp
    68/push  0/imm32/.scale
    68/push  4/imm32/.index/none
    68/push  0/imm32/.base
    68/push  _test-output-buffered-file/imm32
    # . . call
    e8/call  emit-indirect-mode/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/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"/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
    # emit-indirect-mode(_test-output-buffered-file, 6, 4/none, 0, 0)
    # . . write args
    68/push  0/imm32/.disp
    68/push  0/imm32/.scale
    68/push  4/imm32/.index/none
    68/push  7/imm32/.base
    68/push  _test-output-buffered-file/imm32
    # . . call
    e8/call  emit-indirect-mode/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/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-2"/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

test-emit-indirect-mode-with-disp:
    # . 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
    # emit-indirect-mode(_test-output-buffered-file, 6, 4/none, 0, 4)
    # . . write args
    68/push  4/imm32/.disp
    68/push  0/imm32/.scale
    68/push  4/imm32/.index/none
    68/push  6/imm32/.base
    68/push  _test-output-buffered-file/imm32
    # . . call
    e8/call  emit-indirect-mode/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/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, "2/mod/*+disp32 6/rm32 4/disp32", msg)
    # . . push args
    68/push  "F - test-emit-indirect-mode-with-disp"/imm32
    68/push  "2/mod/*+disp32 0x00000006/rm32 0x00000004/disp32"/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-with-disp-negative:
    # . 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
    # emit-indirect-mode(_test-output-buffered-file, 6, 4/none, 0, -4)
    # . . write args
    68/push  -4/imm32/.disp
    68/push  0/imm32/.scale
    68/push  4/imm32/.index/none
    68/push  6/imm32/.base
    68/push  _test-output-buffered-file/imm32
    # . . call
    e8/call  emit-indirect-mode/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/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, "2/mod/*+disp32 6/rm32 -4/disp32", msg)
    # . . push args
    68/push  "F - test-emit-indirect-mode-with-disp"/imm32
    68/push  "2/mod/*+disp32 0x00000006/rm32 0xfffffffc/disp32"/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-with-sib:
    # . 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
    # emit-indirect-mode(_test-output-buffered-file, 6/base, 1/index, 2/scale, 4/disp)
    # . . write args
    68/push  4/imm32/.disp
    68/push  2/imm32/.scale
    68/push  1/imm32/.index
    68/push  6/imm32/.base
    68/push  _test-output-buffered-file/imm32
    # . . call
    e8/call  emit-indirect-mode/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/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, "2/mod/indirect 4/rm32/sib 6/base 1/index 2/scale 4/disp", msg)
    # . . push args
    68/push  "F - test-emit-indirect-mode-with-sib"/imm32
    68/push  "2/mod/*+disp32 4/rm32/sib 0x00000006/base 0x00000001/index 0x00000002/scale 0x00000004/disp32"/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

# assumes 'in' starts with optional '+' or '-', optional whitespace, and an unsigned integer
# returns the value of the integer
# side-effect: modifies 'in' to skip past the integer
next-hex-int:  # in : (address slice) -> result/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
    53/push-EBX
    56/push-ESI
    57/push-EDI
    # result/EDI = 0
    31/xor                          3/mod/direct    7/rm32/EDI    .           .             .           7/r32/EDI   .               .                 # clear EDI
    # ESI = in
    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
    # EDX = in->end
    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           2/r32/EDX   4/disp8         .                 # copy *(ESI+4) to EDX
    # curr/ECX = in->start
    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # copy *ESI to ECX
    # negate?/EBX = false
    31/xor                          3/mod/direct    3/rm32/EBX    .           .             .           3/r32/EBX   .               .                 # clear EBX
    # EAX = *curr
    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
    8a/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/AL    .               .                 # copy byte at *ECX to AL
$next-hex-int:positive:
    # if (*curr == '+') ++curr
    3d/compare-EAX-and  0x2b/imm32/+
    75/jump-if-not-equal  $next-hex-int:negative/disp8
    # . ++curr
    41/increment-ECX
    eb/jump  $next-hex-int:skip-whitespace/disp8
$next-hex-int:negative:
    # else if (*curr == '-') ++curr, negate = true
    3d/compare-EAX-and  0x2d/imm32/-
    75/jump-if-not-equal  $next-hex-int:skip-whitespace/disp8
$next-hex-int:need-to-negate:
    # . ++curr
    41/increment-ECX
    # . negate = true
    bb/copy-to-EBX  1/imm32/true
    # fall through
$next-hex-int:skip-whitespace:
    # spill EAX
    50/push-EAX
    # EAX = skip-chars-matching-whitespace-in-slice(word->start, word->end)
    # . . push args
    52/push-EDX
    51/push-ECX
    # . . 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
    # ECX = EAX
    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy EAX to ECX
    # restore EAX
    58/pop-to-EAX
$next-hex-int:initial-0:
    # skip past leading '0x'
    # . if (*curr != '0') jump to loop
    8a/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/AL    .               .                 # copy byte at *ECX to AL
    3d/compare-EAX-and  0x30/imm32/0
    75/jump-if-not-equal  $next-hex-int:loop/disp8
    # . ++curr
    41/increment-ECX
$next-hex-int:initial-0x:
    # . if (curr >= in->end) return result
    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
    73/jump-if-greater-or-equal-unsigned  $next-hex-int:end/disp8
    # . if (*curr != 'x') jump to loop  # the previous '0' is still valid so doesn't need to be checked again
    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
    8a/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/AL    .               .                 # copy byte at *ECX to AL
    3d/compare-EAX-and  0x78/imm32/x
    75/jump-if-not-equal  $next-hex-int:loop/disp8
    # . ++curr
    41/increment-ECX
$next-hex-int:loop:
    # if (curr >= in->end) break
    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
    73/jump-if-greater-or-equal-unsigned  $next-hex-int:break/disp8
    # if (!is-hex-digit?(*curr)) break
    # . EAX = *curr
    8a/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/AL    .               .                 # copy byte at *ECX to AL
    # . EAX = is-hex-digit?(*curr)
    # . . push args
    50/push-EAX
    # . . call
    e8/call  is-hex-digit?/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
    74/jump-if-equal  $next-hex-int:break/disp8
    # EAX = from-hex-char(*curr)
    # . . copy arg to EAX
    8a/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/AL    .               .                 # copy byte at *ECX to AL
    # . . call
    e8/call  from-hex-char/disp32
    # result = result * 16 + EAX
    c1/shift    4/subop/left        3/mod/direct    7/rm32/EDI    .           .             .           .           .               4/imm8            # shift EDI left by 4 bits
    01/add                          3/mod/direct    7/rm32/EDI    .           .             .           0/r32/EAX   .               .                 # add EAX to EDI
    # ++curr
    41/increment-ECX
    # loop
    eb/jump  $next-hex-int:loop/disp8
$next-hex-int:break:
    81          7/subop/compare     3/mod/direct    3/rm32/EBX    .           .             .           .           .               0/imm32           # compare EBX
    74/jump-if-equal  $next-hex-int:end/disp8
$next-hex-int:negate:
    f7          3/subop/negate      3/mod/direct    7/rm32/EDI    .           .             .           .           .               .                 # negate EDI
$next-hex-int:end:
    # word->start = curr
    89/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # copy ECX to *ESI
    # return EDI
    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
    5b/pop-to-EBX
    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

$next-hex-int:abort:
    # . _write(2/stderr, error)
    # . . push args
    68/push  "next-hex-int: invalid hex char: "/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
    # . print-int32-buffered(Stderr, EAX)
    # . . push args
    50/push-EAX
    68/push  Stderr/imm32
    # . . call
    e8/call  print-int32-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
    # . 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-hex-int-single-digit:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # (EAX..ECX) = "+a)"
    b8/copy-to-EAX  "+a)"/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
    # var slice/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 = next-hex-int(slice)
    # . . push args
    51/push-ECX
    # . . call
    e8/call  next-hex-int/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # check-ints-equal(EAX, 0xa, msg)
    # . . push args
    68/push  "F - test-next-hex-int-single-digit"/imm32
    68/push  0xa/imm32
    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-hex-int-multi-digit:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # (EAX..ECX) = "+ 34a)"
    b8/copy-to-EAX  "+ 34a)"/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
    # var slice/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 = next-hex-int(slice)
    # . . push args
    51/push-ECX
    # . . call
    e8/call  next-hex-int/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # check-ints-equal(EAX, 0x34a, msg)
    # . . push args
    68/push  "F - test-next-hex-int-multi-digit"/imm32
    68/push  0x34a/imm32
    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-hex-int-0x-prefix:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # (EAX..ECX) = "+0x34)"
    b8/copy-to-EAX  "+0x34)"/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
    # var slice/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 = next-hex-int(slice)
    # . . push args
    51/push-ECX
    # . . call
    e8/call  next-hex-int/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # check-ints-equal(EAX, 0x34, msg)
    # . . push args
    68/push  "F - test-next-hex-int-0x-prefix"/imm32
    68/push  0x34/imm32
    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-hex-int-zero:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # (EAX..ECX) = "+0)"
    b8/copy-to-EAX  "+0)"/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
    # var slice/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 = next-hex-int(slice)
    # . . push args
    51/push-ECX
    # . . call
    e8/call  next-hex-int/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # check-ints-equal(EAX, 0, msg)
    # . . push args
    68/push  "F - test-next-hex-int-zero"/imm32
    68/push  0/imm32
    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-hex-int-0-prefix:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # (EAX..ECX) = "+ 03)"
    b8/copy-to-EAX  "+ 03)"/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
    # var slice/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 = next-hex-int(slice)
    # . . push args
    51/push-ECX
    # . . call
    e8/call  next-hex-int/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # check-ints-equal(EAX, 3, msg)
    # . . push args
    68/push  "F - test-next-hex-int-0-prefix"/imm32
    68/push  3/imm32
    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-hex-int-negative:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # (EAX..ECX) = "-03)"
    b8/copy-to-EAX  "-03)"/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
    # var slice/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 = next-hex-int(slice)
    # . . push args
    51/push-ECX
    # . . call
    e8/call  next-hex-int/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # check-ints-equal(EAX, -3, msg)
    # . . push args
    68/push  "F - test-next-hex-int-negative"/imm32
    68/push  -3/imm32
    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-hex-int-negative-with-space:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # (EAX..ECX) = "- 03)"
    b8/copy-to-EAX  "- 03)"/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
    # var slice/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 = next-hex-int(slice)
    # . . push args
    51/push-ECX
    # . . call
    e8/call  next-hex-int/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # check-ints-equal(EAX, -3, msg)
    # . . push args
    68/push  "F - test-next-hex-int-negative-with-space"/imm32
    68/push  -3/imm32
    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

# assumes 'in' starts a positive unsigned integer
# returns the value of the integer
# side-effect: modifies 'in' to skip past the integer
next-positive-hex-int:  # in : (address slice) -> result/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
    53/push-EBX
    56/push-ESI
    57/push-EDI
    # result/EDI = 0
    31/xor                          3/mod/direct    7/rm32/EDI    .           .             .           7/r32/EDI   .               .                 # clear EDI
    # ESI = in
    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
    # EDX = in->end
    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           2/r32/EDX   4/disp8         .                 # copy *(ESI+4) to EDX
    # curr/ECX = in->start
    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # copy *ESI to ECX
    # negate?/EBX = false
    31/xor                          3/mod/direct    3/rm32/EBX    .           .             .           3/r32/EBX   .               .                 # clear EBX
    # EAX = *curr
    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
    8a/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/AL    .               .                 # copy byte at *ECX to AL
$next-positive-hex-int:initial-0:
    # skip past leading '0x'
    # . if (*curr != '0') jump to loop
    8a/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/AL    .               .                 # copy byte at *ECX to AL
    3d/compare-EAX-and  0x30/imm32/0
    75/jump-if-not-equal  $next-positive-hex-int:loop/disp8
    # . ++curr
    41/increment-ECX
$next-positive-hex-int:initial-0x:
    # . if (curr >= in->end) return result
    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
    73/jump-if-greater-or-equal-unsigned  $next-positive-hex-int:end/disp8
    # . if (*curr != 'x') jump to loop  # the previous '0' is still valid so doesn't need to be checked again
    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
    8a/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/AL    .               .                 # copy byte at *ECX to AL
    3d/compare-EAX-and  0x78/imm32/x
    75/jump-if-not-equal  $next-positive-hex-int:loop/disp8
    # . ++curr
    41/increment-ECX
$next-positive-hex-int:loop:
    # if (curr >= in->end) break
    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
    73/jump-if-greater-or-equal-unsigned  $next-positive-hex-int:end/disp8
    # if (!is-hex-digit?(*curr)) break
    # . EAX = *curr
    8a/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/AL    .               .                 # copy byte at *ECX to AL
    # . EAX = is-hex-digit?(*curr)
    # . . push args
    50/push-EAX
    # . . call
    e8/call  is-hex-digit?/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
    74/jump-if-equal  $next-positive-hex-int:end/disp8
    # EAX = from-hex-char(*curr)
    # . . copy arg to EAX
    8a/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/AL    .               .                 # copy byte at *ECX to AL
    # . . call
    e8/call  from-hex-char/disp32
    # result = result * 16 + EAX
    c1/shift    4/subop/left        3/mod/direct    7/rm32/EDI    .           .             .           .           .               4/imm8            # shift EDI left by 4 bits
    01/add                          3/mod/direct    7/rm32/EDI    .           .             .           0/r32/EAX   .               .                 # add EAX to EDI
    # ++curr
    41/increment-ECX
    # loop
    eb/jump  $next-positive-hex-int:loop/disp8
$next-positive-hex-int:end:
    # word->start = curr
    89/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # copy ECX to *ESI
    # return EDI
    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
    5b/pop-to-EBX
    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-next-positive-hex-int-single-digit:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # (EAX..ECX) = "a)"
    b8/copy-to-EAX  "a)"/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
    # var slice/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 = next-positive-hex-int(slice)
    # . . push args
    51/push-ECX
    # . . call
    e8/call  next-positive-hex-int/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # check-ints-equal(EAX, 0xa, msg)
    # . . push args
    68/push  "F - test-next-positive-hex-int-single-digit"/imm32
    68/push  0xa/imm32
    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-positive-hex-int-multi-digit:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # (EAX..ECX) = "34a)"
    b8/copy-to-EAX  "34a)"/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
    # var slice/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 = next-positive-hex-int(slice)
    # . . push args
    51/push-ECX
    # . . call
    e8/call  next-positive-hex-int/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # check-ints-equal(EAX, 0x34a, msg)
    # . . push args
    68/push  "F - test-next-positive-hex-int-multi-digit"/imm32
    68/push  0x34a/imm32
    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-positive-hex-int-0x-prefix:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # (EAX..ECX) = "0x34)"
    b8/copy-to-EAX  "0x34)"/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
    # var slice/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 = next-positive-hex-int(slice)
    # . . push args
    51/push-ECX
    # . . call
    e8/call  next-positive-hex-int/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # check-ints-equal(EAX, 0x34, msg)
    # . . push args
    68/push  "F - test-next-positive-hex-int-0x-prefix"/imm32
    68/push  0x34/imm32
    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-positive-hex-int-zero:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # (EAX..ECX) = "0"
    b8/copy-to-EAX  "0"/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
    # var slice/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 = next-positive-hex-int(slice)
    # . . push args
    51/push-ECX
    # . . call
    e8/call  next-positive-hex-int/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # check-ints-equal(EAX, 0, msg)
    # . . push args
    68/push  "F - test-next-positive-hex-int-zero"/imm32
    68/push  0/imm32
    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-positive-hex-int-0-prefix:
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # (EAX..ECX) = "03)"
    b8/copy-to-EAX  "03)"/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
    # var slice/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 = next-positive-hex-int(slice)
    # . . push args
    51/push-ECX
    # . . call
    e8/call  next-positive-hex-int/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # check-ints-equal(EAX, 3, msg)
    # . . push args
    68/push  "F - test-next-positive-hex-int-0-prefix"/imm32
    68/push  3/imm32
    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

== 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