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