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