https://github.com/akkartik/mu/blob/master/subx/apps/crenshaw2-1.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 digits.
      4 #
      5 # To run (from the subx/ directory):
      6 #   $ ./subx translate *.subx apps/crenshaw2-1.subx -o apps/crenshaw2-1
      7 #   $ echo '3'  |./subx run apps/crenshaw2-1
      8 # Expected output:
      9 #   # syscall(exit, 3)
     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 '3'  |./subx run apps/crenshaw2-1 > z1.subx
     16 #   $ ./subx translate z1.subx -o z1
     17 #   $ ./subx run z1
     18 #   $ echo $?
     19 #   3
     20 #
     21 # Stdin must contain just a single hex digit. Other input will print an error:
     22 #   $ echo 'xyz'  |./subx run apps/crenshaw2-1
     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-aborts-on-non-digit-in-Look/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 single digit into 'out'. Abort if there are none, or if there is no space in 'out'.
    183 # Input comes from the global variable 'Look', and we leave the next byte from
    184 # 'in' into it on exit.
    185 get-num:  # in : (address buffered-file), out : (address stream), err : fd or (address stream), ed : (address exit-descriptor) -> <void>
    186     # pseudocode:
    187     #   if (!is-digit?(Look)) expected(ed, err, "integer")
    188     #   if out->write >= out->length
    189     #     write(err, "Error: too many digits in number\n")
    190     #     stop(ed, 1)
    191     #   out->data[out->write] = LSB(Look)
    192     #   ++out->write
    193     #   Look = get-char(in)
    194     #
    195     # registers:
    196     #   in: ESI
    197     #   out: EDI
    198     #   out->write: ECX (cached copy; need to keep in sync)
    199     #   out->length: EDX
    200     #   temporaries: EAX, EBX
    201     # We can't allocate Look to a register because it gets written implicitly in
    202     # get-char in each iteration of the loop. (Thereby demonstrating that it's
    203     # not the right interface for us. But we'll keep it just to follow Crenshaw.)
    204     #
    205     # . prolog
    206     55/push-EBP
    207     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    208     # - if (is-digit?(Look)) expected(ed, err, "integer")
    209     # . EAX = is-digit?(Look)
    210     # . . push args
    211     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Look/disp32     .                 # push *Look
    212     # . . call
    213     e8/call  is-digit?/disp32
    214     # . . discard args
    215     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    216     # . if (EAX == 0)
    217     3d/compare-EAX-and  0/imm32
    218     75/jump-if-not-equal  $get-num:main/disp8
    219     # . expected(ed, err, "integer")
    220     # . . push args
    221     68/push  "integer"/imm32
    222     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
    223     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
    224     # . . call
    225     e8/call  expected/disp32  # never returns
    226     # . . discard args
    227     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    228 $get-num:main:
    229     # - otherwise read a digit
    230     # . save registers
    231     50/push-EAX
    232     51/push-ECX
    233     52/push-EDX
    234     53/push-EBX
    235     56/push-ESI
    236     57/push-EDI
    237     # read necessary variables to registers
    238     # ESI = in
    239     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
    240     # EDI = out
    241     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   0xc/disp8       .                 # copy *(EBP+12) to EDI
    242     # ECX = out->write
    243     8b/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           1/r32/ECX   .               .                 # copy *EDI to ECX
    244     # EDX = out->length
    245     8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           2/r32/EDX   8/disp8         .                 # copy *(EDI+8) to EDX
    246     # if (out->write >= out->length) error
    247     39/compare                      3/mod/direct    2/rm32/EDX    .           .             .           1/r32/ECX   .               .                 # compare EDX with ECX
    248     7d/jump-if-lesser  $get-num:stage2/disp8
    249     # . error(ed, err, msg)  # TODO: show full number
    250     # . . push args
    251     68/push  "get-num: too many digits in number"/imm32
    252     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
    253     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
    254     # . . call
    255     e8/call  error/disp32  # never returns
    256     # . . discard args
    257     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    258 $get-num:stage2:
    259     # out->data[out->write] = LSB(Look)
    260     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
    261     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Look/disp32     .                 # copy *Look to EAX
    262     88/copy-byte                    0/mod/indirect  3/rm32/EBX    .           .             .           0/r32/AL    .               .                 # copy byte at AL to *EBX
    263     # ++out->write
    264     41/increment-ECX
    265     # Look = get-char(in)
    266     # . . push args
    267     56/push-ESI
    268     # . . call
    269     e8/call  get-char/disp32
    270     # . . discard args
    271     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    272 $get-num:loop-end:
    273     # persist necessary variables from registers
    274     89/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           1/r32/ECX   .               .                 # copy ECX to *EDI
    275 $get-num:end:
    276     # . restore registers
    277     5f/pop-to-EDI
    278     5e/pop-to-ESI
    279     5b/pop-to-EBX
    280     5a/pop-to-EDX
    281     59/pop-to-ECX
    282     58/pop-to-EAX
    283     # . epilog
    284     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    285     5d/pop-to-EBP
    286     c3/return
    287
    288 test-get-num-reads-single-digit:
    289     # - check that get-num returns first character if it's a digit
    290     # This test uses exit-descriptors. Use EBP for setting up local variables.
    291     55/push-EBP
    292     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    293     # clear all streams
    294     # . clear-stream(_test-stream)
    295     # . . push args
    296     68/push  _test-stream/imm32
    297     # . . call
    298     e8/call  clear-stream/disp32
    299     # . . discard args
    300     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    301     # . clear-stream(_test-buffered-file+4)
    302     # . . push args
    303     b8/copy-to-EAX  _test-buffered-file/imm32
    304     05/add-to-EAX  4/imm32
    305     50/push-EAX
    306     # . . call
    307     e8/call  clear-stream/disp32
    308     # . . discard args
    309     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    310     # . clear-stream(_test-output-stream)
    311     # . . push args
    312     68/push  _test-output-stream/imm32
    313     # . . call
    314     e8/call  clear-stream/disp32
    315     # . . discard args
    316     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    317     # . clear-stream(_test-error-stream)
    318     # . . push args
    319     68/push  _test-error-stream/imm32
    320     # . . call
    321     e8/call  clear-stream/disp32
    322     # . . discard args
    323     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    324     # initialize 'in'
    325     # . write(_test-stream, "3")
    326     # . . push args
    327     68/push  "3"/imm32
    328     68/push  _test-stream/imm32
    329     # . . call
    330     e8/call  write/disp32
    331     # . . discard args
    332     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    333     # initialize exit-descriptor 'ed' for the call to 'get-num' below
    334     # . var ed/EAX : exit-descriptor
    335     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
    336     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
    337     # . tailor-exit-descriptor(ed, 16)
    338     # . . push args
    339     68/push  0x10/imm32/nbytes-of-args-for-get-num
    340     50/push-EAX/ed
    341     # . . call
    342     e8/call  tailor-exit-descriptor/disp32
    343     # . . discard args
    344     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    345     # prime the pump
    346     # . get-char(_test-buffered-file)
    347     # . . push args
    348     68/push  _test-buffered-file/imm32
    349     # . . call
    350     e8/call  get-char/disp32
    351     # . . discard args
    352     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    353     # get-num(in, out, err, ed)
    354     # . . push args
    355     50/push-EAX/ed
    356     68/push  _test-error-stream/imm32
    357     68/push  _test-output-stream/imm32
    358     68/push  _test-buffered-file/imm32
    359     # . . call
    360     e8/call  get-num/disp32
    361     # registers except ESP may be clobbered at this point
    362     # . . discard args
    363     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
    364     # check-ints-equal(*_test-output-stream->data, '3', msg)
    365     # . . push args
    366     68/push  "F - test-get-num-reads-single-digit"/imm32
    367     68/push  0x33/imm32
    368     b8/copy-to-EAX  _test-output-stream/imm32
    369     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
    370     # . . call
    371     e8/call  check-ints-equal/disp32
    372     # . . discard args
    373     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    374     # . reclaim locals
    375     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    376     5d/pop-to-EBP
    377     c3/return
    378
    379 test-get-num-aborts-on-non-digit-in-Look:
    380     # - check that get-num returns first character if it's a digit
    381     # This test uses exit-descriptors. Use EBP for setting up local variables.
    382     55/push-EBP
    383     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    384     # clear all streams
    385     # . clear-stream(_test-stream)
    386     # . . push args
    387     68/push  _test-stream/imm32
    388     # . . call
    389     e8/call  clear-stream/disp32
    390     # . . discard args
    391     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    392     # . clear-stream(_test-buffered-file+4)
    393     # . . push args
    394     b8/copy-to-EAX  _test-buffered-file/imm32
    395     05/add-to-EAX  4/imm32
    396     50/push-EAX
    397     # . . call
    398     e8/call  clear-stream/disp32
    399     # . . discard args
    400     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    401     # . clear-stream(_test-output-stream)
    402     # . . push args
    403     68/push  _test-output-stream/imm32
    404     # . . call
    405     e8/call  clear-stream/disp32
    406     # . . discard args
    407     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    408     # . clear-stream(_test-error-stream)
    409     # . . push args
    410     68/push  _test-error-stream/imm32
    411     # . . call
    412     e8/call  clear-stream/disp32
    413     # . . discard args
    414     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    415     # initialize 'in'
    416     # . write(_test-stream, "3")
    417     # . . push args
    418     68/push  "3"/imm32
    419     68/push  _test-stream/imm32
    420     # . . call
    421     e8/call  write/disp32
    422     # . . discard args
    423     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    424     # initialize exit-descriptor 'ed' for the call to 'get-num' below
    425     # . var ed/EAX : (address exit-descriptor)
    426     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
    427     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
    428     # . tailor-exit-descriptor(ed, 16)
    429     # . . push args
    430     68/push  0x10/imm32/nbytes-of-args-for-get-num
    431     50/push-EAX/ed
    432     # . . call
    433     e8/call  tailor-exit-descriptor/disp32
    434     # . . discard args
    435     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    436     # *don't* prime the pump
    437     # get-num(in, out, err, ed)
    438     # . . push args
    439     50/push-EAX/ed
    440     68/push  _test-error-stream/imm32
    441     68/push  _test-output-stream/imm32
    442     68/push  _test-buffered-file/imm32
    443     # . . call
    444     e8/call  get-num/disp32
    445     # registers except ESP may be clobbered at this point
    446     # . . discard args
    447     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
    448     # check that get-num tried to call exit(1)
    449     # . check-ints-equal(ed->value, 2, msg)  # i.e. stop was called with value 1
    450     # . . push args
    451     68/push  "F - test-get-num-aborts-on-non-digit-in-Look"/imm32
    452     68/push  2/imm32
    453     # . . push ed->value
    454     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         .                 # push *(EAX+4)
    455     # . . call
    456     e8/call  check-ints-equal/disp32
    457     # . . discard args
    458     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    459     # . reclaim locals
    460     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    461     5d/pop-to-EBP
    462     c3/return
    463
    464 ## helpers
    465
    466 # write(f, "Error: "+s+" expected\n") then stop(ed, 1)
    467 expected:  # ed : (address exit-descriptor), f : fd or (address stream), s : (address array byte) -> <void>
    468     # . prolog
    469     55/push-EBP
    470     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    471     # write(f, "Error: ")
    472     # . . push args
    473     68/push  "Error: "/imm32
    474     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    475     # . . call
    476     e8/call  write/disp32
    477     # . . discard args
    478     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    479     # write(f, s)
    480     # . . push args
    481     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
    482     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    483     # . . call
    484     e8/call  write/disp32
    485     # . . discard args
    486     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    487     # write(f, " expected")
    488     # . . push args
    489     68/push  " expected\n"/imm32
    490     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    491     # . . call
    492     e8/call  write/disp32
    493     # . . discard args
    494     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    495     # stop(ed, 1)
    496     # . . push args
    497     68/push  1/imm32
    498     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    499     # . . call
    500     e8/call  stop/disp32
    501     # should never get past this point
    502 $expected:dead-end:
    503     # . epilog
    504     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    505     5d/pop-to-EBP
    506     c3/return
    507
    508 # read a byte from 'f', and save it in 'Look'
    509 get-char:  # f : (address buffered-file) -> <void>
    510     # . prolog
    511     55/push-EBP
    512     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    513     # . save registers
    514     50/push-EAX
    515     # read-byte(f)
    516     # . . push args
    517     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    518     # . . call
    519     e8/call  read-byte/disp32
    520     # . . discard args
    521     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    522     # save EAX to Look
    523     89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Look/disp32     .                 # copy EAX to *Look
    524 $get-char:end:
    525     # . restore registers
    526     58/pop-to-EAX
    527     # . epilog
    528     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    529     5d/pop-to-EBP
    530     c3/return
    531
    532 is-digit?:  # c : int -> EAX : boolean
    533     # . prolog
    534     55/push-EBP
    535     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    536     # EAX = false
    537     b8/copy-to-EAX  0/imm32
    538     # if (c < '0') return false
    539     81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         0x30/imm32        # compare *(EBP+8)
    540     7c/jump-if-lesser  $is-digit?:end/disp8
    541     # if (c > '9') return false
    542     81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         0x39/imm32        # compare *(EBP+8)
    543     7f/jump-if-greater  $is-digit?:end/disp8
    544     # otherwise return true
    545     b8/copy-to-EAX  1/imm32
    546 $is-digit?:end:
    547     # . epilog
    548     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    549     5d/pop-to-EBP
    550     c3/return
    551
    552 == data
    553
    554 Look:  # (char with some extra padding)
    555     0/imm32
    556
    557 _test-output-stream:
    558     # current write index
    559     0/imm32
    560     # current read index
    561     0/imm32
    562     # length
    563     8/imm32
    564     # data
    565     00 00 00 00 00 00 00 00  # 8 bytes
    566
    567 _test-error-stream:
    568     # current write index
    569     0/imm32
    570     # current read index
    571     0/imm32
    572     # length
    573     0x40/imm32
    574     # data (4 lines x 16 bytes/line)
    575     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    576     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    577     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    578     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    579
    580 # . . vim:nowrap:textwidth=0