https://github.com/akkartik/mu/blob/master/subx/apps/hex.subx
   1 # Read a text file containing whitespace-separated ascii hex bytes from stdin,
   2 # and convert them into a binary file.
   3 #
   4 # To run (from the subx/ directory):
   5 #   $ ./subx translate *.subx apps/hex.subx -o apps/hex
   6 #   $ echo '80 81 82  # comment'  |./subx run apps/hex  |xxd -
   7 # Expected output:
   8 #   00000000: 8081 82
   9 #
  10 # Only hex bytes and comments are permitted. Outside of comments all words
  11 # must be exactly 2 characters long and contain only characters [0-9a-f]. No
  12 # uppercase hex.
  13 
  14 == code
  15 #   instruction                     effective address                                                   register    displacement    immediate
  16 # . op          subop               mod             rm32          base        index         scale       r32
  17 # . 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
  18 
  19     # for debugging: run a single test
  20 #?     e8/call test-skip-until-newline/disp32
  21 #?     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
  22 #?     eb/jump  $main:end/disp8
  23 
  24 # main: run tests if necessary, convert stdin if not
  25     # . prolog
  26     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
  27     # - if argc > 1 and argv[1] == "test" then return run_tests()
  28     # . argc > 1
  29     81          7/subop/compare     1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           0/disp8         1/imm32           # compare *EBP
  30     7e/jump-if-lesser-or-equal  $run-main/disp8
  31     # . argv[1] == "test"
  32     # . . push args
  33     68/push  "test"/imm32
  34     ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           8/disp8         .                 # push *(EBP+8)
  35     # . . call
  36     e8/call  kernel-string-equal/disp32
  37     # . . discard args
  38     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
  39     # . check result
  40     3d/compare-EAX  1/imm32
  41     75/jump-if-not-equal  $run-main/disp8
  42     # . run-tests()
  43 #?     e8/call test-hex-below-0/disp32
  44 #?     e8/call test-scan-next-byte/disp32
  45 #?     e8/call test-scan-next-byte-skips-comment/disp32
  46     e8/call  run-tests/disp32
  47     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
  48     eb/jump  $main:end/disp8
  49 $run-main:
  50     # - otherwise convert stdin
  51     # var ed/EAX : exit-descriptor
  52     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
  53     8d/copy-address                 0/mod/indirect  4/rm32/sib    4/base/ESP  4/index/none  .           0/r32/EAX   .               .                 # copy ESP to EAX
  54     # configure ed to really exit()
  55     # . ed->target = 0
  56     c7/copy                         0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # copy to *EAX
  57     # return convert(Stdin, 1/stdout, 2/stderr, ed)
  58     # . . push args
  59     50/push-EAX/ed
  60     68/push  2/imm32/stderr
  61     68/push  1/imm32/stdout
  62     68/push  Stdin/imm32
  63     # . . call
  64     e8/call  convert/disp32
  65     # . . discard args
  66     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
  67     # . syscall(exit, 0)
  68     bb/copy-to-EBX  0/imm32
  69 $main:end:
  70     b8/copy-to-EAX  1/imm32/exit
  71     cd/syscall  0x80/imm8
  72 
  73 # the main entry point
  74 convert:  # in : (address buffered-file), out : (address buffered-file), err : (address buffered-file), ed : (address exit-descriptor) -> <void>
  75     # . prolog
  76     55/push-EBP
  77     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
  78     # . save registers
  79 $convert:end:
  80     # . restore registers
  81     # . epilog
  82     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
  83     5d/pop-to-EBP
  84     c3/return
  85 
  86 # read bytes from 'in' until a sequence of two lowercase hex characters (0-9, a-f)
  87 # skip spaces and newlines
  88 # on '#' skip bytes until newline
  89 # raise an error and abort on all other unexpected bytes
  90 # return the binary value of the two hex characters in EAX
  91 # return 0xffffffff on end of file
  92 convert-next-hex-byte:  # in : (address buffered-file), err : (address buffered-file), ed : (address exit-descriptor) -> byte-or-eof/EAX
  93     # pseudocode:
  94     #   EAX = scan-next-byte(in, err, ed)
  95     #   if (EAX == 0xffffffff) return
  96     #   ECX = EAX
  97     #   EAX = scan-next-byte(in, err, ed)
  98     #   if (EAX == 0xffffffff) error("partial byte found")
  99     #   ECX = (ECX << 8) | EAX
 100     #   return
 101     #
 102     # . prolog
 103     55/push-EBP
 104     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 105     # . save registers
 106 $convert-next-hex-byte:end:
 107     # . restore registers
 108     # . epilog
 109     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 110     5d/pop-to-EBP
 111     c3/return
 112 
 113 # read whitespace until a hex byte, and return it
 114 # return 0xffffffff if file ends without finding a hex byte
 115 # on '#' skip all bytes until newline
 116 # abort on any other byte
 117 scan-next-byte:  # in : (address buffered-file), err : (address buffered-file), ed : (address exit-descriptor) -> byte-or-eof/EAX
 118     # pseudocode:
 119     #   repeatedly
 120     #     EAX = read-byte(in)
 121     #     if is-hex-lowercase-byte?(EAX) return EAX
 122     #     if EAX == 0x20 continue
 123     #     if EAX == '#' skip-until-newline(in)
 124     #     else error-byte(ed, err, "unexpected byte: " EAX)
 125     #   return 0xffffffff
 126     #
 127     # . prolog
 128     55/push-EBP
 129     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 130     # . save registers
 131 $scan-next-byte:loop:
 132     # EAX = read-byte(in)
 133     # . . push args
 134     ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           8/disp8         .                 # push *(EBP+8)
 135     # . . call
 136     e8/call  read-byte/disp32
 137     # . . discard args
 138     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 139     # if is-hex-lowercase-byte?(EAX) return EAX
 140     # . save EAX for now
 141     50/push-EAX
 142     # . is-hex-lowercase-byte?(EAX)
 143     # . . push args
 144     50/push-EAX
 145     # . . call
 146     e8/call  is-hex-lowercase-byte?/disp32
 147     # . . discard args
 148     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 149     # . compare with 'false'
 150     3d/compare-with-EAX  0/imm32
 151     # . restore EAX (does not affect flags)
 152     58/pop-to-EAX
 153     # . check whether to return
 154     75/jump-if-not-equal  $scan-next-byte:end/disp8
 155 $scan-next-byte:check1:
 156     # if EAX == ' ' continue
 157     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0x20/imm32        # compare EAX
 158     74/jump-if-equal  $scan-next-byte:loop/disp8
 159     # if EAX == '\t' continue
 160     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0x9/imm32         # compare EAX
 161     74/jump-if-equal  $scan-next-byte:loop/disp8
 162     # if EAX == '\n' continue
 163     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0xa/imm32         # compare EAX
 164     74/jump-if-equal  $scan-next-byte:loop/disp8
 165 $scan-next-byte:check2:
 166     # if EAX == '#' skip-until-newline(in)
 167     3d/compare-with-EAX  0x23/imm32
 168     75/jump-if-not-equal  $scan-next-byte:check3/disp8
 169     # . skip-until-newline(in)
 170     # . . push args
 171     ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           8/disp8         .                 # push *(EBP+8)
 172     # . . call
 173     e8/call  skip-until-newline/disp32
 174     # . . discard args
 175     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 176     eb/jump  $scan-next-byte:loop/disp8
 177 $scan-next-byte:check3:
 178 # TODO: error-byte takes a buffered-file, not a (fd or (address stream))
 179     # otherwise error-byte(ed, err, msg, EAX)
 180     # . . push args
 181     50/push-EAX
 182     68/push  "scan-next-byte: invalid byte"/imm32
 183     ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           0xc/disp8       .                 # push *(EBP+12)
 184     ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           0x10/disp8      .                 # push *(EBP+16)
 185     # . . call
 186     e8/call  error-byte/disp32  # never returns
 187 $scan-next-byte:end:
 188     # . restore registers
 189     # . epilog
 190     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 191     5d/pop-to-EBP
 192     c3/return
 193 
 194 test-scan-next-byte:
 195     # - check that the first two bytes of the input are assembled into the resulting number
 196     # This test uses exit-descriptors. Use EBP for setting up local variables.
 197     55/push-EBP
 198     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 199     # clear all streams
 200     # . clear-stream(_test-stream)
 201     # . . push args
 202     68/push  _test-stream/imm32
 203     # . . call
 204     e8/call  clear-stream/disp32
 205     # . . discard args
 206     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 207     # . clear-stream(_test-buffered-file+4)
 208     # . . push args
 209     b8/copy-to-EAX  _test-buffered-file/imm32
 210     05/add-to-EAX  4/imm32
 211     50/push-EAX
 212     # . . call
 213     e8/call  clear-stream/disp32
 214     # . . discard args
 215     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 216     # . clear-stream(_test-error-stream)
 217     # . . push args
 218     68/push  _test-error-stream/imm32
 219     # . . call
 220     e8/call  clear-stream/disp32
 221     # . . discard args
 222     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 223     # initialize '_test-stream' to "abc"
 224     # . write(_test-stream, "abc")
 225     # . . push args
 226     68/push  "abc"/imm32
 227     68/push  _test-stream/imm32
 228     # . . call
 229     e8/call  write/disp32
 230     # . . discard args
 231     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 232     # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
 233     # . var ed/ECX : exit-descriptor
 234     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
 235     8d/copy-address                 0/mod/indirect  4/rm32/sib    4/base/ESP  4/index/none  .           1/r32/ECX   .               .                 # copy ESP to ECX
 236     # . tailor-exit-descriptor(ed, 12)
 237     # . . push args
 238     68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
 239     51/push-ECX/ed
 240     # . . call
 241     e8/call  tailor-exit-descriptor/disp32
 242     # . . discard args
 243     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 244     # EAX = scan-next-byte(_test-buffered-file, _test-error-stream, ed)
 245     # . . push args
 246     51/push-ECX/ed
 247     68/push  _test-error-stream/imm32
 248     68/push  _test-buffered-file/imm32
 249     # . . call
 250     e8/call  scan-next-byte/disp32
 251     # registers except ESP may be clobbered at this point
 252     # pop args to scan-next-bytes
 253     # . . discard first 2 args
 254     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 255     # . . restore ed
 256     59/pop-to-ECX
 257     # check that scan-next-byte didn't abort
 258     # . check-ints-equal(ed->value, 0, msg)
 259     # . . push args
 260     68/push  "F - test-scan-next-byte: unexpected abort"/imm32
 261     68/push  0/imm32
 262     # . . push ed->value
 263     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
 264     # . . call
 265     e8/call  check-ints-equal/disp32
 266     # . . discard args
 267     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 268     # return if abort
 269     81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
 270     75/jump-if-not-equal  $test-scan-next-byte:end/disp8
 271     # check-ints-equal(EAX, 0x61/a, msg)
 272     # . . push args
 273     68/push  "F - test-scan-next-byte"/imm32
 274     68/push  0x61/imm32/a
 275     50/push-EAX
 276     # . . call
 277     e8/call  check-ints-equal/disp32
 278     # . . discard args
 279     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 280 $test-scan-next-byte:end:
 281     # . epilog
 282     # don't restore ESP from EBP; manually reclaim locals
 283     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 284     5d/pop-to-EBP
 285     c3/return
 286 
 287 test-scan-next-byte-skips-whitespace:
 288     # - check that the first two bytes of the input are assembled into the resulting number
 289     # This test uses exit-descriptors. Use EBP for setting up local variables.
 290     55/push-EBP
 291     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 292     # clear all streams
 293     # . clear-stream(_test-stream)
 294     # . . push args
 295     68/push  _test-stream/imm32
 296     # . . call
 297     e8/call  clear-stream/disp32
 298     # . . discard args
 299     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 300     # . clear-stream(_test-buffered-file+4)
 301     # . . push args
 302     b8/copy-to-EAX  _test-buffered-file/imm32
 303     05/add-to-EAX  4/imm32
 304     50/push-EAX
 305     # . . call
 306     e8/call  clear-stream/disp32
 307     # . . discard args
 308     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 309     # . clear-stream(_test-error-stream)
 310     # . . push args
 311     68/push  _test-error-stream/imm32
 312     # . . call
 313     e8/call  clear-stream/disp32
 314     # . . discard args
 315     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 316     # initialize '_test-stream' to input with leading whitespace
 317     # . write(_test-stream, text)
 318     # . . push args
 319     68/push  "  abc"/imm32
 320     68/push  _test-stream/imm32
 321     # . . call
 322     e8/call  write/disp32
 323     # . . discard args
 324     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 325     # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
 326     # . var ed/ECX : exit-descriptor
 327     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
 328     8d/copy-address                 0/mod/indirect  4/rm32/sib    4/base/ESP  4/index/none  .           1/r32/ECX   .               .                 # copy ESP to ECX
 329     # . tailor-exit-descriptor(ed, 12)
 330     # . . push args
 331     68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
 332     51/push-ECX/ed
 333     # . . call
 334     e8/call  tailor-exit-descriptor/disp32
 335     # . . discard args
 336     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 337     # EAX = scan-next-byte(_test-buffered-file, _test-error-stream, ed)
 338     # . . push args
 339     51/push-ECX/ed
 340     68/push  _test-error-stream/imm32
 341     68/push  _test-buffered-file/imm32
 342     # . . call
 343     e8/call  scan-next-byte/disp32
 344     # registers except ESP may be clobbered at this point
 345     # pop args to scan-next-bytes
 346     # . . discard first 2 args
 347     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 348     # . . restore ed
 349     59/pop-to-ECX
 350     # check that scan-next-byte didn't abort
 351     # . check-ints-equal(ed->value, 0, msg)
 352     # . . push args
 353     68/push  "F - test-scan-next-byte-skips-whitespace: unexpected abort"/imm32
 354     68/push  0/imm32
 355     # . . push ed->value
 356     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
 357     # . . call
 358     e8/call  check-ints-equal/disp32
 359     # . . discard args
 360     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 361     # return if abort
 362     81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
 363     75/jump-if-not-equal  $test-scan-next-byte-skips-whitespace:end/disp8
 364     # check-ints-equal(EAX, 0x61/a, msg)
 365     # . . push args
 366     68/push  "F - test-scan-next-byte-skips-whitespace"/imm32
 367     68/push  0x61/imm32/a
 368     50/push-EAX
 369     # . . call
 370     e8/call  check-ints-equal/disp32
 371     # . . discard args
 372     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 373 $test-scan-next-byte-skips-whitespace:end:
 374     # . epilog
 375     # don't restore ESP from EBP; manually reclaim locals
 376     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 377     5d/pop-to-EBP
 378     c3/return
 379 
 380 test-scan-next-byte-skips-comment:
 381     # - check that the first two bytes of the input are assembled into the resulting number
 382     # This test uses exit-descriptors. Use EBP for setting up local variables.
 383     55/push-EBP
 384     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 385     # clear all streams
 386     # . clear-stream(_test-stream)
 387     # . . push args
 388     68/push  _test-stream/imm32
 389     # . . call
 390     e8/call  clear-stream/disp32
 391     # . . discard args
 392     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 393     # . clear-stream(_test-buffered-file+4)
 394     # . . push args
 395     b8/copy-to-EAX  _test-buffered-file/imm32
 396     05/add-to-EAX  4/imm32
 397     50/push-EAX
 398     # . . call
 399     e8/call  clear-stream/disp32
 400     # . . discard args
 401     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 402     # . clear-stream(_test-error-stream)
 403     # . . push args
 404     68/push  _test-error-stream/imm32
 405     # . . call
 406     e8/call  clear-stream/disp32
 407     # . . discard args
 408     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 409     # initialize '_test-stream' to input with leading comment
 410     # . write(_test-stream, comment)
 411     # . . push args
 412     68/push  "#x"/imm32
 413     68/push  _test-stream/imm32
 414     # . . call
 415     e8/call  write/disp32
 416     # . . discard args
 417     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 418     # . write(_test-stream, Newline)
 419     # . . push args
 420     68/push  Newline/imm32
 421     68/push  _test-stream/imm32
 422     # . . call
 423     e8/call  write/disp32
 424     # . . discard args
 425     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 426     # . write(_test-stream, real text)
 427     # . . push args
 428     68/push  "ab"/imm32
 429     68/push  _test-stream/imm32
 430     # . . call
 431     e8/call  write/disp32
 432     # . . discard args
 433     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 434     # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
 435     # . var ed/ECX : exit-descriptor
 436     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
 437     8d/copy-address                 0/mod/indirect  4/rm32/sib    4/base/ESP  4/index/none  .           1/r32/ECX   .               .                 # copy ESP to ECX
 438     # . tailor-exit-descriptor(ed, 12)
 439     # . . push args
 440     68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
 441     51/push-ECX/ed
 442     # . . call
 443     e8/call  tailor-exit-descriptor/disp32
 444     # . . discard args
 445     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 446     # EAX = scan-next-byte(_test-buffered-file, _test-error-stream, ed)
 447     # . . push args
 448     51/push-ECX/ed
 449     68/push  _test-error-stream/imm32
 450     68/push  _test-buffered-file/imm32
 451     # . . call
 452     e8/call  scan-next-byte/disp32
 453     # registers except ESP may be clobbered at this point
 454     # pop args to scan-next-bytes
 455     # . . discard first 2 args
 456     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 457     # . . restore ed
 458     59/pop-to-ECX
 459     # check that scan-next-byte didn't abort
 460     # . check-ints-equal(ed->value, 0, msg)
 461     # . . push args
 462     68/push  "F - test-scan-next-byte-skips-comment: unexpected abort"/imm32
 463     68/push  0/imm32
 464     # . . push ed->value
 465     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
 466     # . . call
 467     e8/call  check-ints-equal/disp32
 468     # . . discard args
 469     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 470     # return if abort
 471     81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
 472     75/jump-if-not-equal  $test-scan-next-byte-skips-comment:end/disp8
 473     # check-ints-equal(EAX, 0x61/a, msg)
 474     # . . push args
 475     68/push  "F - test-scan-next-byte-skips-comment"/imm32
 476     68/push  0x61/imm32/a
 477     50/push-EAX
 478     # . . call
 479     e8/call  check-ints-equal/disp32
 480     # . . discard args
 481     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 482 $test-scan-next-byte-skips-comment:end:
 483     # . epilog
 484     # don't restore ESP from EBP; manually reclaim locals
 485     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 486     5d/pop-to-EBP
 487     c3/return
 488 
 489 test-scan-next-byte-skips-comment-and-whitespace:
 490     # - check that the first two bytes of the input are assembled into the resulting number
 491     # This test uses exit-descriptors. Use EBP for setting up local variables.
 492     55/push-EBP
 493     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 494     # clear all streams
 495     # . clear-stream(_test-stream)
 496     # . . push args
 497     68/push  _test-stream/imm32
 498     # . . call
 499     e8/call  clear-stream/disp32
 500     # . . discard args
 501     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 502     # . clear-stream(_test-buffered-file+4)
 503     # . . push args
 504     b8/copy-to-EAX  _test-buffered-file/imm32
 505     05/add-to-EAX  4/imm32
 506     50/push-EAX
 507     # . . call
 508     e8/call  clear-stream/disp32
 509     # . . discard args
 510     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 511     # . clear-stream(_test-error-stream)
 512     # . . push args
 513     68/push  _test-error-stream/imm32
 514     # . . call
 515     e8/call  clear-stream/disp32
 516     # . . discard args
 517     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 518     # initialize '_test-stream' to input with leading comment and more whitespace after newline
 519     # . write(_test-stream, comment)
 520     # . . push args
 521     68/push  "#x"/imm32
 522     68/push  _test-stream/imm32
 523     # . . call
 524     e8/call  write/disp32
 525     # . . discard args
 526     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 527     # . write(_test-stream, Newline)
 528     # . . push args
 529     68/push  Newline/imm32
 530     68/push  _test-stream/imm32
 531     # . . call
 532     e8/call  write/disp32
 533     # . . discard args
 534     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 535     # . write(_test-stream, real text)
 536     # . . push args
 537     68/push  " ab"/imm32
 538     68/push  _test-stream/imm32
 539     # . . call
 540     e8/call  write/disp32
 541     # . . discard args
 542     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 543     # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
 544     # . var ed/ECX : exit-descriptor
 545     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
 546     8d/copy-address                 0/mod/indirect  4/rm32/sib    4/base/ESP  4/index/none  .           1/r32/ECX   .               .                 # copy ESP to ECX
 547     # . tailor-exit-descriptor(ed, 12)
 548     # . . push args
 549     68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
 550     51/push-ECX/ed
 551     # . . call
 552     e8/call  tailor-exit-descriptor/disp32
 553     # . . discard args
 554     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 555     # EAX = scan-next-byte(_test-buffered-file, _test-error-stream, ed)
 556     # . . push args
 557     51/push-ECX/ed
 558     68/push  _test-error-stream/imm32
 559     68/push  _test-buffered-file/imm32
 560     # . . call
 561     e8/call  scan-next-byte/disp32
 562     # registers except ESP may be clobbered at this point
 563     # pop args to scan-next-bytes
 564     # . . discard first 2 args
 565     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 566     # . . restore ed
 567     59/pop-to-ECX
 568     # check that scan-next-byte didn't abort
 569     # . check-ints-equal(ed->value, 0, msg)
 570     # . . push args
 571     68/push  "F - test-scan-next-byte-skips-comment-and-whitespace: unexpected abort"/imm32
 572     68/push  0/imm32
 573     # . . push ed->value
 574     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
 575     # . . call
 576     e8/call  check-ints-equal/disp32
 577     # . . discard args
 578     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 579     # return if abort
 580     81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
 581     75/jump-if-not-equal  $test-scan-next-byte-skips-comment-and-whitespace:end/disp8
 582     # check-ints-equal(EAX, 0x61/a, msg)
 583     # . . push args
 584     68/push  "F - test-scan-next-byte-skips-comment-and-whitespace"/imm32
 585     68/push  0x61/imm32/a
 586     50/push-EAX
 587     # . . call
 588     e8/call  check-ints-equal/disp32
 589     # . . discard args
 590     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 591 $test-scan-next-byte-skips-comment-and-whitespace:end:
 592     # . epilog
 593     # don't restore ESP from EBP; manually reclaim locals
 594     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 595     5d/pop-to-EBP
 596     c3/return
 597 
 598 test-scan-next-byte-skips-whitespace-and-comment:
 599     # - check that the first two bytes of the input are assembled into the resulting number
 600     # This test uses exit-descriptors. Use EBP for setting up local variables.
 601     55/push-EBP
 602     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 603     # clear all streams
 604     # . clear-stream(_test-stream)
 605     # . . push args
 606     68/push  _test-stream/imm32
 607     # . . call
 608     e8/call  clear-stream/disp32
 609     # . . discard args
 610     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 611     # . clear-stream(_test-buffered-file+4)
 612     # . . push args
 613     b8/copy-to-EAX  _test-buffered-file/imm32
 614     05/add-to-EAX  4/imm32
 615     50/push-EAX
 616     # . . call
 617     e8/call  clear-stream/disp32
 618     # . . discard args
 619     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 620     # . clear-stream(_test-error-stream)
 621     # . . push args
 622     68/push  _test-error-stream/imm32
 623     # . . call
 624     e8/call  clear-stream/disp32
 625     # . . discard args
 626     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 627     # initialize '_test-stream' to input with leading whitespace and comment
 628     # . write(_test-stream, comment)
 629     # . . push args
 630     68/push  " #x"/imm32
 631     68/push  _test-stream/imm32
 632     # . . call
 633     e8/call  write/disp32
 634     # . . discard args
 635     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 636     # . write(_test-stream, Newline)
 637     # . . push args
 638     68/push  Newline/imm32
 639     68/push  _test-stream/imm32
 640     # . . call
 641     e8/call  write/disp32
 642     # . . discard args
 643     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 644     # . write(_test-stream, real text)
 645     # . . push args
 646     68/push  "ab"/imm32
 647     68/push  _test-stream/imm32
 648     # . . call
 649     e8/call  write/disp32
 650     # . . discard args
 651     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 652     # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
 653     # . var ed/ECX : exit-descriptor
 654     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
 655     8d/copy-address                 0/mod/indirect  4/rm32/sib    4/base/ESP  4/index/none  .           1/r32/ECX   .               .                 # copy ESP to ECX
 656     # . tailor-exit-descriptor(ed, 12)
 657     # . . push args
 658     68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
 659     51/push-ECX/ed
 660     # . . call
 661     e8/call  tailor-exit-descriptor/disp32
 662     # . . discard args
 663     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 664     # EAX = scan-next-byte(_test-buffered-file, _test-error-stream, ed)
 665     # . . push args
 666     51/push-ECX/ed
 667     68/push  _test-error-stream/imm32
 668     68/push  _test-buffered-file/imm32
 669     # . . call
 670     e8/call  scan-next-byte/disp32
 671     # registers except ESP may be clobbered at this point
 672     # pop args to scan-next-bytes
 673     # . . discard first 2 args
 674     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 675     # . . restore ed
 676     59/pop-to-ECX
 677     # check that scan-next-byte didn't abort
 678     # . check-ints-equal(ed->value, 0, msg)
 679     # . . push args
 680     68/push  "F - test-scan-next-byte-skips-whitespace-and-comment: unexpected abort"/imm32
 681     68/push  0/imm32
 682     # . . push ed->value
 683     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
 684     # . . call
 685     e8/call  check-ints-equal/disp32
 686     # . . discard args
 687     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 688     # return if abort
 689     81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
 690     75/jump-if-not-equal  $test-scan-next-byte-skips-whitespace-and-comment:end/disp8
 691     # check-ints-equal(EAX, 0x61/a, msg)
 692     # . . push args
 693     68/push  "F - test-scan-next-byte-skips-whitespace-and-comment"/imm32
 694     68/push  0x61/imm32/a
 695     50/push-EAX
 696     # . . call
 697     e8/call  check-ints-equal/disp32
 698     # . . discard args
 699     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 700 $test-scan-next-byte-skips-whitespace-and-comment:end:
 701     # . epilog
 702     # don't restore ESP from EBP; manually reclaim locals
 703     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 704     5d/pop-to-EBP
 705     c3/return
 706 
 707 test-scan-next-byte-reads-final-byte:
 708     # - check that the first two bytes of the input are assembled into the resulting number
 709     # This test uses exit-descriptors. Use EBP for setting up local variables.
 710     55/push-EBP
 711     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 712     # clear all streams
 713     # . clear-stream(_test-stream)
 714     # . . push args
 715     68/push  _test-stream/imm32
 716     # . . call
 717     e8/call  clear-stream/disp32
 718     # . . discard args
 719     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 720     # . clear-stream(_test-buffered-file+4)
 721     # . . push args
 722     b8/copy-to-EAX  _test-buffered-file/imm32
 723     05/add-to-EAX  4/imm32
 724     50/push-EAX
 725     # . . call
 726     e8/call  clear-stream/disp32
 727     # . . discard args
 728     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 729     # . clear-stream(_test-error-stream)
 730     # . . push args
 731     68/push  _test-error-stream/imm32
 732     # . . call
 733     e8/call  clear-stream/disp32
 734     # . . discard args
 735     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 736     # initialize '_test-stream' to input with single character
 737     # . write(_test-stream, character)
 738     # . . push args
 739     68/push  "a"/imm32
 740     68/push  _test-stream/imm32
 741     # . . call
 742     e8/call  write/disp32
 743     # . . discard args
 744     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 745     # initialize exit-descriptor 'ed' for the call to 'scan-next-byte' below
 746     # . var ed/ECX : exit-descriptor
 747     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
 748     8d/copy-address                 0/mod/indirect  4/rm32/sib    4/base/ESP  4/index/none  .           1/r32/ECX   .               .                 # copy ESP to ECX
 749     # . tailor-exit-descriptor(ed, 12)
 750     # . . push args
 751     68/push  0xc/imm32/nbytes-of-args-for-scan-next-byte
 752     51/push-ECX/ed
 753     # . . call
 754     e8/call  tailor-exit-descriptor/disp32
 755     # . . discard args
 756     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 757     # EAX = scan-next-byte(_test-buffered-file, _test-error-stream, ed)
 758     # . . push args
 759     51/push-ECX/ed
 760     68/push  _test-error-stream/imm32
 761     68/push  _test-buffered-file/imm32
 762     # . . call
 763     e8/call  scan-next-byte/disp32
 764     # registers except ESP may be clobbered at this point
 765     # pop args to scan-next-bytes
 766     # . . discard first 2 args
 767     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 768     # . . restore ed
 769     59/pop-to-ECX
 770     # check that scan-next-byte didn't abort
 771     # . check-ints-equal(ed->value, 0, msg)
 772     # . . push args
 773     68/push  "F - test-scan-next-byte-reads-final-byte: unexpected abort"/imm32
 774     68/push  0/imm32
 775     # . . push ed->value
 776     ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(ECX+4)
 777     # . . call
 778     e8/call  check-ints-equal/disp32
 779     # . . discard args
 780     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 781     # return if abort
 782     81          7/subop/compare     1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         0/imm32           # compare *(ECX+4)
 783     75/jump-if-not-equal  $test-scan-next-byte-reads-final-byte:end/disp8
 784     # check-ints-equal(EAX, 0x61/a, msg)
 785     # . . push args
 786     68/push  "F - test-scan-next-byte-reads-final-byte"/imm32
 787     68/push  0x61/imm32/a
 788     50/push-EAX
 789     # . . call
 790     e8/call  check-ints-equal/disp32
 791     # . . discard args
 792     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 793 $test-scan-next-byte-reads-final-byte:end:
 794     # . epilog
 795     # don't restore ESP from EBP; manually reclaim locals
 796     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 797     5d/pop-to-EBP
 798     c3/return
 799 
 800 is-hex-lowercase-byte?:  # c : byte -> bool/EAX
 801     # . prolog
 802     55/push-EBP
 803     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 804     # . save registers
 805     51/push-ECX
 806     # ECX = c
 807     8b/copy                         1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           1/r32/ECX   8/disp8         .                 # copy *(EBP+8) to ECX
 808     # return false if c < '0'
 809     b8/copy-to-EAX  0/imm32/false
 810     81          7/subop/compare     3/mod/direct    1/rm32/ECX    .           .             .           .           .               0x30/imm32        # compare ECX
 811     7c/jump-if-lesser  $is-hex-lowercase-byte?:end/disp8
 812     # return false if c > 'f'
 813     81          7/subop/compare     3/mod/direct    1/rm32/ECX    .           .             .           .           .               0x66/imm32        # compare ECX
 814     7f/jump-if-greater  $is-hex-lowercase-byte?:end/disp8
 815     # return true if c <= '9'
 816     b8/copy-to-EAX  1/imm32/true
 817     81          7/subop/compare     3/mod/direct    1/rm32/ECX    .           .             .           .           .               0x39/imm32        # compare ECX
 818     7e/jump-if-lesser-or-equal  $is-hex-lowercase-byte?:end/disp8
 819     # return true if c >= 'a'
 820     81          7/subop/compare     3/mod/direct    1/rm32/ECX    .           .             .           .           .               0x61/imm32        # compare ECX
 821     7d/jump-if-greater-or-equal  $is-hex-lowercase-byte?:end/disp8
 822     # otherwise return false
 823     b8/copy-to-EAX  0/imm32/false
 824 $is-hex-lowercase-byte?:end:
 825     # . restore registers
 826     59/pop-to-ECX
 827     # . epilog
 828     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 829     5d/pop-to-EBP
 830     c3/return
 831 
 832 test-hex-below-0:
 833     # is-hex-lowercase-byte?(0x2f)
 834     # . . push args
 835     68/push  0x2f/imm32
 836     # . . call
 837     e8/call  is-hex-lowercase-byte?/disp32
 838     # . . discard args
 839     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 840     # check-ints-equal(EAX, 0, msg)
 841     # . . push args
 842     68/push  "F - test-hex-below-0"/imm32
 843     68/push  0/imm32/false
 844     50/push-EAX
 845     # . . call
 846     e8/call  check-ints-equal/disp32
 847     # . . discard args
 848     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 849     c3/return
 850 
 851 test-hex-0-to-9:
 852     # is-hex-lowercase-byte?(0x30)
 853     # . . push args
 854     68/push  0x30/imm32
 855     # . . call
 856     e8/call  is-hex-lowercase-byte?/disp32
 857     # . . discard args
 858     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 859     # check-ints-equal(EAX, 1, msg)
 860     # . . push args
 861     68/push  "F - test-hex-at-0"/imm32
 862     68/push  1/imm32/true
 863     50/push-EAX
 864     # . . call
 865     e8/call  check-ints-equal/disp32
 866     # . . discard args
 867     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 868     # is-hex-lowercase-byte?(0x39)
 869     # . . push args
 870     68/push  0x39/imm32
 871     # . . call
 872     e8/call  is-hex-lowercase-byte?/disp32
 873     # . . discard args
 874     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 875     # check-ints-equal(EAX, 1, msg)
 876     # . . push args
 877     68/push  "F - test-hex-at-9"/imm32
 878     68/push  1/imm32/true
 879     50/push-EAX
 880     # . . call
 881     e8/call  check-ints-equal/disp32
 882     # . . discard args
 883     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 884     c3/return
 885 
 886 test-hex-above-9-to-a:
 887     # is-hex-lowercase-byte?(0x3a)
 888     # . . push args
 889     68/push  0x3a/imm32
 890     # . . call
 891     e8/call  is-hex-lowercase-byte?/disp32
 892     # . . discard args
 893     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 894     # check-ints-equal(EAX, 0, msg)
 895     # . . push args
 896     68/push  "F - test-hex-above-9-to-a"/imm32
 897     68/push  0/imm32/false
 898     50/push-EAX
 899     # . . call
 900     e8/call  check-ints-equal/disp32
 901     # . . discard args
 902     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 903     c3/return
 904 
 905 test-hex-a-to-f:
 906     # is-hex-lowercase-byte?(0x61)
 907     # . . push args
 908     68/push  0x61/imm32
 909     # . . call
 910     e8/call  is-hex-lowercase-byte?/disp32
 911     # . . discard args
 912     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 913     # check-ints-equal(EAX, 1, msg)
 914     # . . push args
 915     68/push  "F - test-hex-at-a"/imm32
 916     68/push  1/imm32/true
 917     50/push-EAX
 918     # . . call
 919     e8/call  check-ints-equal/disp32
 920     # . . discard args
 921     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 922     # is-hex-lowercase-byte?(0x66)
 923     # . . push args
 924     68/push  0x66/imm32
 925     # . . call
 926     e8/call  is-hex-lowercase-byte?/disp32
 927     # . . discard args
 928     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 929     # check-ints-equal(EAX, 1, msg)
 930     # . . push args
 931     68/push  "F - test-hex-at-f"/imm32
 932     68/push  1/imm32/true
 933     50/push-EAX
 934     # . . call
 935     e8/call  check-ints-equal/disp32
 936     # . . discard args
 937     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 938     c3/return
 939 
 940 test-hex-above-f:
 941     # is-hex-lowercase-byte?(0x67)
 942     # . . push args
 943     68/push  0x67/imm32
 944     # . . call
 945     e8/call  is-hex-lowercase-byte?/disp32
 946     # . . discard args
 947     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 948     # check-ints-equal(EAX, 0, msg)
 949     # . . push args
 950     68/push  "F - test-hex-above-f"/imm32
 951     68/push  0/imm32/false
 952     50/push-EAX
 953     # . . call
 954     e8/call  check-ints-equal/disp32
 955     # . . discard args
 956     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 957     c3/return
 958 
 959 skip-until-newline:  # in : (address buffered-file) -> <void>
 960     # pseudocode:
 961     #   push EAX
 962     #   repeatedly:
 963     #     EAX = read-byte(in)
 964     #     if EAX == 0xffffffff break
 965     #     if EAX == 0x0a break
 966     #   pop EAX
 967     # . prolog
 968     55/push-EBP
 969     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 970     # . save registers
 971     50/push-EAX
 972 $skip-until-newline:loop:
 973     # . EAX = read-byte(in)
 974     # . . push args
 975     ff          6/subop/push        1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none  .           .           8/disp8         .                 # push *(EBP+8)
 976     # . . call
 977     e8/call  read-byte/disp32
 978     # . . discard args
 979     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 980     # . if EAX == 0xffffffff break
 981     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0xffffffff/imm32  # compare EAX
 982     74/jump-if-equal  $skip-until-newline:end/disp8
 983 $aa:
 984     # . if EAX != 0xa/newline loop
 985     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0xa/imm32         # compare EAX
 986     75/jump-if-not-equal  $skip-until-newline:loop/disp8
 987 $skip-until-newline:end:
 988     # . restore registers
 989     58/pop-to-EAX
 990     # . epilog
 991     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 992     5d/pop-to-EBP
 993     c3/return
 994 
 995 test-skip-until-newline:
 996     # - check that the read pointer points after the newline
 997     # setup
 998     # . clear-stream(_test-stream)
 999     # . . push args
1000     68/push  _test-stream/imm32
1001     # . . call
1002     e8/call  clear-stream/disp32
1003     # . . discard args
1004     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1005     # . clear-stream(_test-buffered-file+4)
1006     # . . push args
1007     b8/copy-to-EAX  _test-buffered-file/imm32
1008     05/add-to-EAX  4/imm32
1009     50/push-EAX
1010     # . . call
1011     e8/call  clear-stream/disp32
1012     # . . discard args
1013     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1014     # initialize '_test-stream' to "abc\nde"
1015     # . write(_test-stream, "abc")
1016     # . . push args
1017     68/push  "abc"/imm32
1018     68/push  _test-stream/imm32
1019     # . . call
1020     e8/call  write/disp32
1021     # . . discard args
1022     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1023     # . write(_test-stream, Newline)
1024     # . . push args
1025     68/push  Newline/imm32
1026     68/push  _test-stream/imm32
1027     # . . call
1028     e8/call  write/disp32
1029     # . . discard args
1030     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1031     # . write(_test-stream, "de")
1032     # . . push args
1033     68/push  "de"/imm32
1034     68/push  _test-stream/imm32
1035     # . . call
1036     e8/call  write/disp32
1037     # . . discard args
1038     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1039     # skip-until-newline(_test-buffered-file)
1040     # . . push args
1041     68/push  _test-buffered-file/imm32
1042     # . . call
1043     e8/call  skip-until-newline/disp32
1044     # . . discard args
1045     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1046     # check-ints-equal(_test-buffered-file->read, 4, msg)
1047     # . . push args
1048     68/push  "F - test-skip-until-newline"/imm32
1049     68/push  4/imm32
1050     b8/copy-to-EAX  _test-buffered-file/imm32
1051     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           8/disp8         .                 # push *(EAX+8)
1052     # . . call
1053     e8/call  check-ints-equal/disp32
1054     # . . discard args
1055     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1056     # . end
1057     c3/return
1058 
1059 == data
1060 
1061 _test-output-stream:
1062     # current write index
1063     00 00 00 00
1064     # current read index
1065     00 00 00 00
1066     # length (= 8)
1067     08 00 00 00
1068     # data
1069     00 00 00 00 00 00 00 00  # 8 bytes
1070 
1071 _test-error-stream:
1072     # current write index
1073     00 00 00 00
1074     # current read index
1075     00 00 00 00
1076     # length (= 8)
1077     08 00 00 00
1078     # data
1079     00 00 00 00 00 00 00 00  # 8 bytes
1080 
1081 # . . vim:nowrap:textwidth=0