https://github.com/akkartik/mu/blob/master/subx/apps/crenshaw2-1b.subx

      1 # Port of https://github.com/akkartik/crenshaw/blob/master/tutor2.1.pas
      2 # which corresponds to the section "single digits" in https://compilers.iecc.com/crenshaw/tutor2.txt
      3 # except that we support hex numbers of multiple digits.
      4 #
      5 # To run (from the subx/ directory):
      6 #   $ ./subx translate *.subx apps/crenshaw2-1b.subx -o apps/crenshaw2-1b
      7 #   $ echo '1a'  |./subx run apps/crenshaw2-1b
      8 # Expected output:
      9 #   # syscall(exit, 1a)
     10 #   bb/copy-to-EBX  3/imm32
     11 #   b8/copy-to-EAX  1/imm32/exit
     12 #   cd/syscall  0x80/imm8
     13 #
     14 # To run the generated output:
     15 #   $ echo '1a'  |./subx run apps/crenshaw2-1b > z1.subx
     16 #   $ ./subx translate z1.subx -o z1
     17 #   $ ./subx run z1
     18 #   $ echo $?
     19 #   26  # 0x1a in decimal
     20 #
     21 # Stdin must contain just a single hex digit. Other input will print an error:
     22 #   $ echo 'xyz'  |./subx run apps/crenshaw2-1b
     23 #   Error: integer expected
     24 #
     25 # Names in this file sometimes follow Crenshaw's original rather than my usual
     26 # naming conventions.
     27
     28 == code
     29 #   instruction                     effective address                                                   register    displacement    immediate
     30 # . op          subop               mod             rm32          base        index         scale       r32
     31 # . 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
     32
     33 Entry:  # run tests if necessary, call 'compile' if not
     34
     35 #?     # for debugging: run a single test; don't bother setting status code
     36 #?     e8/call test-get-num-reads-single-digit/disp32
     37 #?     eb/jump  $main:end/disp8
     38
     39     # . prolog
     40     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
     41     # - if argc > 1 and argv[1] == "test", then return run_tests()
     42     # . argc > 1
     43     81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0/disp8         1/imm32           # compare *EBP
     44     7e/jump-if-lesser-or-equal  $run-main/disp8
     45     # . argv[1] == "test"
     46     # . . push args
     47     68/push  "test"/imm32
     48     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
     49     # . . call
     50     e8/call  kernel-string-equal?/disp32
     51     # . . discard args
     52     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
     53     # . check result
     54     3d/compare-EAX-and  1/imm32
     55     75/jump-if-not-equal  $run-main/disp8
     56     # . run-tests()
     57     e8/call  run-tests/disp32
     58     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
     59     eb/jump  $main:end/disp8
     60 $run-main:
     61     # - otherwise read a program from stdin and emit its translation to stdout
     62     # var ed/EAX : exit-descriptor
     63     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
     64     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
     65     # configure ed to really exit()
     66     # . ed->target = 0
     67     c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # copy to *EAX
     68     # return compile(Stdin, 1/stdout, 2/stderr, ed)
     69     # . . push args
     70     50/push-EAX/ed
     71     68/push  2/imm32/stderr
     72     68/push  1/imm32/stdout
     73     68/push  Stdin/imm32
     74     # . . call
     75     e8/call  compile/disp32
     76     # . . discard args
     77     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
     78     # . syscall(exit, 0)
     79     bb/copy-to-EBX  0/imm32
     80 $main:end:
     81     b8/copy-to-EAX  1/imm32/exit
     82     cd/syscall  0x80/imm8
     83
     84 # the main entry point
     85 compile:  # in : (address buffered-file), out : fd or (address stream), err : fd or (address stream), ed : (address exit-descriptor) -> <void>
     86     # . prolog
     87     55/push-EBP
     88     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
     89     # . save registers
     90     50/push-EAX
     91     51/push-ECX
     92     # prime the pump
     93     # . Look = get-char(in)
     94     # . . push args
     95     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8      .                    # push *(EBP+8)
     96     # . . call
     97     e8/call  get-char/disp32
     98     # . . discard args
     99     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    100     # var num/ECX : (address stream) on the stack
    101     # Numbers can be 32 bits or 8 hex bytes long. One of them will be in 'Look', so we need space for 7 bytes.
    102     # Sizing the stream just right buys us overflow-handling for free inside 'get-num'.
    103     # Add 12 bytes for 'read', 'write' and 'length' fields, for a total of 19 bytes, or 0x13 in hex.
    104     # The stack pointer is no longer aligned, so dump_stack() can be misleading past this point.
    105     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x13/imm32        # subtract from ESP
    106     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
    107     # initialize the stream
    108     # . num->length = 7
    109     c7          0/subop/copy        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           8/disp8         7/imm32           # copy to *(ECX+8)
    110     # . clear-stream(num)
    111     # . . push args
    112     51/push-ECX
    113     # . . call
    114     e8/call  clear-stream/disp32
    115     # . . discard args
    116     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    117     # read a digit from 'in' into 'num'
    118     # . get-num(in, num, err, ed)
    119     # . . push args
    120     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
    121     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
    122     51/push-ECX/num
    123     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8      .                    # push *(EBP+8)
    124     # . . call
    125     e8/call  get-num/disp32
    126     # . . discard args
    127     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
    128     # render 'num' into the following template on 'out':
    129     #   bb/copy-to-EBX  _num_
    130     #   b8/copy-to-EAX  1/imm32/exit
    131     #   cd/syscall  0x80/imm8
    132     #
    133     # . write(out, "bb/copy-to-EBX  ")
    134     # . . push args
    135     68/push  "bb/copy-to-EBX  "/imm32
    136     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    137     # . . call
    138     e8/call  write/disp32
    139     # . . discard args
    140     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    141     # . write-stream(out, num)
    142     # . . push args
    143     51/push-ECX/num
    144     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    145     # . . call
    146     e8/call  write-stream/disp32
    147     # . . discard args
    148     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    149     # . write(out, Newline)
    150     # . . push args
    151     68/push  Newline/imm32
    152     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    153     # . . call
    154     e8/call  write/disp32
    155     # . . discard args
    156     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    157     # . write(out, "b8/copy-to-EAX  1/imm32/exit\n")
    158     # . . push args
    159     68/push  "b8/copy-to-EAX  1/imm32/exit\n"/imm32
    160     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    161     # . . call
    162     e8/call  write/disp32
    163     # . . discard args
    164     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    165     # . write(out, "cd/syscall  0x80/imm8\n")
    166     # . . push args
    167     68/push  "cd/syscall  0x80/imm8\n"/imm32
    168     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    169     # . . call
    170     e8/call  write/disp32
    171     # . . discard args
    172     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    173 $compile:end:
    174     # . restore registers
    175     59/pop-to-ECX
    176     58/pop-to-EAX
    177     # . epilog
    178     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    179     5d/pop-to-EBP
    180     c3/return
    181
    182 # Read a sequence of digits into 'out'. Abort if there are none, or if there is
    183 # no space in 'out'.
    184 # Input comes from the global variable 'Look' (first byte) and the argument
    185 # 'in' (rest). We leave the next byte from 'in' into 'Look' on exit.
    186 get-num:  # in : (address buffered-file), out : (address stream), err : fd or (address stream), ed : (address exit-descriptor) -> <void>
    187     # pseudocode:
    188     #   if (!is-digit?(Look)) expected(ed, err, "integer")
    189     #   do
    190     #     if out->write >= out->length
    191     #       write(err, "Error: too many digits in number\n")
    192     #       stop(ed, 1)
    193     #     out->data[out->write] = LSB(Look)
    194     #     ++out->write
    195     #     Look = get-char(in)
    196     #   while is-digit?(Look)
    197     # This is complicated because I don't want to hard-code the error strategy in
    198     # a general helper like write-byte. Maybe I should just create a local helper.
    199     #
    200     # within the loop we'll try to keep things in registers:
    201     #   in: ESI
    202     #   out: EDI
    203     #   out->write: ECX (cached copy; need to keep in sync)
    204     #   out->length: EDX
    205     #   temporaries: EAX, EBX
    206     # We can't allocate Look to a register because it gets written implicitly in
    207     # get-char in each iteration of the loop. (Thereby demonstrating that it's
    208     # not the right interface for us. But we'll keep it just to follow Crenshaw.)
    209     #
    210     # . prolog
    211     55/push-EBP
    212     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    213     # - if (is-digit?(Look)) expected(ed, err, "integer")
    214     # . EAX = is-digit?(Look)
    215     # . . push args
    216     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Look/disp32     .                 # push *Look
    217     # . . call
    218     e8/call  is-digit?/disp32
    219     # . . discard args
    220     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    221     # . if (EAX == 0)
    222     3d/compare-EAX-and  0/imm32
    223     75/jump-if-not-equal  $get-num:main/disp8
    224     # . expected(ed, err, "integer")
    225     # . . push args
    226     68/push  "integer"/imm32
    227     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
    228     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
    229     # . . call
    230     e8/call  expected/disp32  # never returns
    231     # . . discard args
    232     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    233 $get-num:main:
    234     # - otherwise read a digit
    235     # . save registers
    236     50/push-EAX
    237     51/push-ECX
    238     52/push-EDX
    239     53/push-EBX
    240     56/push-ESI
    241     57/push-EDI
    242     # read necessary variables to registers
    243     # ESI = in
    244     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
    245     # EDI = out
    246     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   0xc/disp8       .                 # copy *(EBP+12) to EDI
    247     # ECX = out->write
    248     8b/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           1/r32/ECX   .               .                 # copy *EDI to ECX
    249     # EDX = out->length
    250     8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           2/r32/EDX   8/disp8         .                 # copy *(EDI+8) to EDX
    251 $get-num:loop:
    252     # if (out->write >= out->length) error
    253     39/compare                      3/mod/direct    2/rm32/EDX    .           .             .           1/r32/ECX   .               .                 # compare EDX with ECX
    254     7d/jump-if-lesser  $get-num:loop-stage2/disp8
    255     # . error(ed, err, msg)  # TODO: show full number
    256     # . . push args
    257     68/push  "get-num: too many digits in number"/imm32
    258     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
    259     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
    260     # . . call
    261     e8/call  error/disp32  # never returns
    262     # . . discard args
    263     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    264 $get-num:loop-stage2:
    265     # out->data[out->write] = LSB(Look)
    266     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/EDI  1/index/ECX   .           3/r32/EBX   0xc/disp8       .                 # copy EDI+ECX+12 to EBX
    267     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Look/disp32     .                 # copy *Look to EAX
    268     88/copy-byte                    0/mod/indirect  3/rm32/EBX    .           .             .           0/r32/AL    .               .                 # copy byte at AL to *EBX
    269     # ++out->write
    270     41/increment-ECX
    271     # Look = get-char(in)
    272     # . . push args
    273     56/push-ESI
    274     # . . call
    275     e8/call  get-char/disp32
    276     # . . discard args
    277     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    278     # if (is-digit?(Look)) loop
    279     # . EAX = is-digit?(Look)
    280     # . . push args
    281     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Look/disp32     .                 # push *Look
    282     # . . call
    283     e8/call  is-digit?/disp32
    284     # . . discard args
    285     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    286     # . if (EAX != 0) loop
    287     3d/compare-EAX-and  0/imm32
    288     0f 85/jump-if-not-equal  $get-num:loop/disp32
    289 $get-num:loop-end:
    290     # persist necessary variables from registers
    291     89/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           1/r32/ECX   .               .                 # copy ECX to *EDI
    292 $get-num:end:
    293     # . restore registers
    294     5f/pop-to-EDI
    295     5e/pop-to-ESI
    296     5b/pop-to-EBX
    297     5a/pop-to-EDX
    298     59/pop-to-ECX
    299     58/pop-to-EAX
    300     # . epilog
    301     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    302     5d/pop-to-EBP
    303     c3/return
    304
    305 test-get-num-reads-single-digit:
    306     # - check that get-num returns first character if it's a digit
    307     # This test uses exit-descriptors. Use EBP for setting up local variables.
    308     55/push-EBP
    309     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    310     # clear all streams
    311     # . clear-stream(_test-stream)
    312     # . . push args
    313     68/push  _test-stream/imm32
    314     # . . call
    315     e8/call  clear-stream/disp32
    316     # . . discard args
    317     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    318     # . clear-stream(_test-buffered-file+4)
    319     # . . push args
    320     b8/copy-to-EAX  _test-buffered-file/imm32
    321     05/add-to-EAX  4/imm32
    322     50/push-EAX
    323     # . . call
    324     e8/call  clear-stream/disp32
    325     # . . discard args
    326     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    327     # . clear-stream(_test-output-stream)
    328     # . . push args
    329     68/push  _test-output-stream/imm32
    330     # . . call
    331     e8/call  clear-stream/disp32
    332     # . . discard args
    333     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    334     # . clear-stream(_test-error-stream)
    335     # . . push args
    336     68/push  _test-error-stream/imm32
    337     # . . call
    338     e8/call  clear-stream/disp32
    339     # . . discard args
    340     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    341     # initialize 'in'
    342     # . write(_test-stream, "3")
    343     # . . push args
    344     68/push  "3"/imm32
    345     68/push  _test-stream/imm32
    346     # . . call
    347     e8/call  write/disp32
    348     # . . discard args
    349     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    350     # initialize exit-descriptor 'ed' for the call to 'get-num' below
    351     # . var ed/EAX : exit-descriptor
    352     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
    353     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
    354     # . tailor-exit-descriptor(ed, 16)
    355     # . . push args
    356     68/push  0x10/imm32/nbytes-of-args-for-get-num
    357     50/push-EAX/ed
    358     # . . call
    359     e8/call  tailor-exit-descriptor/disp32
    360     # . . discard args
    361     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    362     # prime the pump
    363     # . get-char(_test-buffered-file)
    364     # . . push args
    365     68/push  _test-buffered-file/imm32
    366     # . . call
    367     e8/call  get-char/disp32
    368     # . . discard args
    369     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    370     # get-num(in, out, err, ed)
    371     # . . push args
    372     50/push-EAX/ed
    373     68/push  _test-error-stream/imm32
    374     68/push  _test-output-stream/imm32
    375     68/push  _test-buffered-file/imm32
    376     # . . call
    377     e8/call  get-num/disp32
    378     # registers except ESP may be clobbered at this point
    379     # . . discard args
    380     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
    381     # check-ints-equal(*_test-output-stream->data, '3', msg)
    382     # . . push args
    383     68/push  "F - test-get-num-reads-single-digit"/imm32
    384     68/push  0x33/imm32
    385     b8/copy-to-EAX  _test-output-stream/imm32
    386     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
    387     # . . call
    388     e8/call  check-ints-equal/disp32
    389     # . . discard args
    390     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    391     # . reclaim locals
    392     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    393     5d/pop-to-EBP
    394     c3/return
    395
    396 test-get-num-aborts-on-non-digit-in-Look:
    397     # - check that get-num returns first character if it's a digit
    398     # This test uses exit-descriptors. Use EBP for setting up local variables.
    399     55/push-EBP
    400     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    401     # clear all streams
    402     # . clear-stream(_test-stream)
    403     # . . push args
    404     68/push  _test-stream/imm32
    405     # . . call
    406     e8/call  clear-stream/disp32
    407     # . . discard args
    408     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    409     # . clear-stream(_test-buffered-file+4)
    410     # . . push args
    411     b8/copy-to-EAX  _test-buffered-file/imm32
    412     05/add-to-EAX  4/imm32
    413     50/push-EAX
    414     # . . call
    415     e8/call  clear-stream/disp32
    416     # . . discard args
    417     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    418     # . clear-stream(_test-output-stream)
    419     # . . push args
    420     68/push  _test-output-stream/imm32
    421     # . . call
    422     e8/call  clear-stream/disp32
    423     # . . discard args
    424     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    425     # . clear-stream(_test-error-stream)
    426     # . . push args
    427     68/push  _test-error-stream/imm32
    428     # . . call
    429     e8/call  clear-stream/disp32
    430     # . . discard args
    431     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    432     # initialize 'in'
    433     # . write(_test-stream, "3")
    434     # . . push args
    435     68/push  "3"/imm32
    436     68/push  _test-stream/imm32
    437     # . . call
    438     e8/call  write/disp32
    439     # . . discard args
    440     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    441     # initialize exit-descriptor 'ed' for the call to 'get-num' below
    442     # . var ed/EAX : (address exit-descriptor)
    443     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
    444     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
    445     # . tailor-exit-descriptor(ed, 16)
    446     # . . push args
    447     68/push  0x10/imm32/nbytes-of-args-for-get-num
    448     50/push-EAX/ed
    449     # . . call
    450     e8/call  tailor-exit-descriptor/disp32
    451     # . . discard args
    452     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    453     # *don't* prime the pump
    454     # get-num(in, out, err, ed)
    455     # . . push args
    456     50/push-EAX/ed
    457     68/push  _test-error-stream/imm32
    458     68/push  _test-output-stream/imm32
    459     68/push  _test-buffered-file/imm32
    460     # . . call
    461     e8/call  get-num/disp32
    462     # registers except ESP may be clobbered at this point
    463     # . . discard args
    464     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
    465     # check that get-num tried to call exit(1)
    466     # . check-ints-equal(ed->value, 2, msg)  # i.e. stop was called with value 1
    467     # . . push args
    468     68/push  "F - test-get-num-aborts-on-non-digit-in-Look"/imm32
    469     68/push  2/imm32
    470     # . . push ed->value
    471     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         .                 # push *(EAX+4)
    472     # . . call
    473     e8/call  check-ints-equal/disp32
    474     # . . discard args
    475     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    476     # . reclaim locals
    477     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    478     5d/pop-to-EBP
    479     c3/return
    480
    481 test-get-num-reads-multiple-digits:
    482     # - check that get-num returns all initial digits until it encounters a non-digit
    483     # This test uses exit-descriptors. Use EBP for setting up local variables.
    484     55/push-EBP
    485     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    486     # clear all streams
    487     # . clear-stream(_test-stream)
    488     # . . push args
    489     68/push  _test-stream/imm32
    490     # . . call
    491     e8/call  clear-stream/disp32
    492     # . . discard args
    493     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    494     # . clear-stream(_test-buffered-file+4)
    495     # . . push args
    496     b8/copy-to-EAX  _test-buffered-file/imm32
    497     05/add-to-EAX  4/imm32
    498     50/push-EAX
    499     # . . call
    500     e8/call  clear-stream/disp32
    501     # . . discard args
    502     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    503     # . clear-stream(_test-output-stream)
    504     # . . push args
    505     68/push  _test-output-stream/imm32
    506     # . . call
    507     e8/call  clear-stream/disp32
    508     # . . discard args
    509     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    510     # . clear-stream(_test-error-stream)
    511     # . . push args
    512     68/push  _test-error-stream/imm32
    513     # . . call
    514     e8/call  clear-stream/disp32
    515     # . . discard args
    516     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    517     # initialize 'in'
    518     # . write(_test-stream, "3456 x")
    519     # . . push args
    520     68/push  "3456"/imm32
    521     68/push  _test-stream/imm32
    522     # . . call
    523     e8/call  write/disp32
    524     # . . discard args
    525     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    526     # initialize exit-descriptor 'ed' for the call to 'get-num' below
    527     # . var ed/EAX : (address exit-descriptor)
    528     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
    529     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
    530     # . tailor-exit-descriptor(ed, 16)
    531     # . . push args
    532     68/push  0x10/imm32/nbytes-of-args-for-get-num
    533     50/push-EAX/ed
    534     # . . call
    535     e8/call  tailor-exit-descriptor/disp32
    536     # . . discard args
    537     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    538     # prime the pump
    539     # . get-char(_test-buffered-file)
    540     # . . push args
    541     68/push  _test-buffered-file/imm32
    542     # . . call
    543     e8/call  get-char/disp32
    544     # . . discard args
    545     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    546     # get-num(in, out, err, ed)
    547     # . . push args
    548     50/push-EAX/ed
    549     68/push  _test-error-stream/imm32
    550     68/push  _test-output-stream/imm32
    551     68/push  _test-buffered-file/imm32
    552     # . . call
    553     e8/call  get-num/disp32
    554     # registers except ESP may be clobbered at this point
    555     # . . discard args
    556     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
    557     # check-ints-equal(*_test-output-stream->data, '3456', msg)
    558     # . . push args
    559     68/push  "F - test-get-num-reads-multiple-digits"/imm32
    560     68/push  0x36353433/imm32
    561     b8/copy-to-EAX  _test-output-stream/imm32
    562     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
    563     # . . call
    564     e8/call  check-ints-equal/disp32
    565     # . . discard args
    566     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    567     # . reclaim locals
    568     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    569     5d/pop-to-EBP
    570     c3/return
    571
    572 test-get-num-reads-multiple-digits-followed-by-nondigit:
    573     # - check that get-num returns all initial digits until it encounters a non-digit
    574     # This test uses exit-descriptors. Use EBP for setting up local variables.
    575     55/push-EBP
    576     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    577     # clear all streams
    578     # . clear-stream(_test-stream)
    579     # . . push args
    580     68/push  _test-stream/imm32
    581     # . . call
    582     e8/call  clear-stream/disp32
    583     # . . discard args
    584     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    585     # . clear-stream(_test-buffered-file+4)
    586     # . . push args
    587     b8/copy-to-EAX  _test-buffered-file/imm32
    588     05/add-to-EAX  4/imm32
    589     50/push-EAX
    590     # . . call
    591     e8/call  clear-stream/disp32
    592     # . . discard args
    593     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    594     # . clear-stream(_test-output-stream)
    595     # . . push args
    596     68/push  _test-output-stream/imm32
    597     # . . call
    598     e8/call  clear-stream/disp32
    599     # . . discard args
    600     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    601     # . clear-stream(_test-error-stream)
    602     # . . push args
    603     68/push  _test-error-stream/imm32
    604     # . . call
    605     e8/call  clear-stream/disp32
    606     # . . discard args
    607     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    608     # initialize 'in'
    609     # . write(_test-stream, "3456 x")
    610     # . . push args
    611     68/push  "3456 x"/imm32
    612     68/push  _test-stream/imm32
    613     # . . call
    614     e8/call  write/disp32
    615     # . . discard args
    616     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    617     # initialize exit-descriptor 'ed' for the call to 'get-num' below
    618     # . var ed/EAX : (address exit-descriptor)
    619     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
    620     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
    621     # . tailor-exit-descriptor(ed, 16)
    622     # . . push args
    623     68/push  0x10/imm32/nbytes-of-args-for-get-num
    624     50/push-EAX/ed
    625     # . . call
    626     e8/call  tailor-exit-descriptor/disp32
    627     # . . discard args
    628     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    629     # prime the pump
    630     # . get-char(_test-buffered-file)
    631     # . . push args
    632     68/push  _test-buffered-file/imm32
    633     # . . call
    634     e8/call  get-char/disp32
    635     # . . discard args
    636     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    637     # get-num(in, out, err, ed)
    638     # . . push args
    639     50/push-EAX/ed
    640     68/push  _test-error-stream/imm32
    641     68/push  _test-output-stream/imm32
    642     68/push  _test-buffered-file/imm32
    643     # . . call
    644     e8/call  get-num/disp32
    645     # registers except ESP may be clobbered at this point
    646     # . . discard args
    647     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
    648     # check-ints-equal(*_test-output-stream->data, '3456', msg)
    649     # . . push args
    650     68/push  "F - test-get-num-reads-multiple-digits-followed-by-nondigit"/imm32
    651     68/push  0x36353433/imm32
    652     b8/copy-to-EAX  _test-output-stream/imm32
    653     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
    654     # . . call
    655     e8/call  check-ints-equal/disp32
    656     # . . discard args
    657     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    658     # . reclaim locals
    659     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    660     5d/pop-to-EBP
    661     c3/return
    662
    663 ## helpers
    664
    665 # write(f, "Error: "+s+" expected\n") then stop(ed, 1)
    666 expected:  # ed : (address exit-descriptor), f : fd or (address stream), s : (address array byte) -> <void>
    667     # . prolog
    668     55/push-EBP
    669     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    670     # write(f, "Error: ")
    671     # . . push args
    672     68/push  "Error: "/imm32
    673     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    674     # . . call
    675     e8/call  write/disp32
    676     # . . discard args
    677     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    678     # write(f, s)
    679     # . . push args
    680     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
    681     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    682     # . . call
    683     e8/call  write/disp32
    684     # . . discard args
    685     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    686     # write(f, " expected\n")
    687     # . . push args
    688     68/push  " expected\n"/imm32
    689     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    690     # . . call
    691     e8/call  write/disp32
    692     # . . discard args
    693     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    694     # stop(ed, 1)
    695     # . . push args
    696     68/push  1/imm32
    697     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    698     # . . call
    699     e8/call  stop/disp32
    700     # should never get past this point
    701 $expected:dead-end:
    702     # . epilog
    703     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    704     5d/pop-to-EBP
    705     c3/return
    706
    707 # read a byte from 'f', and save it in 'Look'
    708 get-char:  # f : (address buffered-file) -> <void>
    709     # . prolog
    710     55/push-EBP
    711     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    712     # . save registers
    713     50/push-EAX
    714     # read-byte(f)
    715     # . . push args
    716     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    717     # . . call
    718     e8/call  read-byte/disp32
    719     # . . discard args
    720     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    721     # save EAX to Look
    722     89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Look/disp32     .                 # copy EAX to *Look
    723 $get-char:end:
    724     # . restore registers
    725     58/pop-to-EAX
    726     # . epilog
    727     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    728     5d/pop-to-EBP
    729     c3/return
    730
    731 is-digit?:  # c : int -> EAX : boolean
    732     # . prolog
    733     55/push-EBP
    734     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    735     # EAX = false
    736     b8/copy-to-EAX  0/imm32
    737     # if (c < '0') return false
    738     81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         0x30/imm32        # compare *(EBP+8)
    739     7c/jump-if-lesser  $is-digit?:end/disp8
    740     # if (c > '9') return false
    741     81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         0x39/imm32        # compare *(EBP+8)
    742     7f/jump-if-greater  $is-digit?:end/disp8
    743     # otherwise return true
    744     b8/copy-to-EAX  1/imm32
    745 $is-digit?:end:
    746     # . epilog
    747     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    748     5d/pop-to-EBP
    749     c3/return
    750
    751 == data
    752
    753 Look:  # (char with some extra padding)
    754     0/imm32
    755
    756 _test-output-stream:
    757     # current write index
    758     0/imm32
    759     # current read index
    760     0/imm32
    761     # length
    762     8/imm32
    763     # data
    764     00 00 00 00 00 00 00 00  # 8 bytes
    765
    766 _test-error-stream:
    767     # current write index
    768     0/imm32
    769     # current read index
    770     0/imm32
    771     # length
    772     0x40/imm32
    773     # data (4 lines x 16 bytes/line)
    774     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    775     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    776     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    777     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    778
    779 # . . vim:nowrap:textwidth=0