https://github.com/akkartik/mu/blob/master/subx/apps/pack.subx
   1 # Read a text file of SubX instructions from stdin, and convert it into a list
   2 # of whitespace-separated ascii hex bytes on stdout, suitable to be further
   3 # processed by apps/hex.
   4 #
   5 # To run (from the subx/ directory):
   6 #   $ ./subx translate *.subx apps/pack.subx -o apps/pack
   7 #   $ echo '05/add-to-EAX 0x20/imm32'  |./subx run apps/pack
   8 # Expected output:
   9 #   05 20 00 00 00  # 05/add-to-EAX 0x20/imm32
  10 # The original instruction gets included as a comment at the end of each
  11 # converted line.
  12 #
  13 # There's zero error-checking. For now we assume the input program is valid.
  14 # We'll continue to rely on the C++ version for error messages.
  15 #
  16 # Label definitions and uses are left untouched for a future 'pass'.
  17 
  18 == code
  19 #   instruction                     effective address                                                   register    displacement    immediate
  20 # . op          subop               mod             rm32          base        index         scale       r32
  21 # . 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
  22 
  23     # for debugging: run a single test
  24 #?     e8/call test-convert-instruction-passes-labels-through/disp32
  25 #?     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
  26 #?     eb/jump  $main:end/disp8
  27 
  28 # main: run tests if necessary, convert stdin if not
  29     # . prolog
  30     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
  31     # - if argc > 1 and argv[1] == "test" then return run_tests()
  32     # . argc > 1
  33     81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0/disp8         1/imm32           # compare *EBP
  34     7e/jump-if-lesser-or-equal  $run-main/disp8
  35     # . argv[1] == "test"
  36     # . . push args
  37     68/push  "test"/imm32
  38     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
  39     # . . call
  40     e8/call  kernel-string-equal?/disp32
  41     # . . discard args
  42     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
  43     # . check result
  44     3d/compare-EAX  1/imm32
  45     75/jump-if-not-equal  $run-main/disp8
  46     # . run-tests()
  47     e8/call  run-tests/disp32
  48     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
  49     eb/jump  $main:end/disp8
  50 $run-main:
  51     # - otherwise convert stdin
  52     # var ed/EAX : exit-descriptor
  53     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
  54     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
  55     # configure ed to really exit()
  56     # . ed->target = 0
  57     c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # copy to *EAX
  58     # return convert(Stdin, 1/stdout, 2/stderr, ed)
  59     # . . push args
  60     50/push-EAX/ed
  61     68/push  Stderr/imm32
  62     68/push  Stdout/imm32
  63     68/push  Stdin/imm32
  64     # . . call
  65     e8/call  convert/disp32
  66     # . . discard args
  67     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
  68     # . syscall(exit, 0)
  69     bb/copy-to-EBX  0/imm32
  70 $main:end:
  71     b8/copy-to-EAX  1/imm32/exit
  72     cd/syscall  0x80/imm8
  73 
  74 # - big picture
  75 # We'll operate on each line/instruction in isolation. That way we only need to
  76 # allocate memory for converting a single instruction.
  77 #
  78 # To pack an entire file:
  79 #   read every line
  80 #   convert it
  81 #
  82 # primary state: line
  83 #   stream of 512 bytes; abort if it ever overflows
  84 
  85 convert:  # in : (address buffered-file), out : (address buffered-file) -> <void>
  86     # pseudocode:
  87     #   line = new-stream(512, 1)
  88     #   repeatedly
  89     #     clear-stream(line)
  90     #     EAX = read-line(in, line)
  91     #     if EAX == EOF break
  92     #     convert-instruction(line, out, err, ed)
  93     #   flush(out)
  94     #
  95     # . prolog
  96     55/push-EBP
  97     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
  98     # . save registers
  99     51/push-ECX
 100     # var line/ECX : (address stream byte) = stream(512)
 101     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x200/imm32       # subtract from ESP
 102     68/push  0x200/imm32/length
 103     68/push  0/imm32/read
 104     68/push  0/imm32/write
 105     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
 106 $convert:loop:
 107     # clear-stream(ECX)
 108     # . . push args
 109     51/push-ECX
 110     # . . call
 111     e8/call  clear-stream/disp32
 112     # . . discard args
 113     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 114     # EAX = read-line(in, line)
 115     # . . push args
 116     51/push-ECX
 117     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
 118     # . . call
 119     e8/call  convert-instruction/disp32
 120     # . . discard args
 121     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 122     # if EAX == 0xffffffff break
 123     3d/compare-with-EAX  0xffffffff/imm32
 124     74/jump-if-equal  $convert:break/disp8
 125     # convert-instruction(line, out, err, ed)
 126     # . . push args
 127     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
 128     51/push-ECX
 129     # . . call
 130     e8/call  convert-instruction/disp32
 131     # . . discard args
 132     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
 133 $convert:break:
 134     # flush(out)
 135     # . . push args
 136     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
 137     # . . call
 138     e8/call  flush/disp32
 139     # . . discard args
 140     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 141 $convert:end:
 142     # . restore registers
 143     59/pop-to-ECX
 144     # . epilog
 145     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 146     5d/pop-to-EBP
 147     c3/return
 148 
 149 # - To pack an instruction, following the C++ version:
 150 # read first word as opcode and write-slice
 151 # if 0f or f2 or f3 read second opcode and write-slice
 152 # if 'f2 0f' or 'f3 0f' read third opcode and write-slice
 153 # while true:
 154 #   word-slice = next-word
 155 #   if empty(word-slice) break
 156 #   if has metadata 'mod', parse into mod
 157 #   if has metadata 'rm32', parse into rm32
 158 #   if has metadata 'r32', parse into r32
 159 #   if has metadata 'subop', parse into r32
 160 # if at least one of the 3 was present, print-byte
 161 # while true:
 162 #   word-slice = next-word
 163 #   if empty(word-slice) break
 164 #   if has metadata 'base', parse into base
 165 #   if has metadata 'index', parse into index
 166 #   if has metadata 'scale', parse into scale
 167 # if at least one of the 3 was present, print-byte
 168 # parse errors => <abort>
 169 # while true:
 170 #   word-slice = next-word
 171 #   if empty(word-slice) break
 172 #   if has metadata 'disp8', emit as 1 byte
 173 #   if has metadata 'disp16', emit as 2 bytes
 174 #   if has metadata 'disp32', emit as 4 bytes
 175 # while true:
 176 #   word-slice = next-word
 177 #   if empty(word-slice) break
 178 #   if has metadata 'imm8', emit
 179 #   if has metadata 'imm32', emit as 4 bytes
 180 # finally, emit line prefixed with a '  # '
 181 
 182 # simplifications since we perform zero error handling (continuing to rely on the C++ version for that):
 183 #   missing fields are always 0-filled
 184 #   bytes never mentioned are silently dropped; if you don't provide /mod, /rm32 or /r32 you don't get a 0 modrm byte. You get *no* modrm byte.
 185 #   in case of conflict, last operand with a name is recognized
 186 #   silently drop extraneous operands
 187 #   unceremoniously abort on non-numeric operands except disp or imm
 188 
 189 # conceptual hierarchy within a line:
 190 #   line = words separated by ' ', maybe followed by comment starting with '#'
 191 #   word = name until '/', then 0 or more metadata separated by '/'
 192 #
 193 # we won't bother saving the internal structure of lines; reparsing should be cheap using three primitives:
 194 #   next-token(stream, delim char) -> slice (start, end pointers)
 195 #   next-token(stream, slice, delim char) -> slice'
 196 #   slice-equal?(slice, string)
 197 
 198 convert-instruction:  # line : (address stream byte), out : (address buffered-file) -> <void>
 199     # pseudocode:
 200     #   word-slice = next-word
 201     #   if *word-slice->start == '#'
 202     #     write-stream-buffered(out, line)
 203     #     return
 204     #   if starts-with(word-slice, '==')
 205     #     segment-name = next-word()
 206     #     write-stream-buffered(out, line)
 207     #     return
 208     #   if segment-name != 'code'
 209     #     write-stream-buffered(out, line)
 210     #     return
 211     #
 212     # . prolog
 213     55/push-EBP
 214     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 215     # . save registers
 216     51/push-ECX
 217     # var word-slice/ECX = {0, 0}
 218     68/push  0/imm32/end
 219     68/push  0/imm32/start
 220     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
 221     # next-word(line, word-slice)
 222     # . . push args
 223     51/push-ECX
 224     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
 225     # . . call
 226     e8/call  next-word/disp32
 227     # . . discard args
 228     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 229 $convert-instruction:pass-line-through:
 230     # write-stream-buffered(out, line)
 231     # . . push args
 232     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
 233     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
 234     # . . call
 235     e8/call  write-stream-buffered/disp32
 236     # . . discard args
 237     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 238 $convert-instruction:end:
 239     # . restore registers
 240     59/pop-to-ECX
 241     # . epilog
 242     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 243     5d/pop-to-EBP
 244     c3/return
 245 
 246 test-convert-instruction-passes-comments-through:
 247     # if a line starts with '#', pass it along unchanged
 248     # . prolog
 249     55/push-EBP
 250     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 251     # setup
 252     # . clear-stream(_test-stream)
 253     # . . push args
 254     68/push  _test-stream/imm32
 255     # . . call
 256     e8/call  clear-stream/disp32
 257     # . . discard args
 258     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 259     # . clear-stream(_test-buffered-file+4)
 260     # . . push args
 261     b8/copy-to-EAX  _test-buffered-file/imm32
 262     05/add-to-EAX  4/imm32
 263     50/push-EAX
 264     # . . call
 265     e8/call  clear-stream/disp32
 266     # . . discard args
 267     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 268     # . clear-stream(_test-tmp-stream)
 269     # . . push args
 270     68/push  _test-tmp-stream/imm32
 271     # . . call
 272     e8/call  clear-stream/disp32
 273     # . . discard args
 274     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 275     # initialize input
 276     # . write(_test-tmp-stream, "# abcd")
 277     # . . push args
 278     68/push  "# abcd"/imm32
 279     68/push  _test-tmp-stream/imm32
 280     # . . call
 281     e8/call  write/disp32
 282     # . . discard args
 283     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 284     # convert-instruction(_test-tmp-stream, _test-buffered-file)
 285     # . . push args
 286     68/push  _test-buffered-file/imm32
 287     68/push  _test-tmp-stream/imm32
 288     # . . call
 289     e8/call  convert-instruction/disp32
 290     # . . discard args
 291     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 292     # check that the write happened as expected
 293     # . flush(_test-buffered-file)
 294     # . . push args
 295     68/push  _test-buffered-file/imm32
 296     # . . call
 297     e8/call  flush/disp32
 298     # . . discard args
 299     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 300     # . check-stream-equal(_test-stream, "# abcd", msg)
 301     # . . push args
 302     68/push  "F - test-convert-instruction-passes-comments-through"/imm32
 303     68/push  "# abcd"/imm32
 304     68/push  _test-stream/imm32
 305     # . . call
 306     e8/call  check-stream-equal/disp32
 307     # . . discard args
 308     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 309     # . epilog
 310     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 311     5d/pop-to-EBP
 312     c3/return
 313 
 314 test-convert-instruction-passes-segment-headers-through:
 315     # if a line starts with '==', pass it along unchanged
 316     # . prolog
 317     55/push-EBP
 318     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 319     # setup
 320     # . clear-stream(_test-stream)
 321     # . . push args
 322     68/push  _test-stream/imm32
 323     # . . call
 324     e8/call  clear-stream/disp32
 325     # . . discard args
 326     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 327     # . clear-stream(_test-buffered-file+4)
 328     # . . push args
 329     b8/copy-to-EAX  _test-buffered-file/imm32
 330     05/add-to-EAX  4/imm32
 331     50/push-EAX
 332     # . . call
 333     e8/call  clear-stream/disp32
 334     # . . discard args
 335     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 336     # . clear-stream(_test-tmp-stream)
 337     # . . push args
 338     68/push  _test-tmp-stream/imm32
 339     # . . call
 340     e8/call  clear-stream/disp32
 341     # . . discard args
 342     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 343     # initialize input
 344     # . write(_test-tmp-stream, "== abcd")
 345     # . . push args
 346     68/push  "== abcd"/imm32
 347     68/push  _test-tmp-stream/imm32
 348     # . . call
 349     e8/call  write/disp32
 350     # . . discard args
 351     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 352     # convert-instruction(_test-tmp-stream, _test-buffered-file)
 353     # . . push args
 354     68/push  _test-buffered-file/imm32
 355     68/push  _test-tmp-stream/imm32
 356     # . . call
 357     e8/call  convert-instruction/disp32
 358     # . . discard args
 359     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 360     # check that the write happened as expected
 361     # . flush(_test-buffered-file)
 362     # . . push args
 363     68/push  _test-buffered-file/imm32
 364     # . . call
 365     e8/call  flush/disp32
 366     # . . discard args
 367     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 368     # . check-stream-equal(_test-stream, "== abcd", msg)
 369     # . . push args
 370     68/push  "F - test-convert-instruction-passes-segment-headers-through"/imm32
 371     68/push  "== abcd"/imm32
 372     68/push  _test-stream/imm32
 373     # . . call
 374     e8/call  check-stream-equal/disp32
 375     # . . discard args
 376     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 377     # . epilog
 378     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 379     5d/pop-to-EBP
 380     c3/return
 381 
 382 test-convert-instruction-passes-empty-lines-through:
 383     # if a line is empty, pass it along unchanged
 384     # . prolog
 385     55/push-EBP
 386     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 387     # setup
 388     # . clear-stream(_test-stream)
 389     # . . push args
 390     68/push  _test-stream/imm32
 391     # . . call
 392     e8/call  clear-stream/disp32
 393     # . . discard args
 394     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 395     # . clear-stream(_test-buffered-file+4)
 396     # . . push args
 397     b8/copy-to-EAX  _test-buffered-file/imm32
 398     05/add-to-EAX  4/imm32
 399     50/push-EAX
 400     # . . call
 401     e8/call  clear-stream/disp32
 402     # . . discard args
 403     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 404     # . clear-stream(_test-tmp-stream)
 405     # . . push args
 406     68/push  _test-tmp-stream/imm32
 407     # . . call
 408     e8/call  clear-stream/disp32
 409     # . . discard args
 410     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 411     # write nothing to input
 412     # convert-instruction(_test-tmp-stream, _test-buffered-file)
 413     # . . push args
 414     68/push  _test-buffered-file/imm32
 415     68/push  _test-tmp-stream/imm32
 416     # . . call
 417     e8/call  convert-instruction/disp32
 418     # . . discard args
 419     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 420     # check that the write happened as expected
 421     # . flush(_test-buffered-file)
 422     # . . push args
 423     68/push  _test-buffered-file/imm32
 424     # . . call
 425     e8/call  flush/disp32
 426     # . . discard args
 427     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 428     # . check-stream-equal(_test-stream, "", msg)
 429     # . . push args
 430     68/push  "F - test-convert-instruction-passes-empty-lines-through"/imm32
 431     68/push  ""/imm32
 432     68/push  _test-stream/imm32
 433     # . . call
 434     e8/call  check-stream-equal/disp32
 435     # . . discard args
 436     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 437     # . epilog
 438     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 439     5d/pop-to-EBP
 440     c3/return
 441 
 442 test-convert-instruction-passes-lines-with-just-whitespace-through:
 443     # if a line is empty, pass it along unchanged
 444     # . prolog
 445     55/push-EBP
 446     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 447     # setup
 448     # . clear-stream(_test-stream)
 449     # . . push args
 450     68/push  _test-stream/imm32
 451     # . . call
 452     e8/call  clear-stream/disp32
 453     # . . discard args
 454     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 455     # . clear-stream(_test-buffered-file+4)
 456     # . . push args
 457     b8/copy-to-EAX  _test-buffered-file/imm32
 458     05/add-to-EAX  4/imm32
 459     50/push-EAX
 460     # . . call
 461     e8/call  clear-stream/disp32
 462     # . . discard args
 463     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 464     # . clear-stream(_test-tmp-stream)
 465     # . . push args
 466     68/push  _test-tmp-stream/imm32
 467     # . . call
 468     e8/call  clear-stream/disp32
 469     # . . discard args
 470     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 471     # initialize input
 472     # . write(_test-tmp-stream, "    ")
 473     # . . push args
 474     68/push  "    "/imm32
 475     68/push  _test-tmp-stream/imm32
 476     # . . call
 477     e8/call  write/disp32
 478     # . . discard args
 479     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 480     # convert-instruction(_test-tmp-stream, _test-buffered-file)
 481     # . . push args
 482     68/push  _test-buffered-file/imm32
 483     68/push  _test-tmp-stream/imm32
 484     # . . call
 485     e8/call  convert-instruction/disp32
 486     # . . discard args
 487     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 488     # check that the write happened as expected
 489     # . flush(_test-buffered-file)
 490     # . . push args
 491     68/push  _test-buffered-file/imm32
 492     # . . call
 493     e8/call  flush/disp32
 494     # . . discard args
 495     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 496     # . check-stream-equal(_test-stream, "    ", msg)
 497     # . . push args
 498     68/push  "F - test-convert-instruction-passes-with-just-whitespace-through"/imm32
 499     68/push  "    "/imm32
 500     68/push  _test-stream/imm32
 501     # . . call
 502     e8/call  check-stream-equal/disp32
 503     # . . discard args
 504     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 505     # . epilog
 506     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 507     5d/pop-to-EBP
 508     c3/return
 509 
 510 test-convert-instruction-passes-labels-through:
 511     # if the first word ends with ':', pass along the entire line unchanged
 512     # . prolog
 513     55/push-EBP
 514     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 515     # setup
 516     # . clear-stream(_test-stream)
 517     # . . push args
 518     68/push  _test-stream/imm32
 519     # . . call
 520     e8/call  clear-stream/disp32
 521     # . . discard args
 522     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 523     # . clear-stream(_test-buffered-file+4)
 524     # . . push args
 525     b8/copy-to-EAX  _test-buffered-file/imm32
 526     05/add-to-EAX  4/imm32
 527     50/push-EAX
 528     # . . call
 529     e8/call  clear-stream/disp32
 530     # . . discard args
 531     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 532     # . clear-stream(_test-tmp-stream)
 533     # . . push args
 534     68/push  _test-tmp-stream/imm32
 535     # . . call
 536     e8/call  clear-stream/disp32
 537     # . . discard args
 538     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 539     # initialize input
 540     # . write(_test-tmp-stream, "ab: # cd")
 541     # . . push args
 542     68/push  "ab: # cd"/imm32
 543     68/push  _test-tmp-stream/imm32
 544     # . . call
 545     e8/call  write/disp32
 546     # . . discard args
 547     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 548     # convert-instruction(_test-tmp-stream, _test-buffered-file)
 549     # . . push args
 550     68/push  _test-buffered-file/imm32
 551     68/push  _test-tmp-stream/imm32
 552     # . . call
 553     e8/call  convert-instruction/disp32
 554     # . . discard args
 555     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 556     # check that the write happened as expected
 557     # . flush(_test-buffered-file)
 558     # . . push args
 559     68/push  _test-buffered-file/imm32
 560     # . . call
 561     e8/call  flush/disp32
 562     # . . discard args
 563     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 564     # . check-stream-equal(_test-stream, "ab: # cd", msg)
 565     # . . push args
 566     68/push  "F - test-convert-instruction-passes-labels-through"/imm32
 567     68/push  "ab: # cd"/imm32
 568     68/push  _test-stream/imm32
 569     # . . call
 570     e8/call  check-stream-equal/disp32
 571     # . . discard args
 572     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 573     # . epilog
 574     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 575     5d/pop-to-EBP
 576     c3/return
 577 
 578 # (re)compute the bounds of the next word in the line
 579 next-word:  # line : (address stream byte), out : (address slice)
 580     # . prolog
 581     55/push-EBP
 582     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 583     # . save registers
 584     50/push-EAX
 585     51/push-ECX
 586     56/push-ESI
 587     57/push-EDI
 588     # ESI = line
 589     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
 590     # EDI = out
 591     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   0xc/disp8       .                 # copy *(EBP+12) to EDI
 592     # skip-chars-matching(line, ' ')
 593     # . . push args
 594     68/push  0x20/imm32/space
 595     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
 596     # . . call
 597     e8/call  skip-chars-matching/disp32
 598     # . . discard args
 599     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 600     # out->start = &line->data[line->read]
 601     8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(ESI+4) to ECX
 602     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/ESI  1/index/ECX   .           0/r32/EAX   0xc/disp8       .                 # copy ESI+ECX+12 to EAX
 603     89/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           0/r32/EAX   .               .                 # copy EAX to *EDI
 604     # if line->data[line->read] == '#': out->end = &line->data[line->write]), skip rest of stream and return
 605     # . EAX = line->data[line->read]
 606     31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
 607     8a/copy-byte                    1/mod/*+disp8   4/rm32/sib    6/base/ESI  1/index/ECX   .           0/r32/AL    0xc/disp8       .                 # copy byte at *(ESI+ECX+12) to AL
 608     # . compare
 609     3d/compare-EAX-with  0x23/imm32/pound
 610     75/jump-if-not-equal  $next-word:not-comment/disp8
 611     # . out->end = &line->data[line->write]
 612     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # copy *ESI to EAX
 613     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/ESI  0/index/EAX   .           0/r32/EAX   0xc/disp8       .                 # copy ESI+EAX+12 to EAX
 614     89/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(EDI+4)
 615     # . line->read = line->write
 616     89/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(ESI+4)
 617     # . return
 618     eb/jump  $next-word:end/disp8
 619 $next-word:not-comment:
 620     # otherwise skip-chars-not-matching(line, ' ')
 621     # . . push args
 622     68/push  0x20/imm32/space
 623     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
 624     # . . call
 625     e8/call  skip-chars-not-matching/disp32
 626     # . . discard args
 627     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 628     # out->end = &line->data[line->read]
 629     8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(ESI+4) to ECX
 630     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/ESI  1/index/ECX   .           0/r32/EAX   0xc/disp8       .                 # copy ESI+ECX+12 to EAX
 631     89/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(EDI+4)
 632 $next-word:end:
 633     # . restore registers
 634     5f/pop-to-EDI
 635     5e/pop-to-ESI
 636     59/pop-to-ECX
 637     58/pop-to-EAX
 638     # . epilog
 639     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 640     5d/pop-to-EBP
 641     c3/return
 642 
 643 test-next-word:
 644     # . prolog
 645     55/push-EBP
 646     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 647     # setup
 648     # . clear-stream(_test-stream)
 649     # . . push args
 650     68/push  _test-stream/imm32
 651     # . . call
 652     e8/call  clear-stream/disp32
 653     # . . discard args
 654     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 655     # var slice/ECX = {0, 0}
 656     68/push  0/imm32/end
 657     68/push  0/imm32/start
 658     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
 659     # write(_test-stream, "  ab")
 660     # . . push args
 661     68/push  "  ab"/imm32
 662     68/push  _test-stream/imm32
 663     # . . call
 664     e8/call  write/disp32
 665     # . . discard args
 666     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 667     # next-word(_test-stream, slice)
 668     # . . push args
 669     51/push-ECX
 670     68/push  _test-stream/imm32
 671     # . . call
 672     e8/call  next-word/disp32
 673     # . . discard args
 674     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 675     # check-ints-equal(slice->start - _test-stream->data, 2, msg)
 676     # . check-ints-equal(slice->start - _test-stream, 14, msg)
 677     # . . push args
 678     68/push  "F - test-next-word: start"/imm32
 679     68/push  0xe/imm32
 680     # . . push slice->start - _test-stream
 681     8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy *ECX to EAX
 682     81          5/subop/subtract    3/mod/direct    0/rm32/EAX    .           .             .           .           .               _test-stream/imm32 # subtract from EAX
 683     50/push-EAX
 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     # check-ints-equal(slice->end - _test-stream->data, 4, msg)
 689     # . check-ints-equal(slice->end - _test-stream, 16, msg)
 690     # . . push args
 691     68/push  "F - test-next-word: end"/imm32
 692     68/push  0x10/imm32
 693     # . . push slice->end - _test-stream
 694     8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ECX+4) to EAX
 695     81          5/subop/subtract    3/mod/direct    0/rm32/EAX    .           .             .           .           .               _test-stream/imm32 # subtract from EAX
 696     50/push-EAX
 697     # . . call
 698     e8/call  check-ints-equal/disp32
 699     # . . discard args
 700     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 701     # . epilog
 702     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 703     5d/pop-to-EBP
 704     c3/return
 705 
 706 test-next-word-returns-whole-comment:
 707     # . prolog
 708     55/push-EBP
 709     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 710     # setup
 711     # . clear-stream(_test-stream)
 712     # . . push args
 713     68/push  _test-stream/imm32
 714     # . . call
 715     e8/call  clear-stream/disp32
 716     # . . discard args
 717     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 718     # var slice/ECX = {0, 0}
 719     68/push  0/imm32/end
 720     68/push  0/imm32/start
 721     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
 722     # write(_test-stream, "  # a")
 723     # . . push args
 724     68/push  "  # a"/imm32
 725     68/push  _test-stream/imm32
 726     # . . call
 727     e8/call  write/disp32
 728     # . . discard args
 729     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 730     # next-word(_test-stream, slice)
 731     # . . push args
 732     51/push-ECX
 733     68/push  _test-stream/imm32
 734     # . . call
 735     e8/call  next-word/disp32
 736     # . . discard args
 737     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 738     # check-ints-equal(slice->start - _test-stream->data, 2, msg)
 739     # . check-ints-equal(slice->start - _test-stream, 14, msg)
 740     # . . push args
 741     68/push  "F - test-next-word-returns-whole-comment: start"/imm32
 742     68/push  0xe/imm32
 743     # . . push slice->start - _test-stream
 744     8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy *ECX to EAX
 745     81          5/subop/subtract    3/mod/direct    0/rm32/EAX    .           .             .           .           .               _test-stream/imm32 # subtract from EAX
 746     50/push-EAX
 747     # . . call
 748     e8/call  check-ints-equal/disp32
 749     # . . discard args
 750     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 751     # check-ints-equal(slice->end - _test-stream->data, 5, msg)
 752     # . check-ints-equal(slice->end - _test-stream, 17, msg)
 753     # . . push args
 754     68/push  "F - test-next-word-returns-whole-comment: end"/imm32
 755     68/push  0x11/imm32
 756     # . . push slice->end - _test-stream
 757     8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ECX+4) to EAX
 758     81          5/subop/subtract    3/mod/direct    0/rm32/EAX    .           .             .           .           .               _test-stream/imm32 # subtract from EAX
 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     # . epilog
 765     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 766     5d/pop-to-EBP
 767     c3/return
 768 
 769 has-metadata?:  # word : (address slice), s : (address string) -> EAX : boolean
 770     # pseudocode:
 771     #   var twig : &slice = next-token-from-slice(word->start, word->end, '/')  # skip name
 772     #   curr = twig->end
 773     #   while true:
 774     #     twig = next-token-from-slice(curr, word->end, '/')
 775     #     if twig.empty() break
 776     #     if slice-equal?(twig, s) return true
 777     #     curr = twig->end
 778     #   return false
 779     # . prolog
 780     55/push-EBP
 781     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 782     # . save registers
 783     51/push-ECX
 784     52/push-EDX
 785     56/push-ESI
 786     57/push-EDI
 787     # ESI = word
 788     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
 789     # EDX = word->end
 790     8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           2/r32/EDX   4/disp8         .                 # copy *(ESI+4) to EDX
 791     # var twig/EDI : (address slice) = {0, 0}
 792     68/push  0/imm32/end
 793     68/push  0/imm32/start
 794     89/copy                         3/mod/direct    7/rm32/EDI    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDI
 795     # next-token-from-slice(word->start, word->end, '/', twig)
 796     # . . push args
 797     57/push-EDI
 798     68/push  0x2f/imm32/slash
 799     52/push-EDX
 800     ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
 801     # . . call
 802     e8/call  next-token-from-slice/disp32
 803     # . . discard args
 804     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
 805     # curr/ECX = twig->end
 806     8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(EDI+4) to ECX
 807 $has-metadata?:loop:
 808     # next-token-from-slice(curr, word->end, '/', twig)
 809     # . . push args
 810     57/push-EDI
 811     68/push  0x2f/imm32/slash
 812     52/push-EDX
 813     51/push-ECX
 814     # . . call
 815     e8/call  next-token-from-slice/disp32
 816     # . . discard args
 817     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
 818     # if slice-empty?(twig) return false
 819     # . EAX = slice-empty?(twig)
 820     # . . push args
 821     57/push-EDI
 822     # . . call
 823     e8/call  slice-empty?/disp32
 824     # . . discard args
 825     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 826     # . if (EAX != 0) return false
 827     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # compare EAX
 828     75/compare-if-not-equal  $has-metadata?:false/disp8
 829     # if slice-equal?(twig, s) return true
 830     # . EAX = slice-equal?(twig, s)
 831     # . . push args
 832     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
 833     57/push-EDI
 834     # . . call
 835     e8/call  slice-equal?/disp32
 836     # . . discard args
 837     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 838     # . if (EAX != 0) return true
 839     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # compare EAX
 840     75/compare-if-not-equal  $has-metadata?:true/disp8
 841     # curr = twig->end
 842     8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(EDI+4) to ECX
 843     eb/jump  $has-metadata?:loop/disp8
 844 $has-metadata?:true:
 845     b8/copy-to-EAX  1/imm32/true
 846     eb/jump  $has-metadata?:end/disp8
 847 $has-metadata?:false:
 848     b8/copy-to-EAX  0/imm32/false
 849 $has-metadata?:end:
 850     # . reclaim locals
 851     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 852     # . restore registers
 853     5f/pop-to-EDI
 854     5e/pop-to-ESI
 855     5a/pop-to-EDX
 856     59/pop-to-ECX
 857     # . epilog
 858     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 859     5d/pop-to-EBP
 860     c3/return
 861 
 862 test-has-metadata-true:
 863     # . prolog
 864     55/push-EBP
 865     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 866     # (EAX..ECX) = "ab/c"
 867     b8/copy-to-EAX  "ab/c"/imm32
 868     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
 869     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  1/index/ECX   .           1/r32/ECX   4/disp8         .                 # copy EAX+ECX+4 to ECX
 870     05/add-to-EAX  4/imm32
 871     # var in/ESI : (address slice) = {EAX, ECX}
 872     51/push-ECX
 873     50/push-EAX
 874     89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
 875     # EAX = has-metadata?(ESI, "c")
 876     # . . push args
 877     68/push  "c"/imm32
 878     56/push-ESI
 879     # . . call
 880     e8/call  has-metadata?/disp32
 881     # . . discard args
 882     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x8/imm32         # add to ESP
 883     # check-ints-equal(EAX, 1, msg)
 884     # . . push args
 885     68/push  "F - test-has-metadata-true"/imm32
 886     68/push  1/imm32/true
 887     50/push-EAX
 888     # . . call
 889     e8/call  check-ints-equal/disp32
 890     # . . discard args
 891     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 892     # . epilog
 893     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 894     5d/pop-to-EBP
 895     c3/return
 896 
 897 test-has-metadata-false:
 898     # . prolog
 899     55/push-EBP
 900     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 901     # (EAX..ECX) = "ab/c"
 902     b8/copy-to-EAX  "ab/c"/imm32
 903     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
 904     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  1/index/ECX   .           1/r32/ECX   4/disp8         .                 # copy EAX+ECX+4 to ECX
 905     05/add-to-EAX  4/imm32
 906     # var in/ESI : (address slice) = {EAX, ECX}
 907     51/push-ECX
 908     50/push-EAX
 909     89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
 910     # EAX = has-metadata?(ESI, "c")
 911     # . . push args
 912     68/push  "d"/imm32
 913     56/push-ESI
 914     # . . call
 915     e8/call  has-metadata?/disp32
 916     # . . discard args
 917     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x8/imm32         # add to ESP
 918     # check-ints-equal(EAX, 0, msg)
 919     # . . push args
 920     68/push  "F - test-has-metadata-false"/imm32
 921     68/push  0/imm32/false
 922     50/push-EAX
 923     # . . call
 924     e8/call  check-ints-equal/disp32
 925     # . . discard args
 926     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 927     # . epilog
 928     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 929     5d/pop-to-EBP
 930     c3/return
 931 
 932 test-has-metadata-ignore-name:
 933     # . prolog
 934     55/push-EBP
 935     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 936     # (EAX..ECX) = "a/b"
 937     b8/copy-to-EAX  "a/b"/imm32
 938     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
 939     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  1/index/ECX   .           1/r32/ECX   4/disp8         .                 # copy EAX+ECX+4 to ECX
 940     05/add-to-EAX  4/imm32
 941     # var in/ESI : (address slice) = {EAX, ECX}
 942     51/push-ECX
 943     50/push-EAX
 944     89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
 945     # EAX = has-metadata?(ESI, "a")
 946     # . . push args
 947     68/push  "a"/imm32
 948     56/push-ESI
 949     # . . call
 950     e8/call  has-metadata?/disp32
 951     # . . discard args
 952     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x8/imm32         # add to ESP
 953     # check-ints-equal(EAX, 0, msg)
 954     # . . push args
 955     68/push  "F - test-has-metadata-ignore-name"/imm32
 956     68/push  0/imm32/false
 957     50/push-EAX
 958     # . . call
 959     e8/call  check-ints-equal/disp32
 960     # . . discard args
 961     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 962     # . epilog
 963     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 964     5d/pop-to-EBP
 965     c3/return
 966 
 967 test-has-metadata-multiple-true:
 968     # . prolog
 969     55/push-EBP
 970     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 971     # (EAX..ECX) = "a/b/c"
 972     b8/copy-to-EAX  "a/b/c"/imm32
 973     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
 974     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  1/index/ECX   .           1/r32/ECX   4/disp8         .                 # copy EAX+ECX+4 to ECX
 975     05/add-to-EAX  4/imm32
 976     # var in/ESI : (address slice) = {EAX, ECX}
 977     51/push-ECX
 978     50/push-EAX
 979     89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
 980     # EAX = has-metadata?(ESI, "c")
 981     # . . push args
 982     68/push  "c"/imm32
 983     56/push-ESI
 984     # . . call
 985     e8/call  has-metadata?/disp32
 986     # . . discard args
 987     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x8/imm32         # add to ESP
 988     # check-ints-equal(EAX, 1, msg)
 989     # . . push args
 990     68/push  "F - test-has-metadata-multiple-true"/imm32
 991     68/push  1/imm32/true
 992     50/push-EAX
 993     # . . call
 994     e8/call  check-ints-equal/disp32
 995     # . . discard args
 996     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
 997     # . epilog
 998     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
 999     5d/pop-to-EBP
1000     c3/return
1001 
1002 test-has-metadata-multiple-false:
1003     # . prolog
1004     55/push-EBP
1005     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1006     # (EAX..ECX) = "a/b/c"
1007     b8/copy-to-EAX  "a/b/c"/imm32
1008     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
1009     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/EAX  1/index/ECX   .           1/r32/ECX   4/disp8         .                 # copy EAX+ECX+4 to ECX
1010     05/add-to-EAX  4/imm32
1011     # var in/ESI : (address slice) = {EAX, ECX}
1012     51/push-ECX
1013     50/push-EAX
1014     89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
1015     # EAX = has-metadata?(ESI, "d")
1016     # . . push args
1017     68/push  "d"/imm32
1018     56/push-ESI
1019     # . . call
1020     e8/call  has-metadata?/disp32
1021     # . . discard args
1022     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x8/imm32         # add to ESP
1023     # check-ints-equal(EAX, 0, msg)
1024     # . . push args
1025     68/push  "F - test-has-metadata-multiple-false"/imm32
1026     68/push  0/imm32/false
1027     50/push-EAX
1028     # . . call
1029     e8/call  check-ints-equal/disp32
1030     # . . discard args
1031     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1032     # . epilog
1033     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1034     5d/pop-to-EBP
1035     c3/return
1036 
1037 # if the name of 'word' is all hex digits, parse and print its value in 'width' bytes of hex, least significant first
1038 # otherwise just print the entire word
1039 emit:  # out : (address buffered-file), word : (address slice), width : int
1040     # . prolog
1041     55/push-EBP
1042     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1043     # . save registers
1044     50/push-EAX
1045     56/push-ESI
1046     57/push-EDI
1047     # ESI = word
1048     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   0xc/disp8       .                 # copy *(EBP+12) to ESI
1049     # var name/EDI : (address slice) = {0, 0}
1050     68/push  0/imm32/end
1051     68/push  0/imm32/start
1052     89/copy                         3/mod/direct    7/rm32/EDI    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDI
1053     # name = next-token-from-slice(word->start, word->end, '/')
1054     # . . push args
1055     57/push-EDI
1056     68/push  0x2f/imm32/slash
1057     ff          6/subop/push        1/mod/*+disp8   6/rm32/ESI    .           .             .           .           4/disp8         .                 # push *(ESI+4)
1058     ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
1059     # . . call
1060     e8/call  next-token-from-slice/disp32
1061     # . . discard args
1062     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
1063     # if !is-hex-int?(name) write-slice(out, word) and return
1064     # . is-hex-int?(name)
1065     # . . push args
1066     57/push-EDI
1067     # . . call
1068     e8/call  is-hex-int?/disp32
1069     # . . discard args
1070     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1071     # . if EAX == 0
1072     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # compare EAX
1073     75/jump-if-not-equal  $emit:hex-int/disp8
1074     # . write-slice(out, word)
1075     # . . push args
1076     56/push-ESI
1077     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
1078     # . . call
1079     e8/call  write-slice/disp32
1080     # . . discard args
1081     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1082     # . return
1083     eb/jump  $emit:end/disp8
1084     # otherwise emit-hex(out, parse-hex-int(name), width)
1085 $emit:hex-int:
1086     # . n/EAX = parse-hex-int(name)
1087     # . . push args
1088     57/push-EDI
1089     # . . call
1090     e8/call  parse-hex-int/disp32
1091     # . . discard args
1092     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1093     # . emit-hex(out, n, width)
1094     # . . push args
1095     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
1096     50/push-EAX
1097     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
1098     # . . call
1099     e8/call  emit-hex/disp32
1100     # . . discard args
1101     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1102 $emit:end:
1103     # . reclaim locals
1104     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1105     # . restore registers
1106     5f/pop-to-EDI
1107     5e/pop-to-ESI
1108     58/pop-to-EAX
1109     # . epilog
1110     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1111     5d/pop-to-EBP
1112     c3/return
1113 
1114 test-emit-number:
1115     # . prolog
1116     55/push-EBP
1117     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1118     # setup
1119     # . clear-stream(_test-stream)
1120     # . . push args
1121     68/push  _test-stream/imm32
1122     # . . call
1123     e8/call  clear-stream/disp32
1124     # . . discard args
1125     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1126     # . clear-stream(_test-buffered-file+4)
1127     # . . push args
1128     b8/copy-to-EAX  _test-buffered-file/imm32
1129     05/add-to-EAX  4/imm32
1130     50/push-EAX
1131     # . . call
1132     e8/call  clear-stream/disp32
1133     # . . discard args
1134     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1135     # var slice/ECX = "-2"
1136     68/push  _test-slice-negative-two-end/imm32/end
1137     68/push  _test-slice-negative-two/imm32/start
1138     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
1139     # emit(_test-buffered-file, slice, 2)
1140     # . . push args
1141     68/push  2/imm32
1142     51/push-ECX
1143     68/push  _test-buffered-file/imm32
1144     # . . call
1145     e8/call  emit/disp32
1146     # . . discard args
1147     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1148     # flush(_test-buffered-file)
1149     # . . push args
1150     68/push  _test-buffered-file/imm32
1151     # . . call
1152     e8/call  flush/disp32
1153     # . . discard args
1154     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1155     # check(_test-stream->data == 'fe ff ')
1156     # . check-ints-equal(_test-stream->data[0..3], 'fe f', msg)
1157     # . . push args
1158     68/push  "F - test-emit-number/1"/imm32
1159     68/push  0x66206566/imm32
1160     # . . push *_test-stream->data
1161     b8/copy-to-EAX  _test-stream/imm32
1162     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
1163     # . . call
1164     e8/call  check-ints-equal/disp32
1165     # . . discard args
1166     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1167     # . check-ints-equal(_test-stream->data[4..7], 'f ', msg)
1168     # . . push args
1169     68/push  "F - test-emit-number/2"/imm32
1170     68/push  0x2066/imm32
1171     # . . push *_test-stream->data
1172     b8/copy-to-EAX  _test-stream/imm32
1173     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0x10/disp8      .                 # push *(EAX+16)
1174     # . . call
1175     e8/call  check-ints-equal/disp32
1176     # . . discard args
1177     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1178     # . epilog
1179     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1180     5d/pop-to-EBP
1181     c3/return
1182 
1183 test-emit-number-with-metadata:
1184     # . prolog
1185     55/push-EBP
1186     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1187     # setup
1188     # . clear-stream(_test-stream)
1189     # . . push args
1190     68/push  _test-stream/imm32
1191     # . . call
1192     e8/call  clear-stream/disp32
1193     # . . discard args
1194     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1195     # . clear-stream(_test-buffered-file+4)
1196     # . . push args
1197     b8/copy-to-EAX  _test-buffered-file/imm32
1198     05/add-to-EAX  4/imm32
1199     50/push-EAX
1200     # . . call
1201     e8/call  clear-stream/disp32
1202     # . . discard args
1203     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1204     # var slice/ECX = "-2/foo"
1205     68/push  _test-slice-negative-two-metadata-end/imm32/end
1206     68/push  _test-slice-negative-two/imm32/start
1207     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
1208     # emit(_test-buffered-file, slice, 2)
1209     # . . push args
1210     68/push  2/imm32
1211     51/push-ECX
1212     68/push  _test-buffered-file/imm32
1213     # . . call
1214     e8/call  emit/disp32
1215     # . . discard args
1216     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1217     # flush(_test-buffered-file)
1218     # . . push args
1219     68/push  _test-buffered-file/imm32
1220     # . . call
1221     e8/call  flush/disp32
1222     # . . discard args
1223     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1224     # the '/foo' will have no impact on the output
1225     # check(_test-stream->data == 'fe ff ')
1226     # . check-ints-equal(_test-stream->data[0..3], 'fe f', msg)
1227     # . . push args
1228     68/push  "F - test-emit-number-with-metadata/1"/imm32
1229     68/push  0x66206566/imm32
1230     # . . push *_test-stream->data
1231     b8/copy-to-EAX  _test-stream/imm32
1232     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
1233     # . . call
1234     e8/call  check-ints-equal/disp32
1235     # . . discard args
1236     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1237     # . check-ints-equal(_test-stream->data[4..7], 'f ', msg)
1238     # . . push args
1239     68/push  "F - test-emit-number-with-metadata/2"/imm32
1240     68/push  0x2066/imm32
1241     # . . push *_test-stream->data
1242     b8/copy-to-EAX  _test-stream/imm32
1243     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0x10/disp8      .                 # push *(EAX+16)
1244     # . . call
1245     e8/call  check-ints-equal/disp32
1246     # . . discard args
1247     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1248     # . epilog
1249     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1250     5d/pop-to-EBP
1251     c3/return
1252 
1253 test-emit-non-number:
1254     # . prolog
1255     55/push-EBP
1256     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1257     # setup
1258     # . clear-stream(_test-stream)
1259     # . . push args
1260     68/push  _test-stream/imm32
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     # . clear-stream(_test-buffered-file+4)
1266     # . . push args
1267     b8/copy-to-EAX  _test-buffered-file/imm32
1268     05/add-to-EAX  4/imm32
1269     50/push-EAX
1270     # . . call
1271     e8/call  clear-stream/disp32
1272     # . . discard args
1273     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1274     # var slice/ECX = "xyz"
1275     68/push  _test-slice-non-number-word-end/imm32/end
1276     68/push  _test-slice-non-number-word/imm32/start
1277     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
1278     # emit(_test-buffered-file, slice, 2)
1279     # . . push args
1280     68/push  2/imm32
1281     51/push-ECX
1282     68/push  _test-buffered-file/imm32
1283     # . . call
1284     e8/call  emit/disp32
1285     # . . discard args
1286     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1287     # flush(_test-buffered-file)
1288     # . . push args
1289     68/push  _test-buffered-file/imm32
1290     # . . call
1291     e8/call  flush/disp32
1292     # . . discard args
1293     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1294     # check(_test-stream->data == 'xyz')
1295     # . check-ints-equal(_test-stream->data[0..3], 'xyz', msg)
1296     # . . push args
1297     68/push  "F - test-emit-non-number"/imm32
1298     68/push  0x7a7978/imm32
1299     # . . push *_test-stream->data
1300     b8/copy-to-EAX  _test-stream/imm32
1301     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
1302     # . . call
1303     e8/call  check-ints-equal/disp32
1304     # . . discard args
1305     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1306     # . epilog
1307     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1308     5d/pop-to-EBP
1309     c3/return
1310 
1311 test-emit-non-number-with-metadata:
1312     # . prolog
1313     55/push-EBP
1314     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1315     # setup
1316     # . clear-stream(_test-stream)
1317     # . . push args
1318     68/push  _test-stream/imm32
1319     # . . call
1320     e8/call  clear-stream/disp32
1321     # . . discard args
1322     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1323     # . clear-stream(_test-buffered-file+4)
1324     # . . push args
1325     b8/copy-to-EAX  _test-buffered-file/imm32
1326     05/add-to-EAX  4/imm32
1327     50/push-EAX
1328     # . . call
1329     e8/call  clear-stream/disp32
1330     # . . discard args
1331     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1332     # var slice/ECX = "xyz/"
1333     68/push  _test-slice-non-number-word-metadata-end/imm32/end
1334     68/push  _test-slice-non-number-word/imm32/start
1335     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
1336     # emit(_test-buffered-file, slice, 2)
1337     # . . push args
1338     68/push  2/imm32
1339     51/push-ECX
1340     68/push  _test-buffered-file/imm32
1341     # . . call
1342     e8/call  emit/disp32
1343     # . . discard args
1344     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1345     # flush(_test-buffered-file)
1346     # . . push args
1347     68/push  _test-buffered-file/imm32
1348     # . . call
1349     e8/call  flush/disp32
1350     # . . discard args
1351     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1352     # check(_test-stream->data == 'xyz/')
1353     # . check-ints-equal(_test-stream->data[0..3], 'xyz/', msg)
1354     # . . push args
1355     68/push  "F - test-emit-non-number-with-metadata"/imm32
1356     68/push  0x2f7a7978/imm32
1357     # . . push *_test-stream->data
1358     b8/copy-to-EAX  _test-stream/imm32
1359     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
1360     # . . call
1361     e8/call  check-ints-equal/disp32
1362     # . . discard args
1363     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1364     # . epilog
1365     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1366     5d/pop-to-EBP
1367     c3/return
1368 
1369 # print 'n' in hex in 'width' bytes in lower-endian order, with a space after every byte
1370 emit-hex:  # out : (address buffered-file), n : int, width : int
1371     # . prolog
1372     55/push-EBP
1373     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
1374     # . save registers
1375     50/push-EAX
1376     51/push-ECX
1377     52/push-EDX
1378     53/push-EBX
1379     57/push-EDI
1380     # EDI = out
1381     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   8/disp8         .                 # copy *(EBP+8) to EDI
1382     # EBX = n
1383     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           3/r32/EBX   0xc/disp8       .                 # copy *(EBP+12) to EBX
1384     # EDX = width
1385     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           2/r32/EDX   0x10/disp8      .                 # copy *(EBP+16) to EDX
1386     # var curr/ECX = 0
1387     31/xor                          3/mod/direct    1/rm32/ECX    .           .             .           1/r32/ECX   .               .                 # clear ECX
1388 $emit-hex:loop:
1389     # if (curr >= width) break
1390     39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX and EDX
1391     7d/jump-if-greater-or-equal  $emit-hex:end/disp8
1392     # print-byte(out, EBX)
1393     # . . push args
1394     53/push-EBX
1395     57/push-EDI
1396     # . . call
1397     e8/call  print-byte/disp32
1398     # . . discard args
1399     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1400     # write-byte(out, ' ')
1401     # . . push args
1402     68/push  0x20/imm32/space
1403     57/push-EDI
1404     # . . call
1405     e8/call  write-byte/disp32
1406     # . . discard args
1407     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
1408     # EBX = EBX >> 8
1409     c1/shift    5/subop/logic-right 3/mod/direct    3/rm32/EBX    .           .             .           .           .               8/imm8            # shift EBX right by 8 bits, while padding zeroes
1410     # ++curr
1411     41/increment-ECX
1412     eb/jump  $emit-hex:loop/disp8
1413 $emit-hex:end:
1414     # . restore registers
1415     5f/pop-to-EDI
1416     5b/pop-to-EBX
1417     5a/pop-to-EAX
1418     59/pop-to-ECX
1419     58/pop-to-EAX
1420     # . epilog
1421     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
1422     5d/pop-to-EBP
1423     c3/return
1424 
1425 test-emit-hex-single-byte:
1426     # setup
1427     # . clear-stream(_test-stream)
1428     # . . push args
1429     68/push  _test-stream/imm32
1430     # . . call
1431     e8/call  clear-stream/disp32
1432     # . . discard args
1433     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1434     # . clear-stream(_test-buffered-file+4)
1435     # . . push args
1436     b8/copy-to-EAX  _test-buffered-file/imm32
1437     05/add-to-EAX  4/imm32
1438     50/push-EAX
1439     # . . call
1440     e8/call  clear-stream/disp32
1441     # . . discard args
1442     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1443     # emit-hex(_test-buffered-file, 0xab, 1)
1444     # . . push args
1445     68/push  1/imm32
1446     68/push  0xab/imm32
1447     68/push  _test-buffered-file/imm32
1448     # . . call
1449     e8/call  emit-hex/disp32
1450     # . . discard args
1451     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1452     # flush(_test-buffered-file)
1453     # . . push args
1454     68/push  _test-buffered-file/imm32
1455     # . . call
1456     e8/call  flush/disp32
1457     # . . discard args
1458     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1459     # check-ints-equal(*_test-stream->data, 'ab ', msg)
1460     # . . push args
1461     68/push  "F - test-emit-hex-single-byte"/imm32
1462     68/push  0x206261/imm32
1463     # . . push *_test-stream->data
1464     b8/copy-to-EAX  _test-stream/imm32
1465     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
1466     # . . call
1467     e8/call  check-ints-equal/disp32
1468     # . . discard args
1469     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1470     # . end
1471     c3/return
1472 
1473 test-emit-hex-multiple-byte:
1474     # setup
1475     # . clear-stream(_test-stream)
1476     # . . push args
1477     68/push  _test-stream/imm32
1478     # . . call
1479     e8/call  clear-stream/disp32
1480     # . . discard args
1481     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1482     # . clear-stream(_test-buffered-file+4)
1483     # . . push args
1484     b8/copy-to-EAX  _test-buffered-file/imm32
1485     05/add-to-EAX  4/imm32
1486     50/push-EAX
1487     # . . call
1488     e8/call  clear-stream/disp32
1489     # . . discard args
1490     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1491     # emit-hex(_test-buffered-file, 0x1234, 2)
1492     # . . push args
1493     68/push  2/imm32
1494     68/push  0x1234/imm32
1495     68/push  _test-buffered-file/imm32
1496     # . . call
1497     e8/call  emit-hex/disp32
1498     # . . discard args
1499     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1500     # flush(_test-buffered-file)
1501     # . . push args
1502     68/push  _test-buffered-file/imm32
1503     # . . call
1504     e8/call  flush/disp32
1505     # . . discard args
1506     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1507     # check(_test-stream->data == '34 12 ')
1508     # . check-ints-equal(_test-stream->data[0..3], '34 1', msg)
1509     # . . push args
1510     68/push  "F - test-emit-hex-multiple-byte/1"/imm32
1511     68/push  0x31203433/imm32
1512     # . . push *_test-stream->data
1513     b8/copy-to-EAX  _test-stream/imm32
1514     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
1515     # . . call
1516     e8/call  check-ints-equal/disp32
1517     # . . discard args
1518     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1519     # . check-ints-equal(_test-stream->data[4..7], '2 ', msg)
1520     # . . push args
1521     68/push  "F - test-emit-hex-multiple-byte/2"/imm32
1522     68/push  0x2032/imm32
1523     # . . push *_test-stream->data
1524     b8/copy-to-EAX  _test-stream/imm32
1525     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0x10/disp8      .                 # push *(EAX+16)
1526     # . . call
1527     e8/call  check-ints-equal/disp32
1528     # . . discard args
1529     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1530     # . end
1531     c3/return
1532 
1533 test-emit-hex-zero-pad:
1534     # setup
1535     # . clear-stream(_test-stream)
1536     # . . push args
1537     68/push  _test-stream/imm32
1538     # . . call
1539     e8/call  clear-stream/disp32
1540     # . . discard args
1541     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1542     # . clear-stream(_test-buffered-file+4)
1543     # . . push args
1544     b8/copy-to-EAX  _test-buffered-file/imm32
1545     05/add-to-EAX  4/imm32
1546     50/push-EAX
1547     # . . call
1548     e8/call  clear-stream/disp32
1549     # . . discard args
1550     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1551     # emit-hex(_test-buffered-file, 0xab, 2)
1552     # . . push args
1553     68/push  2/imm32
1554     68/push  0xab/imm32
1555     68/push  _test-buffered-file/imm32
1556     # . . call
1557     e8/call  emit-hex/disp32
1558     # . . discard args
1559     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1560     # flush(_test-buffered-file)
1561     # . . push args
1562     68/push  _test-buffered-file/imm32
1563     # . . call
1564     e8/call  flush/disp32
1565     # . . discard args
1566     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1567     # check(_test-stream->data == '00 ab')
1568     # . check-ints-equal(*_test-stream->data, 'ab 0', msg)
1569     # . . push args
1570     68/push  "F - test-emit-hex-zero-pad/1"/imm32
1571     68/push  0x30206261/imm32
1572     # . . push *_test-stream->data
1573     b8/copy-to-EAX  _test-stream/imm32
1574     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
1575     # . . call
1576     e8/call  check-ints-equal/disp32
1577     # . . discard args
1578     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1579     # . check-ints-equal(*_test-stream->data[1], '0 ', msg)
1580     # . . push args
1581     68/push  "F - test-emit-hex-zero-pad/2"/imm32
1582     68/push  0x2030/imm32
1583     # . . push *_test-stream->data
1584     b8/copy-to-EAX  _test-stream/imm32
1585     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0x10/disp8      .                 # push *(EAX+16)
1586     # . . call
1587     e8/call  check-ints-equal/disp32
1588     # . . discard args
1589     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1590     # . end
1591     c3/return
1592 
1593 test-emit-hex-negative:
1594     # setup
1595     # . clear-stream(_test-stream)
1596     # . . push args
1597     68/push  _test-stream/imm32
1598     # . . call
1599     e8/call  clear-stream/disp32
1600     # . . discard args
1601     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1602     # . clear-stream(_test-buffered-file+4)
1603     # . . push args
1604     b8/copy-to-EAX  _test-buffered-file/imm32
1605     05/add-to-EAX  4/imm32
1606     50/push-EAX
1607     # . . call
1608     e8/call  clear-stream/disp32
1609     # . . discard args
1610     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1611     # emit-hex(_test-buffered-file, -1, 2)
1612     # . . push args
1613     68/push  2/imm32
1614     68/push  -1/imm32
1615     68/push  _test-buffered-file/imm32
1616     # . . call
1617     e8/call  emit-hex/disp32
1618     # . . discard args
1619     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1620     # flush(_test-buffered-file)
1621     # . . push args
1622     68/push  _test-buffered-file/imm32
1623     # . . call
1624     e8/call  flush/disp32
1625     # . . discard args
1626     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
1627     # check(_test-stream->data == 'ff ff ')
1628     # . check-ints-equal(_test-stream->data[0..3], 'ff f', msg)
1629     # . . push args
1630     68/push  "F - test-emit-hex-negative/1"/imm32
1631     68/push  0x66206666/imm32
1632     # . . push *_test-stream->data
1633     b8/copy-to-EAX  _test-stream/imm32
1634     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
1635     # . . call
1636     e8/call  check-ints-equal/disp32
1637     # . . discard args
1638     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1639     # . check-ints-equal(_test-stream->data[4..7], 'f ', msg)
1640     # . . push args
1641     68/push  "F - test-emit-hex-negative/2"/imm32
1642     68/push  0x2066/imm32
1643     # . . push *_test-stream->data
1644     b8/copy-to-EAX  _test-stream/imm32
1645     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0x10/disp8      .                 # push *(EAX+16)
1646     # . . call
1647     e8/call  check-ints-equal/disp32
1648     # . . discard args
1649     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
1650     # . end
1651     c3/return
1652 
1653 == data
1654 
1655 _test-slice-negative-two:
1656     2d/- 32/2
1657 _test-slice-negative-two-end:
1658     2f/slash 66/f 6f/o 6f/o
1659 _test-slice-negative-two-metadata-end:
1660 
1661 _test-slice-non-number-word:
1662     78/x 79/y 7a/z
1663 _test-slice-non-number-word-end:
1664     2f/slash
1665 _test-slice-non-number-word-metadata-end:
1666 
1667 # . . vim:nowrap:textwidth=0