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-skip-until-newline/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  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     #   repeatedly
  76     #     EAX = convert-next-octet(in, err, ed)
  77     #     if (EAX == 0xffffffff) break  # eof
  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 == 0xffffffff) break
  97     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0xffffffff/imm32  # compare EAX
  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 0xffffffff on 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 == 0xffffffff) return
 135     #   ECX = from-hex-char(EAX)
 136     #   EAX = scan-next-byte(in, err, ed)
 137     #   if (EAX == 0xffffffff) 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 == 0xffffffff) return
 157     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0xffffffff/imm32  # compare EAX
 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 == 0xffffffff) error(ed, err, "partial byte found.")
 173     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0xffffffff/imm32  # compare EAX
 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 eof returns the sentinel octet
 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, 0xffffffff, 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 0xffffffff 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     #   repeatedly
 492     #     EAX = read-byte(in)
 493     #     if (EAX == 0xffffffff) 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 == 0xffffffff) return EAX
 512     3d/compare-with-EAX  0xffffffff/imm32
 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     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0x20/imm32        # compare EAX
 533     74/jump-if-equal  $scan-next-byte:loop/disp8
 534     # if (EAX == '\t') continue
 535     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0x9/imm32         # compare EAX
 536     74/jump-if-equal  $scan-next-byte:loop/disp8
 537     # if (EAX == '\n') continue
 538     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0xa/imm32         # compare EAX
 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"/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, Newline)
 820     # . . push args
 821     68/push  Newline/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     # . write(_test-stream, real text)
 828     # . . push args
 829     68/push  "ab"/imm32
 830     68/push  _test-stream/imm32
 831     # . . call
 832     e8/call  write/disp32
 833     # . . discard args
 834     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 835     # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
 836     # . var ed/ECX : exit-descriptor
 837     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
 838     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
 839     # . tailor-exit-descriptor(ed, 12)
 840     # . . push args
 841     68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
 842     51/push-ECX/ed
 843     # . . call
 844     e8/call  tailor-exit-descriptor/disp32
 845     # . . discard args
 846     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 847     # EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
 848     # . . push args
 849     51/push-ECX/ed
 850     68/push  _test-error-buffered-file/imm32
 851     68/push  _test-buffered-file/imm32
 852     # . . call
 853     e8/call  scan-next-byte/disp32
 854     # registers except ESP may be clobbered at this point
 855     # pop args to scan-next-byte
 856     # . . discard first 2 args
 857     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 858     # . . restore ed
 859     59/pop-to-ECX
 860     # check that scan-next-byte didn't abort
 861     # . check-ints-equal(ed->value, 0, msg)
 862     # . . push args
 863     68/push  "F - test-scan-next-byte-skips-comment: unexpected abort"/imm32
 864     68/push  0/imm32
 865     # . . push ed->value
 866     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
 867     # . . call
 868     e8/call  check-ints-equal/disp32
 869     # . . discard args
 870     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 871     # return if abort
 872     81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
 873     75/jump-if-not-equal  $test-scan-next-byte-skips-comment:end/disp8
 874     # check-ints-equal(EAX, 0x61/a, msg)
 875     # . . push args
 876     68/push  "F - test-scan-next-byte-skips-comment"/imm32
 877     68/push  0x61/imm32/a
 878     50/push-EAX
 879     # . . call
 880     e8/call  check-ints-equal/disp32
 881     # . . discard args
 882     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 883 $test-scan-next-byte-skips-comment:end:
 884     # . epilog
 885     # don't restore ESP from EBP; manually reclaim locals
 886     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 887     5d/pop-to-EBP
 888     c3/return
 889 
 890 test-scan-next-byte-skips-comment-and-whitespace:
 891     # - check that the first byte after a comment and any further whitespace is returned
 892     # This test uses exit-descriptors. Use EBP for setting up local variables.
 893     55/push-EBP
 894     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 895     # clear all streams
 896     # . clear-stream(_test-stream)
 897     # . . push args
 898     68/push  _test-stream/imm32
 899     # . . call
 900     e8/call  clear-stream/disp32
 901     # . . discard args
 902     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 903     # . clear-stream(_test-buffered-file+4)
 904     # . . push args
 905     b8/copy-to-EAX  _test-buffered-file/imm32
 906     05/add-to-EAX  4/imm32
 907     50/push-EAX
 908     # . . call
 909     e8/call  clear-stream/disp32
 910     # . . discard args
 911     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 912     # . clear-stream(_test-error-stream)
 913     # . . push args
 914     68/push  _test-error-stream/imm32
 915     # . . call
 916     e8/call  clear-stream/disp32
 917     # . . discard args
 918     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 919     # . clear-stream(_test-error-buffered-file+4)
 920     # . . push args
 921     b8/copy-to-EAX  _test-error-buffered-file/imm32
 922     05/add-to-EAX  4/imm32
 923     50/push-EAX
 924     # . . call
 925     e8/call  clear-stream/disp32
 926     # . . discard args
 927     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 928     # initialize '_test-stream' to input with leading comment and more whitespace after newline
 929     # . write(_test-stream, comment)
 930     # . . push args
 931     68/push  "#x"/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     # . write(_test-stream, Newline)
 938     # . . push args
 939     68/push  Newline/imm32
 940     68/push  _test-stream/imm32
 941     # . . call
 942     e8/call  write/disp32
 943     # . . discard args
 944     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 945     # . write(_test-stream, real text)
 946     # . . push args
 947     68/push  " ab"/imm32
 948     68/push  _test-stream/imm32
 949     # . . call
 950     e8/call  write/disp32
 951     # . . discard args
 952     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 953     # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
 954     # . var ed/ECX : exit-descriptor
 955     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
 956     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
 957     # . tailor-exit-descriptor(ed, 12)
 958     # . . push args
 959     68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
 960     51/push-ECX/ed
 961     # . . call
 962     e8/call  tailor-exit-descriptor/disp32
 963     # . . discard args
 964     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 965     # EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
 966     # . . push args
 967     51/push-ECX/ed
 968     68/push  _test-error-buffered-file/imm32
 969     68/push  _test-buffered-file/imm32
 970     # . . call
 971     e8/call  scan-next-byte/disp32
 972     # registers except ESP may be clobbered at this point
 973     # pop args to scan-next-byte
 974     # . . discard first 2 args
 975     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 976     # . . restore ed
 977     59/pop-to-ECX
 978     # check that scan-next-byte didn't abort
 979     # . check-ints-equal(ed->value, 0, msg)
 980     # . . push args
 981     68/push  "F - test-scan-next-byte-skips-comment-and-whitespace: unexpected abort"/imm32
 982     68/push  0/imm32
 983     # . . push ed->value
 984     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
 985     # . . call
 986     e8/call  check-ints-equal/disp32
 987     # . . discard args
 988     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 989     # return if abort
 990     81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
 991     75/jump-if-not-equal  $test-scan-next-byte-skips-comment-and-whitespace:end/disp8
 992     # check-ints-equal(EAX, 0x61/a, msg)
 993     # . . push args
 994     68/push  "F - test-scan-next-byte-skips-comment-and-whitespace"/imm32
 995     68/push  0x61/imm32/a
 996     50/push-EAX
 997     # . . call
 998     e8/call  check-ints-equal/disp32
 999     # . . discard args
1000     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1001 $test-scan-next-byte-skips-comment-and-whitespace:end:
1002     # . epilog
1003     # don't restore ESP from EBP; manually reclaim locals
1004     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1005     5d/pop-to-EBP
1006     c3/return
1007 
1008 test-scan-next-byte-skips-whitespace-and-comment:
1009     # - check that the first byte after any whitespace and comments is returned
1010     # This test uses exit-descriptors. Use EBP for setting up local variables.
1011     55/push-EBP
1012     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1013     # clear all streams
1014     # . clear-stream(_test-stream)
1015     # . . push args
1016     68/push  _test-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-buffered-file+4)
1022     # . . push args
1023     b8/copy-to-EAX  _test-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     # . clear-stream(_test-error-stream)
1031     # . . push args
1032     68/push  _test-error-stream/imm32
1033     # . . call
1034     e8/call  clear-stream/disp32
1035     # . . discard args
1036     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1037     # . clear-stream(_test-error-buffered-file+4)
1038     # . . push args
1039     b8/copy-to-EAX  _test-error-buffered-file/imm32
1040     05/add-to-EAX  4/imm32
1041     50/push-EAX
1042     # . . call
1043     e8/call  clear-stream/disp32
1044     # . . discard args
1045     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1046     # initialize '_test-stream' to input with leading whitespace and comment
1047     # . write(_test-stream, comment)
1048     # . . push args
1049     68/push  " #x"/imm32
1050     68/push  _test-stream/imm32
1051     # . . call
1052     e8/call  write/disp32
1053     # . . discard args
1054     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1055     # . write(_test-stream, Newline)
1056     # . . push args
1057     68/push  Newline/imm32
1058     68/push  _test-stream/imm32
1059     # . . call
1060     e8/call  write/disp32
1061     # . . discard args
1062     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1063     # . write(_test-stream, real text)
1064     # . . push args
1065     68/push  "ab"/imm32
1066     68/push  _test-stream/imm32
1067     # . . call
1068     e8/call  write/disp32
1069     # . . discard args
1070     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1071     # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
1072     # . var ed/ECX : exit-descriptor
1073     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
1074     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
1075     # . tailor-exit-descriptor(ed, 12)
1076     # . . push args
1077     68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
1078     51/push-ECX/ed
1079     # . . call
1080     e8/call  tailor-exit-descriptor/disp32
1081     # . . discard args
1082     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1083     # EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
1084     # . . push args
1085     51/push-ECX/ed
1086     68/push  _test-error-buffered-file/imm32
1087     68/push  _test-buffered-file/imm32
1088     # . . call
1089     e8/call  scan-next-byte/disp32
1090     # registers except ESP may be clobbered at this point
1091     # pop args to scan-next-byte
1092     # . . discard first 2 args
1093     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1094     # . . restore ed
1095     59/pop-to-ECX
1096     # check that scan-next-byte didn't abort
1097     # . check-ints-equal(ed->value, 0, msg)
1098     # . . push args
1099     68/push  "F - test-scan-next-byte-skips-whitespace-and-comment: unexpected abort"/imm32
1100     68/push  0/imm32
1101     # . . push ed->value
1102     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
1103     # . . call
1104     e8/call  check-ints-equal/disp32
1105     # . . discard args
1106     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1107     # return if abort
1108     81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
1109     75/jump-if-not-equal  $test-scan-next-byte-skips-whitespace-and-comment:end/disp8
1110     # check-ints-equal(EAX, 0x61/a, msg)
1111     # . . push args
1112     68/push  "F - test-scan-next-byte-skips-whitespace-and-comment"/imm32
1113     68/push  0x61/imm32/a
1114     50/push-EAX
1115     # . . call
1116     e8/call  check-ints-equal/disp32
1117     # . . discard args
1118     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1119 $test-scan-next-byte-skips-whitespace-and-comment:end:
1120     # . epilog
1121     # don't restore ESP from EBP; manually reclaim locals
1122     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1123     5d/pop-to-EBP
1124     c3/return
1125 
1126 test-scan-next-byte-reads-final-byte:
1127     # - check that the final byte in input is returned
1128     # This test uses exit-descriptors. Use EBP for setting up local variables.
1129     55/push-EBP
1130     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1131     # clear all streams
1132     # . clear-stream(_test-stream)
1133     # . . push args
1134     68/push  _test-stream/imm32
1135     # . . call
1136     e8/call  clear-stream/disp32
1137     # . . discard args
1138     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1139     # . clear-stream(_test-buffered-file+4)
1140     # . . push args
1141     b8/copy-to-EAX  _test-buffered-file/imm32
1142     05/add-to-EAX  4/imm32
1143     50/push-EAX
1144     # . . call
1145     e8/call  clear-stream/disp32
1146     # . . discard args
1147     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1148     # . clear-stream(_test-error-stream)
1149     # . . push args
1150     68/push  _test-error-stream/imm32
1151     # . . call
1152     e8/call  clear-stream/disp32
1153     # . . discard args
1154     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1155     # . clear-stream(_test-error-buffered-file+4)
1156     # . . push args
1157     b8/copy-to-EAX  _test-error-buffered-file/imm32
1158     05/add-to-EAX  4/imm32
1159     50/push-EAX
1160     # . . call
1161     e8/call  clear-stream/disp32
1162     # . . discard args
1163     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1164     # initialize '_test-stream' to input with single character
1165     # . write(_test-stream, character)
1166     # . . push args
1167     68/push  "a"/imm32
1168     68/push  _test-stream/imm32
1169     # . . call
1170     e8/call  write/disp32
1171     # . . discard args
1172     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1173     # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
1174     # . var ed/ECX : exit-descriptor
1175     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
1176     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
1177     # . tailor-exit-descriptor(ed, 12)
1178     # . . push args
1179     68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
1180     51/push-ECX/ed
1181     # . . call
1182     e8/call  tailor-exit-descriptor/disp32
1183     # . . discard args
1184     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1185     # EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
1186     # . . push args
1187     51/push-ECX/ed
1188     68/push  _test-error-buffered-file/imm32
1189     68/push  _test-buffered-file/imm32
1190     # . . call
1191     e8/call  scan-next-byte/disp32
1192     # registers except ESP may be clobbered at this point
1193     # pop args to scan-next-byte
1194     # . . discard first 2 args
1195     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1196     # . . restore ed
1197     59/pop-to-ECX
1198     # check that scan-next-byte didn't abort
1199     # . check-ints-equal(ed->value, 0, msg)
1200     # . . push args
1201     68/push  "F - test-scan-next-byte-reads-final-byte: unexpected abort"/imm32
1202     68/push  0/imm32
1203     # . . push ed->value
1204     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
1205     # . . call
1206     e8/call  check-ints-equal/disp32
1207     # . . discard args
1208     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1209     # return if abort
1210     81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
1211     75/jump-if-not-equal  $test-scan-next-byte-reads-final-byte:end/disp8
1212     # check-ints-equal(EAX, 0x61/a, msg)
1213     # . . push args
1214     68/push  "F - test-scan-next-byte-reads-final-byte"/imm32
1215     68/push  0x61/imm32/a
1216     50/push-EAX
1217     # . . call
1218     e8/call  check-ints-equal/disp32
1219     # . . discard args
1220     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1221 $test-scan-next-byte-reads-final-byte:end:
1222     # . epilog
1223     # don't restore ESP from EBP; manually reclaim locals
1224     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1225     5d/pop-to-EBP
1226     c3/return
1227 
1228 test-scan-next-byte-handles-eof:
1229     # - check that the right sentinel value is returned when there's no data remaining to be read
1230     # This test uses exit-descriptors. Use EBP for setting up local variables.
1231     55/push-EBP
1232     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1233     # clear all streams
1234     # . clear-stream(_test-stream)
1235     # . . push args
1236     68/push  _test-stream/imm32
1237     # . . call
1238     e8/call  clear-stream/disp32
1239     # . . discard args
1240     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1241     # . clear-stream(_test-buffered-file+4)
1242     # . . push args
1243     b8/copy-to-EAX  _test-buffered-file/imm32
1244     05/add-to-EAX  4/imm32
1245     50/push-EAX
1246     # . . call
1247     e8/call  clear-stream/disp32
1248     # . . discard args
1249     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1250     # . clear-stream(_test-error-stream)
1251     # . . push args
1252     68/push  _test-error-stream/imm32
1253     # . . call
1254     e8/call  clear-stream/disp32
1255     # . . discard args
1256     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1257     # . clear-stream(_test-error-buffered-file+4)
1258     # . . push args
1259     b8/copy-to-EAX  _test-error-buffered-file/imm32
1260     05/add-to-EAX  4/imm32
1261     50/push-EAX
1262     # . . call
1263     e8/call  clear-stream/disp32
1264     # . . discard args
1265     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1266     # leave '_test-stream' empty
1267     # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
1268     # . var ed/ECX : exit-descriptor
1269     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
1270     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
1271     # . tailor-exit-descriptor(ed, 12)
1272     # . . push args
1273     68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
1274     51/push-ECX/ed
1275     # . . call
1276     e8/call  tailor-exit-descriptor/disp32
1277     # . . discard args
1278     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1279     # EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
1280     # . . push args
1281     51/push-ECX/ed
1282     68/push  _test-error-buffered-file/imm32
1283     68/push  _test-buffered-file/imm32
1284     # . . call
1285     e8/call  scan-next-byte/disp32
1286     # registers except ESP may be clobbered at this point
1287     # pop args to scan-next-byte
1288     # . . discard first 2 args
1289     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1290     # . . restore ed
1291     59/pop-to-ECX
1292     # check that scan-next-byte didn't abort
1293     # . check-ints-equal(ed->value, 0, msg)
1294     # . . push args
1295     68/push  "F - test-scan-next-byte-handles-eof: unexpected abort"/imm32
1296     68/push  0/imm32
1297     # . . push ed->value
1298     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
1299     # . . call
1300     e8/call  check-ints-equal/disp32
1301     # . . discard args
1302     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1303     # return if abort
1304     81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
1305     75/jump-if-not-equal  $test-scan-next-byte-handles-eof:end/disp8
1306     # check-ints-equal(EAX, 0xffffffff/eof, msg)
1307     # . . push args
1308     68/push  "F - test-scan-next-byte-handles-eof"/imm32
1309     68/push  0xffffffff/imm32/eof
1310     50/push-EAX
1311     # . . call
1312     e8/call  check-ints-equal/disp32
1313     # . . discard args
1314     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1315 $test-scan-next-byte-handles-eof:end:
1316     # . epilog
1317     # don't restore ESP from EBP; manually reclaim locals
1318     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1319     5d/pop-to-EBP
1320     c3/return
1321 
1322 test-scan-next-byte-aborts-on-invalid-byte:
1323     # - check that the a bad byte immediately aborts
1324     # This test uses exit-descriptors. Use EBP for setting up local variables.
1325     55/push-EBP
1326     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1327     # clear all streams
1328     # . clear-stream(_test-stream)
1329     # . . push args
1330     68/push  _test-stream/imm32
1331     # . . call
1332     e8/call  clear-stream/disp32
1333     # . . discard args
1334     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1335     # . clear-stream(_test-buffered-file+4)
1336     # . . push args
1337     b8/copy-to-EAX  _test-buffered-file/imm32
1338     05/add-to-EAX  4/imm32
1339     50/push-EAX
1340     # . . call
1341     e8/call  clear-stream/disp32
1342     # . . discard args
1343     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1344     # . clear-stream(_test-error-stream)
1345     # . . push args
1346     68/push  _test-error-stream/imm32
1347     # . . call
1348     e8/call  clear-stream/disp32
1349     # . . discard args
1350     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1351     # . clear-stream(_test-error-buffered-file+4)
1352     # . . push args
1353     b8/copy-to-EAX  _test-error-buffered-file/imm32
1354     05/add-to-EAX  4/imm32
1355     50/push-EAX
1356     # . . call
1357     e8/call  clear-stream/disp32
1358     # . . discard args
1359     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1360     # initialize '_test-stream' to "x"
1361     # . write(_test-stream, "x")
1362     # . . push args
1363     68/push  "x"/imm32
1364     68/push  _test-stream/imm32
1365     # . . call
1366     e8/call  write/disp32
1367     # . . discard args
1368     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1369     # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
1370     # . var ed/ECX : exit-descriptor
1371     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
1372     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
1373     # . tailor-exit-descriptor(ed, 12)
1374     # . . push args
1375     68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
1376     51/push-ECX/ed
1377     # . . call
1378     e8/call  tailor-exit-descriptor/disp32
1379     # . . discard args
1380     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1381     # EAX = scan-next-byte(_test-buffered-file, _test-error-buffered-file, ed)
1382     # . . push args
1383     51/push-ECX/ed
1384     68/push  _test-error-buffered-file/imm32
1385     68/push  _test-buffered-file/imm32
1386     # . . call
1387     e8/call  scan-next-byte/disp32
1388     # registers except ESP may be clobbered at this point
1389     # pop args to scan-next-byte
1390     # . . discard first 2 args
1391     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1392     # . . restore ed
1393     59/pop-to-ECX
1394     # check that scan-next-byte aborted
1395     # . check-ints-equal(ed->value, 2, msg)
1396     # . . push args
1397     68/push  "F - test-scan-next-byte-aborts-on-invalid-byte"/imm32
1398     68/push  2/imm32
1399     # . . push ed->value
1400     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
1401     # . . call
1402     e8/call  check-ints-equal/disp32
1403     # . . discard args
1404     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1405 $test-scan-next-byte-aborts-on-invalid-byte:end:
1406     # . epilog
1407     # don't restore ESP from EBP; manually reclaim locals
1408     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1409     5d/pop-to-EBP
1410     c3/return
1411 
1412 skip-until-newline:  # in : (address buffered-file) -> <void>
1413     # pseudocode:
1414     #   push EAX
1415     #   repeatedly:
1416     #     EAX = read-byte(in)
1417     #     if (EAX == 0xffffffff) break
1418     #     if (EAX == 0x0a) break
1419     #   pop EAX
1420     # . prolog
1421     55/push-EBP
1422     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1423     # . save registers
1424     50/push-EAX
1425 $skip-until-newline:loop:
1426     # . EAX = read-byte(in)
1427     # . . push args
1428     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
1429     # . . call
1430     e8/call  read-byte/disp32
1431     # . . discard args
1432     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1433     # . if (EAX == 0xffffffff) break
1434     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0xffffffff/imm32  # compare EAX
1435     74/jump-if-equal  $skip-until-newline:end/disp8
1436     # . if (EAX != 0xa/newline) loop
1437     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0xa/imm32         # compare EAX
1438     75/jump-if-not-equal  $skip-until-newline:loop/disp8
1439 $skip-until-newline:end:
1440     # . restore registers
1441     58/pop-to-EAX
1442     # . epilog
1443     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1444     5d/pop-to-EBP
1445     c3/return
1446 
1447 test-skip-until-newline:
1448     # - check that the read pointer points after the newline
1449     # setup
1450     # . clear-stream(_test-stream)
1451     # . . push args
1452     68/push  _test-stream/imm32
1453     # . . call
1454     e8/call  clear-stream/disp32
1455     # . . discard args
1456     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1457     # . clear-stream(_test-buffered-file+4)
1458     # . . push args
1459     b8/copy-to-EAX  _test-buffered-file/imm32
1460     05/add-to-EAX  4/imm32
1461     50/push-EAX
1462     # . . call
1463     e8/call  clear-stream/disp32
1464     # . . discard args
1465     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1466     # initialize '_test-stream' to "abc\nde"
1467     # . write(_test-stream, "abc")
1468     # . . push args
1469     68/push  "abc"/imm32
1470     68/push  _test-stream/imm32
1471     # . . call
1472     e8/call  write/disp32
1473     # . . discard args
1474     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1475     # . write(_test-stream, Newline)
1476     # . . push args
1477     68/push  Newline/imm32
1478     68/push  _test-stream/imm32
1479     # . . call
1480     e8/call  write/disp32
1481     # . . discard args
1482     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1483     # . write(_test-stream, "de")
1484     # . . push args
1485     68/push  "de"/imm32
1486     68/push  _test-stream/imm32
1487     # . . call
1488     e8/call  write/disp32
1489     # . . discard args
1490     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1491     # skip-until-newline(_test-buffered-file)
1492     # . . push args
1493     68/push  _test-buffered-file/imm32
1494     # . . call
1495     e8/call  skip-until-newline/disp32
1496     # . . discard args
1497     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1498     # check-ints-equal(_test-buffered-file->read, 4, msg)
1499     # . . push args
1500     68/push  "F - test-skip-until-newline"/imm32
1501     68/push  4/imm32
1502     b8/copy-to-EAX  _test-buffered-file/imm32
1503     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           8/disp8         .                 # push *(EAX+8)
1504     # . . call
1505     e8/call  check-ints-equal/disp32
1506     # . . discard args
1507     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1508     # . end
1509     c3/return
1510 
1511 == data
1512 
1513 _test-error-stream:
1514     # current write index
1515     0/imm32
1516     # current read index
1517     0/imm32
1518     # length
1519     8/imm32
1520     # data
1521     00 00 00 00 00 00 00 00  # 8 bytes
1522 
1523 # a test buffered file for _test-stream
1524 _test-error-buffered-file:
1525     # file descriptor or (address stream)
1526     _test-error-stream/imm32
1527     # current write index
1528     0/imm32
1529     # current read index
1530     0/imm32
1531     # length
1532     6/imm32
1533     # data
1534     00 00 00 00 00 00  # 6 bytes
1535 
1536 # . . vim:nowrap:textwidth=0