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