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