https://github.com/akkartik/mu/blob/master/subx/apps/hex.subx
   1 # Read a text file containing whitespace-separated pairs of ascii hex bytes
   2 # from stdin, and convert them into binary bytes (octets) on stdout. Ignore
   3 # comments between '#' and newline.
   4 #
   5 # To run (from the subx/ directory):
   6 #   $ ./subx translate *.subx apps/hex.subx -o apps/hex
   7 #   $ echo '80 81 82  # comment'  |./subx run apps/hex  |xxd -
   8 # Expected output:
   9 #   00000000: 8081 82
  10 #
  11 # Only hex bytes and comments are permitted. Outside of comments all words
  12 # must be exactly 2 characters long and contain only characters [0-9a-f]. No
  13 # uppercase hex.
  14 
  15 == code
  16 #   instruction                     effective address                                                   register    displacement    immediate
  17 # . op          subop               mod             rm32          base        index         scale       r32
  18 # . 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
  19 
  20     # for debugging: run a single test
  21 #?     e8/call test-skip-until-newline/disp32
  22 #?     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
  23 #?     eb/jump  $main:end/disp8
  24 
  25 # main: run tests if necessary, convert stdin if not
  26     # . prolog
  27     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
  28     # - if argc > 1 and argv[1] == "test" then return run_tests()
  29     # . argc > 1
  30     81          7/subop/compare     1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           0/disp8         1/imm32           # compare *EBP
  31     7e/jump-if-lesser-or-equal  $run-main/disp8
  32     # . argv[1] == "test"
  33     # . . push args
  34     68/push  "test"/imm32
  35     ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           8/disp8         .                 # push *(EBP+8)
  36     # . . call
  37     e8/call  kernel-string-equal/disp32
  38     # . . discard args
  39     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
  40     # . check result
  41     3d/compare-EAX  1/imm32
  42     75/jump-if-not-equal  $run-main/disp8
  43     # . run-tests()
  44 #?     e8/call test-hex-below-0/disp32
  45 #?     e8/call test-scan-next-byte/disp32
  46 #?     e8/call test-scan-next-byte-handles-eof/disp32
  47 #?     e8/call test-scan-next-byte-skips-comment/disp32
  48 #?     e8/call test-scan-next-byte-aborts-on-invalid-byte/disp32
  49 #?     e8/call test-convert-next-octet/disp32
  50     e8/call  run-tests/disp32
  51     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
  52     eb/jump  $main:end/disp8
  53 $run-main:
  54     # - otherwise convert stdin
  55     # var ed/EAX : exit-descriptor
  56     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
  57     8d/copy-address                 0/mod/indirect  4/rm32/sib    4/base/ESP  4/index/none  .           0/r32/EAX   .               .                 # copy ESP to EAX
  58     # configure ed to really exit()
  59     # . ed->target = 0
  60     c7/copy                         0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # copy to *EAX
  61     # return convert(Stdin, 1/stdout, 2/stderr, ed)
  62     # . . push args
  63     50/push-EAX/ed
  64     68/push  Stderr/imm32
  65     68/push  Stdout/imm32
  66     68/push  Stdin/imm32
  67     # . . call
  68     e8/call  convert/disp32
  69     # . . discard args
  70     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
  71     # . syscall(exit, 0)
  72     bb/copy-to-EBX  0/imm32
  73 $main:end:
  74     b8/copy-to-EAX  1/imm32/exit
  75     cd/syscall  0x80/imm8
  76 
  77 # the main entry point
  78 convert:  # in : (address buffered-file), out : (address buffered-file), err : (address buffered-file), ed : (address exit-descriptor) -> <void>
  79     # pseudocode:
  80     #   repeatedly
  81     #     EAX = convert-next-octet(in, err, ed)
  82     #     if EAX == 0xffffffff break  # eof
  83     #     write-byte(out, AL)
  84     #   flush(out)
  85     #
  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 $convert:loop:
  92     # EAX = convert-next-octet(in, err, ed)
  93     # . . push args
  94     ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           0x14/disp8      .                 # push *(EBP+20)
  95     ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           0x10/disp8      .                 # push *(EBP+16)
  96     ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           8/disp8         .                 # push *(EBP+8)
  97     # . . call
  98     e8/call  convert-next-octet/disp32
  99     # . . discard first 2 args
 100     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 101     # if EAX == 0xffffffff break
 102     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0xffffffff/imm32  # compare EAX
 103     74/jump-if-equal  $convert:loop-end/disp8
 104     # write-byte(out, AL)
 105     # . . push args
 106     50/push-EAX
 107     ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           0xc/disp8       .                 # push *(EBP+12)
 108     # . . call
 109     e8/call  write-byte/disp32
 110     # . . discard args
 111     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 112     # loop
 113     eb/jump  $convert:loop/disp8
 114 $convert:loop-end:
 115     # flush(out)
 116     # . . push args
 117     ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           0xc/disp8       .                 # push *(EBP+12)
 118     # . . call
 119     e8/call  flush/disp32
 120     # . . discard args
 121     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 122 $convert:end:
 123     # . restore registers
 124     58/pop-to-EAX
 125     # . epilog
 126     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 127     5d/pop-to-EBP
 128     c3/return
 129 
 130 # read bytes from 'in' until a sequence of two lowercase hex (0-9, a-f) bytes
 131 # skip spaces and newlines
 132 # on '#' skip bytes until newline
 133 # raise an error and abort on all other unexpected bytes
 134 # return in EAX an _octet_ containing the binary value of the two hex characters
 135 # return 0xffffffff on end of file
 136 convert-next-octet:  # in : (address buffered-file), err : (address buffered-file), ed : (address exit-descriptor) -> byte-or-eof/EAX
 137     # pseudocode:
 138     #   EAX = scan-next-byte(in, err, ed)
 139     #   if (EAX == 0xffffffff) return
 140     #   ECX = parse-hex-digit(EAX)
 141     #   EAX = scan-next-byte(in, err, ed)
 142     #   if (EAX == 0xffffffff) error("partial byte found.")
 143     #   EAX = parse-hex-digit(EAX)
 144     #   EAX = (ECX << 4) | EAX
 145     #   return
 146     #
 147     # . prolog
 148     55/push-EBP
 149     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 150     # . save registers
 151     51/push-ECX
 152     # EAX = scan-next-byte(in, err, ed)
 153     # . . push args
 154     ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           0x10/disp8      .                 # push *(EBP+16)
 155     ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           0xc/disp8       .                 # push *(EBP+12)
 156     ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           8/disp8         .                 # push *(EBP+8)
 157     # . . call
 158     e8/call  scan-next-byte/disp32
 159     # . . discard args
 160     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 161     # if (EAX == 0xffffffff) return
 162     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0xffffffff/imm32  # compare EAX
 163     74/jump-if-equal  $convert-next-octet:end/disp8
 164     # EAX = parse-hex-digit(EAX)
 165     e8/call parse-hex-digit/disp32
 166     # ECX = EAX
 167     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy EAX to ECX
 168     # EAX = scan-next-byte(in, err, ed)
 169     # . . push args
 170     ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           0x10/disp8      .                 # push *(EBP+16)
 171     ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           0xc/disp8       .                 # push *(EBP+12)
 172     ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           8/disp8         .                 # push *(EBP+8)
 173     # . . call
 174     e8/call  scan-next-byte/disp32
 175     # . . discard args
 176     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 177     # if (EAX == 0xffffffff) error(ed, err, "partial byte found.")
 178     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0xffffffff/imm32  # compare EAX
 179     75/jump-if-not-equal  $convert-next-octet:convert/disp8
 180     # . error-byte(ed, err, msg, '.')  # reusing error-byte to avoid creating _yet_ another helper
 181     # . . push args
 182     68/push  0x2e/imm32/period/dummy
 183     68/push  "convert-next-octet: partial byte found"/imm32
 184     ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           0xc/disp8       .                 # push *(EBP+12)
 185     ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           0x10/disp8      .                 # push *(EBP+16)
 186     # . . call
 187     e8/call  error-byte/disp32  # never returns
 188 $convert-next-octet:convert:
 189     # EAX = parse-hex-digit(EAX)
 190     e8/call parse-hex-digit/disp32
 191     # EAX = (ECX << 4) | EAX
 192     # . ECX <<= 4
 193     c1/shift    4/subop/left        3/mod/direct    1/rm32/ECX    .           .             .           .           .               4/imm8            # shift ECX left by 4 bits
 194     # . EAX |= ECX
 195     09/or                           3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # EAX = bitwise OR with ECX
 196 $convert-next-octet:end:
 197     # . restore registers
 198     59/pop-to-ECX
 199     # . epilog
 200     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 201     5d/pop-to-EBP
 202     c3/return
 203 
 204 test-convert-next-octet:
 205     # - check that the first two bytes of the input are assembled into the resulting octet
 206     # This test uses exit-descriptors. Use EBP for setting up local variables.
 207     55/push-EBP
 208     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 209     # clear all streams
 210     # . clear-stream(_test-stream)
 211     # . . push args
 212     68/push  _test-stream/imm32
 213     # . . call
 214     e8/call  clear-stream/disp32
 215     # . . discard args
 216     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 217     # . clear-stream(_test-buffered-file+4)
 218     # . . push args
 219     b8/copy-to-EAX  _test-buffered-file/imm32
 220     05/add-to-EAX  4/imm32
 221     50/push-EAX
 222     # . . call
 223     e8/call  clear-stream/disp32
 224     # . . discard args
 225     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 226     # . clear-stream(_test-error-stream)
 227     # . . push args
 228     68/push  _test-error-stream/imm32
 229     # . . call
 230     e8/call  clear-stream/disp32
 231     # . . discard args
 232     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 233     # . clear-stream(_test-error-buffered-file+4)
 234     # . . push args
 235     b8/copy-to-EAX  _test-error-buffered-file/imm32
 236     05/add-to-EAX  4/imm32
 237     50/push-EAX
 238     # . . call
 239     e8/call  clear-stream/disp32
 240     # . . discard args
 241     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 242     # initialize '_test-stream' to "abc"
 243     # . write(_test-stream, "abc")
 244     # . . push args
 245     68/push  "abc"/imm32
 246     68/push  _test-stream/imm32
 247     # . . call
 248     e8/call  write/disp32
 249     # . . discard args
 250     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 251     # initialize exit-descriptor 'ed' for the call to 'convert-next-octet' below
 252     # . var ed/ECX : exit-descriptor
 253     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
 254     8d/copy-address                 0/mod/indirect  4/rm32/sib    4/base/ESP  4/index/none  .           1/r32/ECX   .               .                 # copy ESP to ECX
 255     # . tailor-exit-descriptor(ed, 12)
 256     # . . push args
 257     68/push  0xc/imm32/nbytes-of-args-for-convert-next-octet
 258     51/push-ECX/ed
 259     # . . call
 260     e8/call  tailor-exit-descriptor/disp32
 261     # . . discard args
 262     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 263     # EAX = convert-next-octet(_test-buffered-file, _test-error-buffered-file, ed)
 264     # . . push args
 265     51/push-ECX/ed
 266     68/push  _test-error-buffered-file/imm32
 267     68/push  _test-buffered-file/imm32
 268     # . . call
 269     e8/call  convert-next-octet/disp32
 270     # registers except ESP may be clobbered at this point
 271     # pop args to convert-next-octet
 272     # . . discard first 2 args
 273     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 274     # . . restore ed
 275     59/pop-to-ECX
 276     # check that convert-next-octet didn't abort
 277     # . check-ints-equal(ed->value, 0, msg)
 278     # . . push args
 279     68/push  "F - test-convert-next-octet: unexpected abort"/imm32
 280     68/push  0/imm32
 281     # . . push ed->value
 282     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
 283     # . . call
 284     e8/call  check-ints-equal/disp32
 285     # . . discard args
 286     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 287     # return if abort
 288     81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
 289     75/jump-if-not-equal  $test-convert-next-octet:end/disp8
 290     # check-ints-equal(EAX, 0xab, msg)
 291     # . . push args
 292     68/push  "F - test-convert-next-octet"/imm32
 293     68/push  0xab/imm32/ab
 294     50/push-EAX
 295     # . . call
 296     e8/call  check-ints-equal/disp32
 297     # . . discard args
 298     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 299 $test-convert-next-octet:end:
 300     # . epilog
 301     # don't restore ESP from EBP; manually reclaim locals
 302     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 303     5d/pop-to-EBP
 304     c3/return
 305 
 306 test-convert-next-octet-handles-eof:
 307     # - check that eof returns the sentinel octet
 308     # This test uses exit-descriptors. Use EBP for setting up local variables.
 309     55/push-EBP
 310     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 311     # clear all streams
 312     # . clear-stream(_test-stream)
 313     # . . push args
 314     68/push  _test-stream/imm32
 315     # . . call
 316     e8/call  clear-stream/disp32
 317     # . . discard args
 318     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 319     # . clear-stream(_test-buffered-file+4)
 320     # . . push args
 321     b8/copy-to-EAX  _test-buffered-file/imm32
 322     05/add-to-EAX  4/imm32
 323     50/push-EAX
 324     # . . call
 325     e8/call  clear-stream/disp32
 326     # . . discard args
 327     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 328     # . clear-stream(_test-error-stream)
 329     # . . push args
 330     68/push  _test-error-stream/imm32
 331     # . . call
 332     e8/call  clear-stream/disp32
 333     # . . discard args
 334     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 335     # . clear-stream(_test-error-buffered-file+4)
 336     # . . push args
 337     b8/copy-to-EAX  _test-error-buffered-file/imm32
 338     05/add-to-EAX  4/imm32
 339     50/push-EAX
 340     # . . call
 341     e8/call  clear-stream/disp32
 342     # . . discard args
 343     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 344     # don't initialize '_test-stream'
 345     # initialize exit-descriptor 'ed' for the call to 'convert-next-octet' below
 346     # . var ed/ECX : exit-descriptor
 347     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
 348     8d/copy-address                 0/mod/indirect  4/rm32/sib    4/base/ESP  4/index/none  .           1/r32/ECX   .               .                 # copy ESP to ECX
 349     # . tailor-exit-descriptor(ed, 12)
 350     # . . push args
 351     68/push  0xc/imm32/nbytes-of-args-for-convert-next-octet
 352     51/push-ECX/ed
 353     # . . call
 354     e8/call  tailor-exit-descriptor/disp32
 355     # . . discard args
 356     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 357     # EAX = convert-next-octet(_test-buffered-file, _test-error-buffered-file, ed)
 358     # . . push args
 359     51/push-ECX/ed
 360     68/push  _test-error-buffered-file/imm32
 361     68/push  _test-buffered-file/imm32
 362     # . . call
 363     e8/call  convert-next-octet/disp32
 364     # registers except ESP may be clobbered at this point
 365     # pop args to convert-next-octet
 366     # . . discard first 2 args
 367     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 368     # . . restore ed
 369     59/pop-to-ECX
 370     # check that convert-next-octet didn't abort
 371     # . check-ints-equal(ed->value, 0, msg)
 372     # . . push args
 373     68/push  "F - test-convert-next-octet: unexpected abort"/imm32
 374     68/push  0/imm32
 375     # . . push ed->value
 376     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
 377     # . . call
 378     e8/call  check-ints-equal/disp32
 379     # . . discard args
 380     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 381     # return if abort
 382     81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
 383     75/jump-if-not-equal  $test-convert-next-octet-handles-eof:end/disp8
 384     # check-ints-equal(EAX, 0xffffffff, msg)
 385     # . . push args
 386     68/push  "F - test-convert-next-octet-handles-eof"/imm32
 387     68/push  0xffffffff/imm32/eof
 388     50/push-EAX
 389     # . . call
 390     e8/call  check-ints-equal/disp32
 391     # . . discard args
 392     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 393 $test-convert-next-octet-handles-eof:end:
 394     # . epilog
 395     # don't restore ESP from EBP; manually reclaim locals
 396     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 397     5d/pop-to-EBP
 398     c3/return
 399 
 400 test-convert-next-octet-aborts-on-single-hex-byte:
 401     # - check that a single unaccompanied hex byte aborts
 402     # This test uses exit-descriptors. Use EBP for setting up local variables.
 403     55/push-EBP
 404     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 405     # clear all streams
 406     # . clear-stream(_test-stream)
 407     # . . push args
 408     68/push  _test-stream/imm32
 409     # . . call
 410     e8/call  clear-stream/disp32
 411     # . . discard args
 412     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 413     # . clear-stream(_test-buffered-file+4)
 414     # . . push args
 415     b8/copy-to-EAX  _test-buffered-file/imm32
 416     05/add-to-EAX  4/imm32
 417     50/push-EAX
 418     # . . call
 419     e8/call  clear-stream/disp32
 420     # . . discard args
 421     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 422     # . clear-stream(_test-error-stream)
 423     # . . push args
 424     68/push  _test-error-stream/imm32
 425     # . . call
 426     e8/call  clear-stream/disp32
 427     # . . discard args
 428     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 429     # . clear-stream(_test-error-buffered-file+4)
 430     # . . push args
 431     b8/copy-to-EAX  _test-error-buffered-file/imm32
 432     05/add-to-EAX  4/imm32
 433     50/push-EAX
 434     # . . call
 435     e8/call  clear-stream/disp32
 436     # . . discard args
 437     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 438     # initialize '_test-stream' to "a"
 439     # . write(_test-stream, "a")
 440     # . . push args
 441     68/push  "a"/imm32
 442     68/push  _test-stream/imm32
 443     # . . call
 444     e8/call  write/disp32
 445     # . . discard args
 446     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 447     # initialize exit-descriptor 'ed' for the call to 'convert-next-octet' below
 448     # . var ed/ECX : exit-descriptor
 449     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
 450     8d/copy-address                 0/mod/indirect  4/rm32/sib    4/base/ESP  4/index/none  .           1/r32/ECX   .               .                 # copy ESP to ECX
 451     # . tailor-exit-descriptor(ed, 12)
 452     # . . push args
 453     68/push  0xc/imm32/nbytes-of-args-for-convert-next-octet
 454     51/push-ECX/ed
 455     # . . call
 456     e8/call  tailor-exit-descriptor/disp32
 457     # . . discard args
 458     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 459     # EAX = convert-next-octet(_test-buffered-file, _test-error-buffered-file, ed)
 460     # . . push args
 461     51/push-ECX/ed
 462     68/push  _test-error-buffered-file/imm32
 463     68/push  _test-buffered-file/imm32
 464     # . . call
 465     e8/call  convert-next-octet/disp32
 466     # registers except ESP may be clobbered at this point
 467     # pop args to convert-next-octet
 468     # . . discard first 2 args
 469     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 470     # . . restore ed
 471     59/pop-to-ECX
 472     # check that convert-next-octet aborted
 473     # . check-ints-equal(ed->value, 2, msg)
 474     # . . push args
 475     68/push  "F - test-convert-next-octet-aborts-on-single-hex-byte: unexpected abort"/imm32
 476     68/push  2/imm32
 477     # . . push ed->value
 478     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
 479     # . . call
 480     e8/call  check-ints-equal/disp32
 481     # . . discard args
 482     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 483 $test-convert-next-octet-aborts-on-single-hex-byte:end:
 484     # . epilog
 485     # don't restore ESP from EBP; manually reclaim locals
 486     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 487     5d/pop-to-EBP
 488     c3/return
 489 
 490 # read whitespace until a hex byte, and return it
 491 # return 0xffffffff if file ends without finding a hex byte
 492 # on '#' skip all bytes until newline
 493 # abort on any other byte
 494 scan-next-byte:  # in : (address buffered-file), err : (address buffered-file), ed : (address exit-descriptor) -> byte-or-eof/EAX
 495     # pseudocode:
 496     #   repeatedly
 497     #     EAX = read-byte(in)
 498     #     if EAX == 0xffffffff return EAX
 499     #     if is-hex-lowercase-byte?(EAX) return EAX
 500     #     if EAX == ' ' or '\t' or '\n' continue
 501     #     if EAX == '#' skip-until-newline(in)
 502     #     else error-byte(ed, err, "invalid byte: " EAX)
 503     #
 504     # . prolog
 505     55/push-EBP
 506     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 507     # . save registers
 508 $scan-next-byte:loop:
 509     # EAX = read-byte(in)
 510     # . . push args
 511     ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           8/disp8         .                 # push *(EBP+8)
 512     # . . call
 513     e8/call  read-byte/disp32
 514     # . . discard args
 515     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 516     # if (EAX == 0xffffffff) return EAX
 517     3d/compare-with-EAX  0xffffffff/imm32
 518     74/jump-if-equal  $scan-next-byte:end/disp8
 519     # if is-hex-lowercase-byte?(EAX) return EAX
 520     # . save EAX for now
 521     50/push-EAX
 522     # . is-hex-lowercase-byte?(EAX)
 523     # . . push args
 524     50/push-EAX
 525     # . . call
 526     e8/call  is-hex-lowercase-byte?/disp32
 527     # . . discard args
 528     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 529     # . compare with 'false'
 530     3d/compare-with-EAX  0/imm32
 531     # . restore EAX (does not affect flags)
 532     58/pop-to-EAX
 533     # . check whether to return
 534     75/jump-if-not-equal  $scan-next-byte:end/disp8
 535 $scan-next-byte:check1:
 536     # if EAX == ' ' continue
 537     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0x20/imm32        # compare EAX
 538     74/jump-if-equal  $scan-next-byte:loop/disp8
 539     # if EAX == '\t' continue
 540     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0x9/imm32         # compare EAX
 541     74/jump-if-equal  $scan-next-byte:loop/disp8
 542     # if EAX == '\n' continue
 543     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0xa/imm32         # compare EAX
 544     74/jump-if-equal  $scan-next-byte:loop/disp8
 545 $scan-next-byte:check2:
 546     # if EAX == '#' skip-until-newline(in)
 547     3d/compare-with-EAX  0x23/imm32
 548     75/jump-if-not-equal  $scan-next-byte:check3/disp8
 549     # . skip-until-newline(in)
 550     # . . push args
 551     ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           8/disp8         .                 # push *(EBP+8)
 552     # . . call
 553     e8/call  skip-until-newline/disp32
 554     # . . discard args
 555     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 556     eb/jump  $scan-next-byte:loop/disp8
 557 $scan-next-byte:check3:
 558     # otherwise error-byte(ed, err, msg, EAX)
 559     # . . push args
 560     50/push-EAX
 561     68/push  "scan-next-byte: invalid byte"/imm32
 562     ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           0xc/disp8       .                 # push *(EBP+12)
 563     ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           0x10/disp8      .                 # push *(EBP+16)
 564     # . . call
 565     e8/call  error-byte/disp32  # never returns
 566 $scan-next-byte:end:
 567     # . restore registers
 568     # . epilog
 569     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 570     5d/pop-to-EBP
 571     c3/return
 572 
 573 test-scan-next-byte:
 574     # - check that the first byte of the input is returned
 575     # This test uses exit-descriptors. Use EBP for setting up local variables.
 576     55/push-EBP
 577     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 578     # clear all streams
 579     # . clear-stream(_test-stream)
 580     # . . push args
 581     68/push  _test-stream/imm32
 582     # . . call
 583     e8/call  clear-stream/disp32
 584     # . . discard args
 585     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 586     # . clear-stream(_test-buffered-file+4)
 587     # . . push args
 588     b8/copy-to-EAX  _test-buffered-file/imm32
 589     05/add-to-EAX  4/imm32
 590     50/push-EAX
 591     # . . call
 592     e8/call  clear-stream/disp32
 593     # . . discard args
 594     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 595     # . clear-stream(_test-error-stream)
 596     # . . push args
 597     68/push  _test-error-stream/imm32
 598     # . . call
 599     e8/call  clear-stream/disp32
 600     # . . discard args
 601     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 602     # . clear-stream(_test-error-buffered-file+4)
 603     # . . push args
 604     b8/copy-to-EAX  _test-error-buffered-file/imm32
 605     05/add-to-EAX  4/imm32
 606     50/push-EAX
 607     # . . call
 608     e8/call  clear-stream/disp32
 609     # . . discard args
 610     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 611     # initialize '_test-stream' to "abc"
 612     # . write(_test-stream, "abc")
 613     # . . push args
 614     68/push  "abc"/imm32
 615     68/push  _test-stream/imm32
 616     # . . call
 617     e8/call  write/disp32
 618     # . . discard args
 619     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 620     # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
 621     # . var ed/ECX : exit-descriptor
 622     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
 623     8d/copy-address                 0/mod/indirect  4/rm32/sib    4/base/ESP  4/index/none  .           1/r32/ECX   .               .                 # copy ESP to ECX
 624     # . tailor-exit-descriptor(ed, 12)
 625     # . . push args
 626     68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
 627     51/push-ECX/ed
 628     # . . call
 629     e8/call  tailor-exit-descriptor/disp32
 630     # . . discard args
 631     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 632     # EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
 633     # . . push args
 634     51/push-ECX/ed
 635     68/push  _test-error-buffered-file/imm32
 636     68/push  _test-buffered-file/imm32
 637     # . . call
 638     e8/call  scan-next-byte/disp32
 639     # registers except ESP may be clobbered at this point
 640     # pop args to scan-next-byte
 641     # . . discard first 2 args
 642     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 643     # . . restore ed
 644     59/pop-to-ECX
 645     # check that scan-next-byte didn't abort
 646     # . check-ints-equal(ed->value, 0, msg)
 647     # . . push args
 648     68/push  "F - test-scan-next-byte: unexpected abort"/imm32
 649     68/push  0/imm32
 650     # . . push ed->value
 651     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
 652     # . . call
 653     e8/call  check-ints-equal/disp32
 654     # . . discard args
 655     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 656     # return if abort
 657     81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
 658     75/jump-if-not-equal  $test-scan-next-byte:end/disp8
 659     # check-ints-equal(EAX, 0x61/a, msg)
 660     # . . push args
 661     68/push  "F - test-scan-next-byte"/imm32
 662     68/push  0x61/imm32/a
 663     50/push-EAX
 664     # . . call
 665     e8/call  check-ints-equal/disp32
 666     # . . discard args
 667     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 668 $test-scan-next-byte:end:
 669     # . epilog
 670     # don't restore ESP from EBP; manually reclaim locals
 671     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 672     5d/pop-to-EBP
 673     c3/return
 674 
 675 test-scan-next-byte-skips-whitespace:
 676     # - check that the first byte after whitespace is returned
 677     # This test uses exit-descriptors. Use EBP for setting up local variables.
 678     55/push-EBP
 679     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 680     # clear all streams
 681     # . clear-stream(_test-stream)
 682     # . . push args
 683     68/push  _test-stream/imm32
 684     # . . call
 685     e8/call  clear-stream/disp32
 686     # . . discard args
 687     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 688     # . clear-stream(_test-buffered-file+4)
 689     # . . push args
 690     b8/copy-to-EAX  _test-buffered-file/imm32
 691     05/add-to-EAX  4/imm32
 692     50/push-EAX
 693     # . . call
 694     e8/call  clear-stream/disp32
 695     # . . discard args
 696     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 697     # . clear-stream(_test-error-stream)
 698     # . . push args
 699     68/push  _test-error-stream/imm32
 700     # . . call
 701     e8/call  clear-stream/disp32
 702     # . . discard args
 703     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 704     # . clear-stream(_test-error-buffered-file+4)
 705     # . . push args
 706     b8/copy-to-EAX  _test-error-buffered-file/imm32
 707     05/add-to-EAX  4/imm32
 708     50/push-EAX
 709     # . . call
 710     e8/call  clear-stream/disp32
 711     # . . discard args
 712     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 713     # initialize '_test-stream' to input with leading whitespace
 714     # . write(_test-stream, text)
 715     # . . push args
 716     68/push  "  abc"/imm32
 717     68/push  _test-stream/imm32
 718     # . . call
 719     e8/call  write/disp32
 720     # . . discard args
 721     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 722     # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
 723     # . var ed/ECX : exit-descriptor
 724     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
 725     8d/copy-address                 0/mod/indirect  4/rm32/sib    4/base/ESP  4/index/none  .           1/r32/ECX   .               .                 # copy ESP to ECX
 726     # . tailor-exit-descriptor(ed, 12)
 727     # . . push args
 728     68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
 729     51/push-ECX/ed
 730     # . . call
 731     e8/call  tailor-exit-descriptor/disp32
 732     # . . discard args
 733     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 734     # EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
 735     # . . push args
 736     51/push-ECX/ed
 737     68/push  _test-error-buffered-file/imm32
 738     68/push  _test-buffered-file/imm32
 739     # . . call
 740     e8/call  scan-next-byte/disp32
 741     # registers except ESP may be clobbered at this point
 742     # pop args to scan-next-byte
 743     # . . discard first 2 args
 744     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 745     # . . restore ed
 746     59/pop-to-ECX
 747     # check that scan-next-byte didn't abort
 748     # . check-ints-equal(ed->value, 0, msg)
 749     # . . push args
 750     68/push  "F - test-scan-next-byte-skips-whitespace: unexpected abort"/imm32
 751     68/push  0/imm32
 752     # . . push ed->value
 753     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
 754     # . . call
 755     e8/call  check-ints-equal/disp32
 756     # . . discard args
 757     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 758     # return if abort
 759     81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
 760     75/jump-if-not-equal  $test-scan-next-byte-skips-whitespace:end/disp8
 761     # check-ints-equal(EAX, 0x61/a, msg)
 762     # . . push args
 763     68/push  "F - test-scan-next-byte-skips-whitespace"/imm32
 764     68/push  0x61/imm32/a
 765     50/push-EAX
 766     # . . call
 767     e8/call  check-ints-equal/disp32
 768     # . . discard args
 769     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 770 $test-scan-next-byte-skips-whitespace:end:
 771     # . epilog
 772     # don't restore ESP from EBP; manually reclaim locals
 773     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 774     5d/pop-to-EBP
 775     c3/return
 776 
 777 test-scan-next-byte-skips-comment:
 778     # - check that the first byte after a comment (and newline) is returned
 779     # This test uses exit-descriptors. Use EBP for setting up local variables.
 780     55/push-EBP
 781     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 782     # clear all streams
 783     # . clear-stream(_test-stream)
 784     # . . push args
 785     68/push  _test-stream/imm32
 786     # . . call
 787     e8/call  clear-stream/disp32
 788     # . . discard args
 789     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 790     # . clear-stream(_test-buffered-file+4)
 791     # . . push args
 792     b8/copy-to-EAX  _test-buffered-file/imm32
 793     05/add-to-EAX  4/imm32
 794     50/push-EAX
 795     # . . call
 796     e8/call  clear-stream/disp32
 797     # . . discard args
 798     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 799     # . clear-stream(_test-error-stream)
 800     # . . push args
 801     68/push  _test-error-stream/imm32
 802     # . . call
 803     e8/call  clear-stream/disp32
 804     # . . discard args
 805     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 806     # . clear-stream(_test-error-buffered-file+4)
 807     # . . push args
 808     b8/copy-to-EAX  _test-error-buffered-file/imm32
 809     05/add-to-EAX  4/imm32
 810     50/push-EAX
 811     # . . call
 812     e8/call  clear-stream/disp32
 813     # . . discard args
 814     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 815     # initialize '_test-stream' to input with leading comment
 816     # . write(_test-stream, comment)
 817     # . . push args
 818     68/push  "#x"/imm32
 819     68/push  _test-stream/imm32
 820     # . . call
 821     e8/call  write/disp32
 822     # . . discard args
 823     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 824     # . write(_test-stream, Newline)
 825     # . . push args
 826     68/push  Newline/imm32
 827     68/push  _test-stream/imm32
 828     # . . call
 829     e8/call  write/disp32
 830     # . . discard args
 831     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 832     # . write(_test-stream, real text)
 833     # . . push args
 834     68/push  "ab"/imm32
 835     68/push  _test-stream/imm32
 836     # . . call
 837     e8/call  write/disp32
 838     # . . discard args
 839     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 840     # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
 841     # . var ed/ECX : exit-descriptor
 842     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
 843     8d/copy-address                 0/mod/indirect  4/rm32/sib    4/base/ESP  4/index/none  .           1/r32/ECX   .               .                 # copy ESP to ECX
 844     # . tailor-exit-descriptor(ed, 12)
 845     # . . push args
 846     68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
 847     51/push-ECX/ed
 848     # . . call
 849     e8/call  tailor-exit-descriptor/disp32
 850     # . . discard args
 851     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 852     # EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
 853     # . . push args
 854     51/push-ECX/ed
 855     68/push  _test-error-buffered-file/imm32
 856     68/push  _test-buffered-file/imm32
 857     # . . call
 858     e8/call  scan-next-byte/disp32
 859     # registers except ESP may be clobbered at this point
 860     # pop args to scan-next-byte
 861     # . . discard first 2 args
 862     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 863     # . . restore ed
 864     59/pop-to-ECX
 865     # check that scan-next-byte didn't abort
 866     # . check-ints-equal(ed->value, 0, msg)
 867     # . . push args
 868     68/push  "F - test-scan-next-byte-skips-comment: unexpected abort"/imm32
 869     68/push  0/imm32
 870     # . . push ed->value
 871     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
 872     # . . call
 873     e8/call  check-ints-equal/disp32
 874     # . . discard args
 875     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 876     # return if abort
 877     81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
 878     75/jump-if-not-equal  $test-scan-next-byte-skips-comment:end/disp8
 879     # check-ints-equal(EAX, 0x61/a, msg)
 880     # . . push args
 881     68/push  "F - test-scan-next-byte-skips-comment"/imm32
 882     68/push  0x61/imm32/a
 883     50/push-EAX
 884     # . . call
 885     e8/call  check-ints-equal/disp32
 886     # . . discard args
 887     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 888 $test-scan-next-byte-skips-comment:end:
 889     # . epilog
 890     # don't restore ESP from EBP; manually reclaim locals
 891     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 892     5d/pop-to-EBP
 893     c3/return
 894 
 895 test-scan-next-byte-skips-comment-and-whitespace:
 896     # - check that the first byte after a comment and any further whitespace is returned
 897     # This test uses exit-descriptors. Use EBP for setting up local variables.
 898     55/push-EBP
 899     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 900     # clear all streams
 901     # . clear-stream(_test-stream)
 902     # . . push args
 903     68/push  _test-stream/imm32
 904     # . . call
 905     e8/call  clear-stream/disp32
 906     # . . discard args
 907     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 908     # . clear-stream(_test-buffered-file+4)
 909     # . . push args
 910     b8/copy-to-EAX  _test-buffered-file/imm32
 911     05/add-to-EAX  4/imm32
 912     50/push-EAX
 913     # . . call
 914     e8/call  clear-stream/disp32
 915     # . . discard args
 916     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 917     # . clear-stream(_test-error-stream)
 918     # . . push args
 919     68/push  _test-error-stream/imm32
 920     # . . call
 921     e8/call  clear-stream/disp32
 922     # . . discard args
 923     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 924     # . clear-stream(_test-error-buffered-file+4)
 925     # . . push args
 926     b8/copy-to-EAX  _test-error-buffered-file/imm32
 927     05/add-to-EAX  4/imm32
 928     50/push-EAX
 929     # . . call
 930     e8/call  clear-stream/disp32
 931     # . . discard args
 932     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 933     # initialize '_test-stream' to input with leading comment and more whitespace after newline
 934     # . write(_test-stream, comment)
 935     # . . push args
 936     68/push  "#x"/imm32
 937     68/push  _test-stream/imm32
 938     # . . call
 939     e8/call  write/disp32
 940     # . . discard args
 941     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 942     # . write(_test-stream, Newline)
 943     # . . push args
 944     68/push  Newline/imm32
 945     68/push  _test-stream/imm32
 946     # . . call
 947     e8/call  write/disp32
 948     # . . discard args
 949     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 950     # . write(_test-stream, real text)
 951     # . . push args
 952     68/push  " ab"/imm32
 953     68/push  _test-stream/imm32
 954     # . . call
 955     e8/call  write/disp32
 956     # . . discard args
 957     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 958     # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
 959     # . var ed/ECX : exit-descriptor
 960     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
 961     8d/copy-address                 0/mod/indirect  4/rm32/sib    4/base/ESP  4/index/none  .           1/r32/ECX   .               .                 # copy ESP to ECX
 962     # . tailor-exit-descriptor(ed, 12)
 963     # . . push args
 964     68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
 965     51/push-ECX/ed
 966     # . . call
 967     e8/call  tailor-exit-descriptor/disp32
 968     # . . discard args
 969     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 970     # EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
 971     # . . push args
 972     51/push-ECX/ed
 973     68/push  _test-error-buffered-file/imm32
 974     68/push  _test-buffered-file/imm32
 975     # . . call
 976     e8/call  scan-next-byte/disp32
 977     # registers except ESP may be clobbered at this point
 978     # pop args to scan-next-byte
 979     # . . discard first 2 args
 980     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 981     # . . restore ed
 982     59/pop-to-ECX
 983     # check that scan-next-byte didn't abort
 984     # . check-ints-equal(ed->value, 0, msg)
 985     # . . push args
 986     68/push  "F - test-scan-next-byte-skips-comment-and-whitespace: unexpected abort"/imm32
 987     68/push  0/imm32
 988     # . . push ed->value
 989     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
 990     # . . call
 991     e8/call  check-ints-equal/disp32
 992     # . . discard args
 993     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 994     # return if abort
 995     81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
 996     75/jump-if-not-equal  $test-scan-next-byte-skips-comment-and-whitespace:end/disp8
 997     # check-ints-equal(EAX, 0x61/a, msg)
 998     # . . push args
 999     68/push  "F - test-scan-next-byte-skips-comment-and-whitespace"/imm32
1000     68/push  0x61/imm32/a
1001     50/push-EAX
1002     # . . call
1003     e8/call  check-ints-equal/disp32
1004     # . . discard args
1005     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1006 $test-scan-next-byte-skips-comment-and-whitespace:end:
1007     # . epilog
1008     # don't restore ESP from EBP; manually reclaim locals
1009     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1010     5d/pop-to-EBP
1011     c3/return
1012 
1013 test-scan-next-byte-skips-whitespace-and-comment:
1014     # - check that the first byte after any whitespace and comments is returned
1015     # This test uses exit-descriptors. Use EBP for setting up local variables.
1016     55/push-EBP
1017     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1018     # clear all streams
1019     # . clear-stream(_test-stream)
1020     # . . push args
1021     68/push  _test-stream/imm32
1022     # . . call
1023     e8/call  clear-stream/disp32
1024     # . . discard args
1025     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1026     # . clear-stream(_test-buffered-file+4)
1027     # . . push args
1028     b8/copy-to-EAX  _test-buffered-file/imm32
1029     05/add-to-EAX  4/imm32
1030     50/push-EAX
1031     # . . call
1032     e8/call  clear-stream/disp32
1033     # . . discard args
1034     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1035     # . clear-stream(_test-error-stream)
1036     # . . push args
1037     68/push  _test-error-stream/imm32
1038     # . . call
1039     e8/call  clear-stream/disp32
1040     # . . discard args
1041     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1042     # . clear-stream(_test-error-buffered-file+4)
1043     # . . push args
1044     b8/copy-to-EAX  _test-error-buffered-file/imm32
1045     05/add-to-EAX  4/imm32
1046     50/push-EAX
1047     # . . call
1048     e8/call  clear-stream/disp32
1049     # . . discard args
1050     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1051     # initialize '_test-stream' to input with leading whitespace and comment
1052     # . write(_test-stream, comment)
1053     # . . push args
1054     68/push  " #x"/imm32
1055     68/push  _test-stream/imm32
1056     # . . call
1057     e8/call  write/disp32
1058     # . . discard args
1059     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1060     # . write(_test-stream, Newline)
1061     # . . push args
1062     68/push  Newline/imm32
1063     68/push  _test-stream/imm32
1064     # . . call
1065     e8/call  write/disp32
1066     # . . discard args
1067     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1068     # . write(_test-stream, real text)
1069     # . . push args
1070     68/push  "ab"/imm32
1071     68/push  _test-stream/imm32
1072     # . . call
1073     e8/call  write/disp32
1074     # . . discard args
1075     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1076     # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
1077     # . var ed/ECX : exit-descriptor
1078     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
1079     8d/copy-address                 0/mod/indirect  4/rm32/sib    4/base/ESP  4/index/none  .           1/r32/ECX   .               .                 # copy ESP to ECX
1080     # . tailor-exit-descriptor(ed, 12)
1081     # . . push args
1082     68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
1083     51/push-ECX/ed
1084     # . . call
1085     e8/call  tailor-exit-descriptor/disp32
1086     # . . discard args
1087     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1088     # EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
1089     # . . push args
1090     51/push-ECX/ed
1091     68/push  _test-error-buffered-file/imm32
1092     68/push  _test-buffered-file/imm32
1093     # . . call
1094     e8/call  scan-next-byte/disp32
1095     # registers except ESP may be clobbered at this point
1096     # pop args to scan-next-byte
1097     # . . discard first 2 args
1098     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1099     # . . restore ed
1100     59/pop-to-ECX
1101     # check that scan-next-byte didn't abort
1102     # . check-ints-equal(ed->value, 0, msg)
1103     # . . push args
1104     68/push  "F - test-scan-next-byte-skips-whitespace-and-comment: unexpected abort"/imm32
1105     68/push  0/imm32
1106     # . . push ed->value
1107     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
1108     # . . call
1109     e8/call  check-ints-equal/disp32
1110     # . . discard args
1111     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1112     # return if abort
1113     81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
1114     75/jump-if-not-equal  $test-scan-next-byte-skips-whitespace-and-comment:end/disp8
1115     # check-ints-equal(EAX, 0x61/a, msg)
1116     # . . push args
1117     68/push  "F - test-scan-next-byte-skips-whitespace-and-comment"/imm32
1118     68/push  0x61/imm32/a
1119     50/push-EAX
1120     # . . call
1121     e8/call  check-ints-equal/disp32
1122     # . . discard args
1123     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1124 $test-scan-next-byte-skips-whitespace-and-comment:end:
1125     # . epilog
1126     # don't restore ESP from EBP; manually reclaim locals
1127     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1128     5d/pop-to-EBP
1129     c3/return
1130 
1131 test-scan-next-byte-reads-final-byte:
1132     # - check that the final byte in input is returned
1133     # This test uses exit-descriptors. Use EBP for setting up local variables.
1134     55/push-EBP
1135     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1136     # clear all streams
1137     # . clear-stream(_test-stream)
1138     # . . push args
1139     68/push  _test-stream/imm32
1140     # . . call
1141     e8/call  clear-stream/disp32
1142     # . . discard args
1143     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1144     # . clear-stream(_test-buffered-file+4)
1145     # . . push args
1146     b8/copy-to-EAX  _test-buffered-file/imm32
1147     05/add-to-EAX  4/imm32
1148     50/push-EAX
1149     # . . call
1150     e8/call  clear-stream/disp32
1151     # . . discard args
1152     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1153     # . clear-stream(_test-error-stream)
1154     # . . push args
1155     68/push  _test-error-stream/imm32
1156     # . . call
1157     e8/call  clear-stream/disp32
1158     # . . discard args
1159     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1160     # . clear-stream(_test-error-buffered-file+4)
1161     # . . push args
1162     b8/copy-to-EAX  _test-error-buffered-file/imm32
1163     05/add-to-EAX  4/imm32
1164     50/push-EAX
1165     # . . call
1166     e8/call  clear-stream/disp32
1167     # . . discard args
1168     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1169     # initialize '_test-stream' to input with single character
1170     # . write(_test-stream, character)
1171     # . . push args
1172     68/push  "a"/imm32
1173     68/push  _test-stream/imm32
1174     # . . call
1175     e8/call  write/disp32
1176     # . . discard args
1177     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1178     # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
1179     # . var ed/ECX : exit-descriptor
1180     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
1181     8d/copy-address                 0/mod/indirect  4/rm32/sib    4/base/ESP  4/index/none  .           1/r32/ECX   .               .                 # copy ESP to ECX
1182     # . tailor-exit-descriptor(ed, 12)
1183     # . . push args
1184     68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
1185     51/push-ECX/ed
1186     # . . call
1187     e8/call  tailor-exit-descriptor/disp32
1188     # . . discard args
1189     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1190     # EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
1191     # . . push args
1192     51/push-ECX/ed
1193     68/push  _test-error-buffered-file/imm32
1194     68/push  _test-buffered-file/imm32
1195     # . . call
1196     e8/call  scan-next-byte/disp32
1197     # registers except ESP may be clobbered at this point
1198     # pop args to scan-next-byte
1199     # . . discard first 2 args
1200     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1201     # . . restore ed
1202     59/pop-to-ECX
1203     # check that scan-next-byte didn't abort
1204     # . check-ints-equal(ed->value, 0, msg)
1205     # . . push args
1206     68/push  "F - test-scan-next-byte-reads-final-byte: unexpected abort"/imm32
1207     68/push  0/imm32
1208     # . . push ed->value
1209     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
1210     # . . call
1211     e8/call  check-ints-equal/disp32
1212     # . . discard args
1213     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1214     # return if abort
1215     81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
1216     75/jump-if-not-equal  $test-scan-next-byte-reads-final-byte:end/disp8
1217     # check-ints-equal(EAX, 0x61/a, msg)
1218     # . . push args
1219     68/push  "F - test-scan-next-byte-reads-final-byte"/imm32
1220     68/push  0x61/imm32/a
1221     50/push-EAX
1222     # . . call
1223     e8/call  check-ints-equal/disp32
1224     # . . discard args
1225     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1226 $test-scan-next-byte-reads-final-byte:end:
1227     # . epilog
1228     # don't restore ESP from EBP; manually reclaim locals
1229     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1230     5d/pop-to-EBP
1231     c3/return
1232 
1233 test-scan-next-byte-handles-eof:
1234     # - check that the right sentinel value is returned when there's no data remaining to be read
1235     # This test uses exit-descriptors. Use EBP for setting up local variables.
1236     55/push-EBP
1237     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1238     # clear all streams
1239     # . clear-stream(_test-stream)
1240     # . . push args
1241     68/push  _test-stream/imm32
1242     # . . call
1243     e8/call  clear-stream/disp32
1244     # . . discard args
1245     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1246     # . clear-stream(_test-buffered-file+4)
1247     # . . push args
1248     b8/copy-to-EAX  _test-buffered-file/imm32
1249     05/add-to-EAX  4/imm32
1250     50/push-EAX
1251     # . . call
1252     e8/call  clear-stream/disp32
1253     # . . discard args
1254     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1255     # . clear-stream(_test-error-stream)
1256     # . . push args
1257     68/push  _test-error-stream/imm32
1258     # . . call
1259     e8/call  clear-stream/disp32
1260     # . . discard args
1261     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1262     # . clear-stream(_test-error-buffered-file+4)
1263     # . . push args
1264     b8/copy-to-EAX  _test-error-buffered-file/imm32
1265     05/add-to-EAX  4/imm32
1266     50/push-EAX
1267     # . . call
1268     e8/call  clear-stream/disp32
1269     # . . discard args
1270     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1271     # leave '_test-stream' empty
1272     # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
1273     # . var ed/ECX : exit-descriptor
1274     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
1275     8d/copy-address                 0/mod/indirect  4/rm32/sib    4/base/ESP  4/index/none  .           1/r32/ECX   .               .                 # copy ESP to ECX
1276     # . tailor-exit-descriptor(ed, 12)
1277     # . . push args
1278     68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
1279     51/push-ECX/ed
1280     # . . call
1281     e8/call  tailor-exit-descriptor/disp32
1282     # . . discard args
1283     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1284     # EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
1285     # . . push args
1286     51/push-ECX/ed
1287     68/push  _test-error-buffered-file/imm32
1288     68/push  _test-buffered-file/imm32
1289     # . . call
1290     e8/call  scan-next-byte/disp32
1291     # registers except ESP may be clobbered at this point
1292     # pop args to scan-next-byte
1293     # . . discard first 2 args
1294     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1295     # . . restore ed
1296     59/pop-to-ECX
1297     # check that scan-next-byte didn't abort
1298     # . check-ints-equal(ed->value, 0, msg)
1299     # . . push args
1300     68/push  "F - test-scan-next-byte-handles-eof: unexpected abort"/imm32
1301     68/push  0/imm32
1302     # . . push ed->value
1303     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
1304     # . . call
1305     e8/call  check-ints-equal/disp32
1306     # . . discard args
1307     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1308     # return if abort
1309     81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
1310     75/jump-if-not-equal  $test-scan-next-byte-handles-eof:end/disp8
1311     # check-ints-equal(EAX, 0xffffffff/eof, msg)
1312     # . . push args
1313     68/push  "F - test-scan-next-byte-handles-eof"/imm32
1314     68/push  0xffffffff/imm32/eof
1315     50/push-EAX
1316     # . . call
1317     e8/call  check-ints-equal/disp32
1318     # . . discard args
1319     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1320 $test-scan-next-byte-handles-eof:end:
1321     # . epilog
1322     # don't restore ESP from EBP; manually reclaim locals
1323     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1324     5d/pop-to-EBP
1325     c3/return
1326 
1327 test-scan-next-byte-aborts-on-invalid-byte:
1328     # - check that the a bad byte immediately aborts
1329     # This test uses exit-descriptors. Use EBP for setting up local variables.
1330     55/push-EBP
1331     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1332     # clear all streams
1333     # . clear-stream(_test-stream)
1334     # . . push args
1335     68/push  _test-stream/imm32
1336     # . . call
1337     e8/call  clear-stream/disp32
1338     # . . discard args
1339     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1340     # . clear-stream(_test-buffered-file+4)
1341     # . . push args
1342     b8/copy-to-EAX  _test-buffered-file/imm32
1343     05/add-to-EAX  4/imm32
1344     50/push-EAX
1345     # . . call
1346     e8/call  clear-stream/disp32
1347     # . . discard args
1348     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1349     # . clear-stream(_test-error-stream)
1350     # . . push args
1351     68/push  _test-error-stream/imm32
1352     # . . call
1353     e8/call  clear-stream/disp32
1354     # . . discard args
1355     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1356     # . clear-stream(_test-error-buffered-file+4)
1357     # . . push args
1358     b8/copy-to-EAX  _test-error-buffered-file/imm32
1359     05/add-to-EAX  4/imm32
1360     50/push-EAX
1361     # . . call
1362     e8/call  clear-stream/disp32
1363     # . . discard args
1364     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1365     # initialize '_test-stream' to "x"
1366     # . write(_test-stream, "x")
1367     # . . push args
1368     68/push  "x"/imm32
1369     68/push  _test-stream/imm32
1370     # . . call
1371     e8/call  write/disp32
1372     # . . discard args
1373     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1374     # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
1375     # . var ed/ECX : exit-descriptor
1376     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
1377     8d/copy-address                 0/mod/indirect  4/rm32/sib    4/base/ESP  4/index/none  .           1/r32/ECX   .               .                 # copy ESP to ECX
1378     # . tailor-exit-descriptor(ed, 12)
1379     # . . push args
1380     68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
1381     51/push-ECX/ed
1382     # . . call
1383     e8/call  tailor-exit-descriptor/disp32
1384     # . . discard args
1385     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1386     # EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
1387     # . . push args
1388     51/push-ECX/ed
1389     68/push  _test-error-buffered-file/imm32
1390     68/push  _test-buffered-file/imm32
1391     # . . call
1392     e8/call  scan-next-byte/disp32
1393     # registers except ESP may be clobbered at this point
1394     # pop args to scan-next-byte
1395     # . . discard first 2 args
1396     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1397     # . . restore ed
1398     59/pop-to-ECX
1399     # check that scan-next-byte aborted
1400     # . check-ints-equal(ed->value, 2, msg)
1401     # . . push args
1402     68/push  "F - test-scan-next-byte-aborts-on-invalid-byte"/imm32
1403     68/push  2/imm32
1404     # . . push ed->value
1405     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
1406     # . . call
1407     e8/call  check-ints-equal/disp32
1408     # . . discard args
1409     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1410 $test-scan-next-byte-aborts-on-invalid-byte:end:
1411     # . epilog
1412     # don't restore ESP from EBP; manually reclaim locals
1413     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1414     5d/pop-to-EBP
1415     c3/return
1416 
1417 is-hex-lowercase-byte?:  # c : byte -> bool/EAX
1418     # . prolog
1419     55/push-EBP
1420     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1421     # . save registers
1422     51/push-ECX
1423     # ECX = c
1424     8b/copy                         1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           1/r32/ECX   8/disp8         .                 # copy *(EBP+8) to ECX
1425     # return false if c < '0'
1426     b8/copy-to-EAX  0/imm32/false
1427     81          7/subop/compare     3/mod/direct    1/rm32/ECX    .           .             .           .           .               0x30/imm32        # compare ECX
1428     7c/jump-if-lesser  $is-hex-lowercase-byte?:end/disp8
1429     # return false if c > 'f'
1430     81          7/subop/compare     3/mod/direct    1/rm32/ECX    .           .             .           .           .               0x66/imm32        # compare ECX
1431     7f/jump-if-greater  $is-hex-lowercase-byte?:end/disp8
1432     # return true if c <= '9'
1433     b8/copy-to-EAX  1/imm32/true
1434     81          7/subop/compare     3/mod/direct    1/rm32/ECX    .           .             .           .           .               0x39/imm32        # compare ECX
1435     7e/jump-if-lesser-or-equal  $is-hex-lowercase-byte?:end/disp8
1436     # return true if c >= 'a'
1437     81          7/subop/compare     3/mod/direct    1/rm32/ECX    .           .             .           .           .               0x61/imm32        # compare ECX
1438     7d/jump-if-greater-or-equal  $is-hex-lowercase-byte?:end/disp8
1439     # otherwise return false
1440     b8/copy-to-EAX  0/imm32/false
1441 $is-hex-lowercase-byte?:end:
1442     # . restore registers
1443     59/pop-to-ECX
1444     # . epilog
1445     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1446     5d/pop-to-EBP
1447     c3/return
1448 
1449 test-hex-below-0:
1450     # is-hex-lowercase-byte?(0x2f)
1451     # . . push args
1452     68/push  0x2f/imm32
1453     # . . call
1454     e8/call  is-hex-lowercase-byte?/disp32
1455     # . . discard args
1456     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1457     # check-ints-equal(EAX, 0, msg)
1458     # . . push args
1459     68/push  "F - test-hex-below-0"/imm32
1460     68/push  0/imm32/false
1461     50/push-EAX
1462     # . . call
1463     e8/call  check-ints-equal/disp32
1464     # . . discard args
1465     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1466     c3/return
1467 
1468 test-hex-0-to-9:
1469     # is-hex-lowercase-byte?(0x30)
1470     # . . push args
1471     68/push  0x30/imm32
1472     # . . call
1473     e8/call  is-hex-lowercase-byte?/disp32
1474     # . . discard args
1475     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1476     # check-ints-equal(EAX, 1, msg)
1477     # . . push args
1478     68/push  "F - test-hex-at-0"/imm32
1479     68/push  1/imm32/true
1480     50/push-EAX
1481     # . . call
1482     e8/call  check-ints-equal/disp32
1483     # . . discard args
1484     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1485     # is-hex-lowercase-byte?(0x39)
1486     # . . push args
1487     68/push  0x39/imm32
1488     # . . call
1489     e8/call  is-hex-lowercase-byte?/disp32
1490     # . . discard args
1491     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1492     # check-ints-equal(EAX, 1, msg)
1493     # . . push args
1494     68/push  "F - test-hex-at-9"/imm32
1495     68/push  1/imm32/true
1496     50/push-EAX
1497     # . . call
1498     e8/call  check-ints-equal/disp32
1499     # . . discard args
1500     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1501     c3/return
1502 
1503 test-hex-above-9-to-a:
1504     # is-hex-lowercase-byte?(0x3a)
1505     # . . push args
1506     68/push  0x3a/imm32
1507     # . . call
1508     e8/call  is-hex-lowercase-byte?/disp32
1509     # . . discard args
1510     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1511     # check-ints-equal(EAX, 0, msg)
1512     # . . push args
1513     68/push  "F - test-hex-above-9-to-a"/imm32
1514     68/push  0/imm32/false
1515     50/push-EAX
1516     # . . call
1517     e8/call  check-ints-equal/disp32
1518     # . . discard args
1519     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1520     c3/return
1521 
1522 test-hex-a-to-f:
1523     # is-hex-lowercase-byte?(0x61)
1524     # . . push args
1525     68/push  0x61/imm32
1526     # . . call
1527     e8/call  is-hex-lowercase-byte?/disp32
1528     # . . discard args
1529     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1530     # check-ints-equal(EAX, 1, msg)
1531     # . . push args
1532     68/push  "F - test-hex-at-a"/imm32
1533     68/push  1/imm32/true
1534     50/push-EAX
1535     # . . call
1536     e8/call  check-ints-equal/disp32
1537     # . . discard args
1538     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1539     # is-hex-lowercase-byte?(0x66)
1540     # . . push args
1541     68/push  0x66/imm32
1542     # . . call
1543     e8/call  is-hex-lowercase-byte?/disp32
1544     # . . discard args
1545     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1546     # check-ints-equal(EAX, 1, msg)
1547     # . . push args
1548     68/push  "F - test-hex-at-f"/imm32
1549     68/push  1/imm32/true
1550     50/push-EAX
1551     # . . call
1552     e8/call  check-ints-equal/disp32
1553     # . . discard args
1554     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1555     c3/return
1556 
1557 test-hex-above-f:
1558     # is-hex-lowercase-byte?(0x67)
1559     # . . push args
1560     68/push  0x67/imm32
1561     # . . call
1562     e8/call  is-hex-lowercase-byte?/disp32
1563     # . . discard args
1564     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1565     # check-ints-equal(EAX, 0, msg)
1566     # . . push args
1567     68/push  "F - test-hex-above-f"/imm32
1568     68/push  0/imm32/false
1569     50/push-EAX
1570     # . . call
1571     e8/call  check-ints-equal/disp32
1572     # . . discard args
1573     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1574     c3/return
1575 
1576 parse-hex-digit:  # in/EAX : byte -> out/EAX : num
1577     # no error checking; accepts argument in EAX
1578     # if EAX <= '9' return EAX - '0'
1579     3d/compare-EAX  0x39/imm32/9
1580     7f/jump-if-greater  $parse-hex-digit:else/disp8
1581     2d/subtract-from-EAX  0x30/imm32/0
1582     c3/return
1583 $parse-hex-digit:else:
1584     # otherwise return EAX - 'a' + 10
1585     2d/subtract-from-EAX  0x57/imm32/a-10
1586     c3/return
1587 
1588 skip-until-newline:  # in : (address buffered-file) -> <void>
1589     # pseudocode:
1590     #   push EAX
1591     #   repeatedly:
1592     #     EAX = read-byte(in)
1593     #     if EAX == 0xffffffff break
1594     #     if EAX == 0x0a break
1595     #   pop EAX
1596     # . prolog
1597     55/push-EBP
1598     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1599     # . save registers
1600     50/push-EAX
1601 $skip-until-newline:loop:
1602     # . EAX = read-byte(in)
1603     # . . push args
1604     ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           8/disp8         .                 # push *(EBP+8)
1605     # . . call
1606     e8/call  read-byte/disp32
1607     # . . discard args
1608     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1609     # . if EAX == 0xffffffff break
1610     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0xffffffff/imm32  # compare EAX
1611     74/jump-if-equal  $skip-until-newline:end/disp8
1612     # . if EAX != 0xa/newline loop
1613     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0xa/imm32         # compare EAX
1614     75/jump-if-not-equal  $skip-until-newline:loop/disp8
1615 $skip-until-newline:end:
1616     # . restore registers
1617     58/pop-to-EAX
1618     # . epilog
1619     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1620     5d/pop-to-EBP
1621     c3/return
1622 
1623 test-skip-until-newline:
1624     # - check that the read pointer points after the newline
1625     # setup
1626     # . clear-stream(_test-stream)
1627     # . . push args
1628     68/push  _test-stream/imm32
1629     # . . call
1630     e8/call  clear-stream/disp32
1631     # . . discard args
1632     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1633     # . clear-stream(_test-buffered-file+4)
1634     # . . push args
1635     b8/copy-to-EAX  _test-buffered-file/imm32
1636     05/add-to-EAX  4/imm32
1637     50/push-EAX
1638     # . . call
1639     e8/call  clear-stream/disp32
1640     # . . discard args
1641     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1642     # initialize '_test-stream' to "abc\nde"
1643     # . write(_test-stream, "abc")
1644     # . . push args
1645     68/push  "abc"/imm32
1646     68/push  _test-stream/imm32
1647     # . . call
1648     e8/call  write/disp32
1649     # . . discard args
1650     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1651     # . write(_test-stream, Newline)
1652     # . . push args
1653     68/push  Newline/imm32
1654     68/push  _test-stream/imm32
1655     # . . call
1656     e8/call  write/disp32
1657     # . . discard args
1658     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1659     # . write(_test-stream, "de")
1660     # . . push args
1661     68/push  "de"/imm32
1662     68/push  _test-stream/imm32
1663     # . . call
1664     e8/call  write/disp32
1665     # . . discard args
1666     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1667     # skip-until-newline(_test-buffered-file)
1668     # . . push args
1669     68/push  _test-buffered-file/imm32
1670     # . . call
1671     e8/call  skip-until-newline/disp32
1672     # . . discard args
1673     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1674     # check-ints-equal(_test-buffered-file->read, 4, msg)
1675     # . . push args
1676     68/push  "F - test-skip-until-newline"/imm32
1677     68/push  4/imm32
1678     b8/copy-to-EAX  _test-buffered-file/imm32
1679     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           8/disp8         .                 # push *(EAX+8)
1680     # . . call
1681     e8/call  check-ints-equal/disp32
1682     # . . discard args
1683     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1684     # . end
1685     c3/return
1686 
1687 == data
1688 
1689 _test-error-stream:
1690     # current write index
1691     00 00 00 00
1692     # current read index
1693     00 00 00 00
1694     # length (= 8)
1695     08 00 00 00
1696     # data
1697     00 00 00 00 00 00 00 00  # 8 bytes
1698 
1699 # a test buffered file for _test-stream
1700 _test-error-buffered-file:
1701     # file descriptor or (address stream)
1702     _test-error-stream/imm32
1703     # current write index
1704     00 00 00 00
1705     # current read index
1706     00 00 00 00
1707     # length (6)
1708     06 00 00 00
1709     # data
1710     00 00 00 00 00 00  # 6 bytes
1711 
1712 # . . vim:nowrap:textwidth=0