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