diff --git a/html/subx/apps/pack.subx.html b/html/subx/apps/pack.subx.html index 36bb9206..460cb861 100644 --- a/html/subx/apps/pack.subx.html +++ b/html/subx/apps/pack.subx.html @@ -21,6 +21,7 @@ a { color:inherit; } .CommentedCode { color: #8a8a8a; } .Constant { color: #008787; } .subxFunction { color: #af5f00; text-decoration: underline; } +.subxMinorFunction { color: #875f5f; } .Normal { color: #000000; background-color: #c6c6c6; padding-bottom: 1px; } .subxTest { color: #5f8700; } .SpecialChar { color: #d70000; } @@ -60,577 +61,1252 @@ if ('onhashchange' in window) { 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-emit-hex-zero-pad/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 #   skip segment headers
- 80 #   pack every instruction in the code segment
- 81 #   skip other segments
- 82 
- 83 # - To pack an instruction, following the C++ version:
- 84 # read line
- 85 # parse words
- 86 # read first word as opcode and write-slice
- 87 # if 0f or f2 or f3 read second opcode and write-slice
- 88 # if 'f2 0f' or 'f3 0f' read third opcode and write-slice
- 89 # scan words
- 90 #   if has metadata 'mod', parse into mod
- 91 #   if has metadata 'rm32', parse into rm32
- 92 #   if has metadata 'r32', parse into r32
- 93 #   if has metadata 'subop', parse into r32
- 94 # if at least one of the 3 was present, print-byte
- 95 # scan words
- 96 #   if has metadata 'base', parse into base
- 97 #   if has metadata 'index', parse into index
- 98 #   if has metadata 'scale', parse into scale
- 99 # if at least one of the 3 was present, print-byte
-100 # parse errors => <abort>
-101 # scan words
-102 #   if has metadata 'disp8', emit as 1 byte
-103 #   if has metadata 'disp16', emit as 2 bytes
-104 #   if has metadata 'disp32', emit as 4 bytes
-105 # scan words
-106 #   if has metadata 'imm8', emit
-107 #   if has metadata 'imm32', emit as 4 bytes
-108 # finally, emit line prefixed with a '  # '
-109 
-110 # simplifications since we perform zero error handling (continuing to rely on the C++ version for that):
-111 #   missing fields are always 0-filled
-112 #   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.
-113 #   in case of conflict, last operand with a name is recognized
-114 #   silently drop extraneous operands
-115 #   unceremoniously abort on non-numeric operands except disp or imm
-116 
-117 # primary state: line
-118 #   stream of 512 bytes; abort if it ever overflows
-119 #
-120 # conceptual hierarchy within a line:
-121 #   line = words separated by ' ', maybe followed by comment starting with '#'
-122 #   word = name until '/', then 0 or more metadata separated by '/'
-123 #
-124 # we won't bother saving the internal structure of lines; reparsing should be cheap using two primitives:
-125 #   next-token(stream, delim char) -> slice (start, end pointers)
-126 #   slice-equal?(slice, kernel string)
-127 
-128 # helpers:
-129 #   emit(out : &buffered-file, word : &slice, width : int)
-130 #     if slice is all hex digits, parse and print appropriate digits
-131 #     otherwise just write-slice
-132 #   has-metadata?(word : &slice, s : &kernel-string) -> bool
-133 
-134 convert:  # in : (address buffered-file), out : (address buffered-file), err : (address buffered-file), ed : (address exit-descriptor) -> <void>
-135     # pseudocode:
-136     #   line = new-stream(512, 1)
-137     #   repeatedly
-138     #     clear-stream(line)
-139     #     EAX = read-line(in, line, err, ed)
-140     #     if EAX == EOF break
-141     #     convert-instruction(line, out, err, ed)
-142     #   flush(out)
-143     #
-144     # . prolog
-145     55/push-EBP
-146     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-147     # . save registers
-148     # . restore registers
-149     # . epilog
-150     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-151     5d/pop-to-EBP
-152     c3/return
-153 
-154 # (re)compute the bounds of the next word in the line
-155 next-word:  # line : (address stream byte), out : (address slice)
-156     # . prolog
-157     55/push-EBP
-158     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-159     # . save registers
-160     50/push-EAX
-161     51/push-ECX
-162     56/push-ESI
-163     57/push-EDI
-164     # ESI = line
-165     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
-166     # EDI = out
-167     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   0xc/disp8       .                 # copy *(EBP+12) to EDI
-168     # skip-chars-matching(line, ' ')
-169     # . . push args
-170     68/push  0x20/imm32/space
-171     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-172     # . . call
-173     e8/call  skip-chars-matching/disp32
-174     # . . discard args
-175     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-176     # out->start = &line->data[line->read]
-177     8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(ESI+4) to ECX
-178     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
-179     89/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           0/r32/EAX   .               .                 # copy EAX to *EDI
-180     # if line->data[line->read] == '#': out->end = &line->data[line->write]), skip rest of stream and return
-181     # . EAX = line->data[line->read]
-182     31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-183     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
-184     # . compare
-185     3d/compare-EAX-with  0x23/imm32/pound
-186     75/jump-if-not-equal  $next-word:not-comment/disp8
-187     # . out->end = &line->data[line->write]
-188     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # copy *ESI to EAX
-189     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
-190     89/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(EDI+4)
-191     # . line->read = line->write
-192     89/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(ESI+4)
-193     # . return
-194     eb/jump  $next-word:end/disp8
-195 $next-word:not-comment:
-196     # otherwise skip-chars-not-matching(line, ' ')
-197     # . . push args
-198     68/push  0x20/imm32/space
-199     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
-200     # . . call
-201     e8/call  skip-chars-not-matching/disp32
-202     # . . discard args
-203     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-204     # out->end = &line->data[line->read]
-205     8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(ESI+4) to ECX
-206     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
-207     89/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(EDI+4)
-208 $next-word:end:
-209     # . restore registers
-210     5f/pop-to-EDI
-211     5e/pop-to-ESI
-212     59/pop-to-ECX
-213     58/pop-to-EAX
-214     # . epilog
-215     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-216     5d/pop-to-EBP
-217     c3/return
-218 
-219 test-next-word:
-220     # . prolog
-221     55/push-EBP
-222     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-223     # setup
-224     # . clear-stream(_test-stream)
-225     # . . push args
-226     68/push  _test-stream/imm32
-227     # . . call
-228     e8/call  clear-stream/disp32
-229     # . . discard args
-230     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-231     # var slice/ECX = {0, 0}
-232     68/push  0/imm32/end
-233     68/push  0/imm32/start
-234     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-235     # write(_test-stream, "  ab")
-236     # . . push args
-237     68/push  "  ab"/imm32
-238     68/push  _test-stream/imm32
-239     # . . call
-240     e8/call  write/disp32
-241     # . . discard args
-242     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-243     # next-word(_test-stream, slice)
-244     # . . push args
-245     51/push-ECX
-246     68/push  _test-stream/imm32
-247     # . . call
-248     e8/call  next-word/disp32
-249     # . . discard args
-250     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-251     # check-ints-equal(slice->start - _test-stream->data, 2, msg)
-252     # . check-ints-equal(slice->start - _test-stream, 14, msg)
-253     # . . push args
-254     68/push  "F - test-next-word: start"/imm32
-255     68/push  0xe/imm32
-256     # . . push slice->start - _test-stream
-257     8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy *ECX to EAX
-258     81          5/subop/subtract    3/mod/direct    0/rm32/EAX    .           .             .           .           .               _test-stream/imm32 # subtract from EAX
-259     50/push-EAX
-260     # . . call
-261     e8/call  check-ints-equal/disp32
-262     # . . discard args
-263     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-264     # check-ints-equal(slice->end - _test-stream->data, 4, msg)
-265     # . check-ints-equal(slice->end - _test-stream, 16, msg)
-266     # . . push args
-267     68/push  "F - test-next-word: end"/imm32
-268     68/push  0x10/imm32
-269     # . . push slice->end - _test-stream
-270     8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ECX+4) to EAX
-271     81          5/subop/subtract    3/mod/direct    0/rm32/EAX    .           .             .           .           .               _test-stream/imm32 # subtract from EAX
-272     50/push-EAX
-273     # . . call
-274     e8/call  check-ints-equal/disp32
-275     # . . discard args
-276     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-277     # . epilog
-278     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-279     5d/pop-to-EBP
-280     c3/return
-281 
-282 test-next-word-returns-whole-comment:
-283     # . prolog
-284     55/push-EBP
-285     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-286     # setup
-287     # . clear-stream(_test-stream)
-288     # . . push args
-289     68/push  _test-stream/imm32
-290     # . . call
-291     e8/call  clear-stream/disp32
-292     # . . discard args
-293     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-294     # var slice/ECX = {0, 0}
-295     68/push  0/imm32/end
-296     68/push  0/imm32/start
-297     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-298     # write(_test-stream, "  # a")
-299     # . . push args
-300     68/push  "  # a"/imm32
-301     68/push  _test-stream/imm32
-302     # . . call
-303     e8/call  write/disp32
-304     # . . discard args
-305     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-306     # next-word(_test-stream, slice)
-307     # . . push args
-308     51/push-ECX
-309     68/push  _test-stream/imm32
-310     # . . call
-311     e8/call  next-word/disp32
-312     # . . discard args
-313     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-314     # check-ints-equal(slice->start - _test-stream->data, 2, msg)
-315     # . check-ints-equal(slice->start - _test-stream, 14, msg)
-316     # . . push args
-317     68/push  "F - test-next-word-returns-whole-comment: start"/imm32
-318     68/push  0xe/imm32
-319     # . . push slice->start - _test-stream
-320     8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy *ECX to EAX
-321     81          5/subop/subtract    3/mod/direct    0/rm32/EAX    .           .             .           .           .               _test-stream/imm32 # subtract from EAX
-322     50/push-EAX
-323     # . . call
-324     e8/call  check-ints-equal/disp32
-325     # . . discard args
-326     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-327     # check-ints-equal(slice->end - _test-stream->data, 5, msg)
-328     # . check-ints-equal(slice->end - _test-stream, 17, msg)
-329     # . . push args
-330     68/push  "F - test-next-word-returns-whole-comment: end"/imm32
-331     68/push  0x11/imm32
-332     # . . push slice->end - _test-stream
-333     8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ECX+4) to EAX
-334     81          5/subop/subtract    3/mod/direct    0/rm32/EAX    .           .             .           .           .               _test-stream/imm32 # subtract from EAX
-335     50/push-EAX
-336     # . . call
-337     e8/call  check-ints-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 # print 'n' in hex in 'width' bytes in lower-endian order, with a space after every byte
-346 emit-hex:  # out : (address buffered-file), n : int, width : int
-347     # . prolog
-348     55/push-EBP
-349     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-350     # . save registers
-351     50/push-EAX
-352     51/push-ECX
-353     52/push-EDX
-354     53/push-EBX
-355     57/push-EDI
-356     # EDI = out
-357     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   8/disp8         .                 # copy *(EBP+8) to EDI
-358     # EBX = n
-359     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           3/r32/EBX   0xc/disp8       .                 # copy *(EBP+12) to EBX
-360     # EDX = width
-361     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           2/r32/EDX   0x10/disp8      .                 # copy *(EBP+16) to EDX
-362     # var curr/ECX = 0
-363     31/xor                          3/mod/direct    1/rm32/ECX    .           .             .           1/r32/ECX   .               .                 # clear ECX
-364 $emit-hex:loop:
-365     # if (curr >= width) break
-366     39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX and EDX
-367     7d/jump-if-greater-or-equal  $emit-hex:end/disp8
-368     # print-byte(out, EBX)
-369     # . . push args
-370     53/push-EBX
-371     57/push-EDI
-372     # . . call
-373     e8/call  print-byte/disp32
-374     # . . discard args
-375     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-376     # write-byte(out, ' ')
-377     # . . push args
-378     68/push  0x20/imm32/space
-379     57/push-EDI
-380     # . . call
-381     e8/call  write-byte/disp32
-382     # . . discard args
-383     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-384     # EBX = EBX >> 8
-385     c1/shift    5/subop/logic-right 3/mod/direct    3/rm32/EBX    .           .             .           .           .               8/imm8            # shift EBX right by 8 bits, while padding zeroes
-386     # ++curr
-387     41/increment-ECX
-388     eb/jump  $emit-hex:loop/disp8
-389 $emit-hex:end:
-390     # . restore registers
-391     5f/pop-to-EDI
-392     5b/pop-to-EBX
-393     5a/pop-to-EAX
-394     59/pop-to-ECX
-395     58/pop-to-EAX
-396     # . epilog
-397     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-398     5d/pop-to-EBP
-399     c3/return
-400 
-401 test-emit-hex-single-byte:
-402     # setup
-403     # . clear-stream(_test-stream)
-404     # . . push args
-405     68/push  _test-stream/imm32
-406     # . . call
-407     e8/call  clear-stream/disp32
-408     # . . discard args
-409     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-410     # . clear-stream(_test-buffered-file+4)
-411     # . . push args
-412     b8/copy-to-EAX  _test-buffered-file/imm32
-413     05/add-to-EAX  4/imm32
-414     50/push-EAX
-415     # . . call
-416     e8/call  clear-stream/disp32
-417     # . . discard args
-418     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-419     # emit-hex(_test-buffered-file, 0xab, 1)
-420     # . . push args
-421     68/push  1/imm32
-422     68/push  0xab/imm32
-423     68/push  _test-buffered-file/imm32
-424     # . . call
-425     e8/call  emit-hex/disp32
-426     # . . discard args
-427     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-428     # flush(_test-buffered-file)
-429     # . . push args
-430     68/push  _test-buffered-file/imm32
-431     # . . call
-432     e8/call  flush/disp32
-433     # . . discard args
-434     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-435     # check-ints-equal(*_test-stream->data, 'ab ', msg)
-436     # . . push args
-437     68/push  "F - test-emit-hex-single-byte"/imm32
-438     68/push  0x206261/imm32
-439     # . . push *_test-stream->data
-440     b8/copy-to-EAX  _test-stream/imm32
-441     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
-442     # . . call
-443     e8/call  check-ints-equal/disp32
-444     # . . discard args
-445     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-446     # . end
-447     c3/return
-448 
-449 test-emit-hex-multiple-byte:
-450     # setup
-451     # . clear-stream(_test-stream)
-452     # . . push args
-453     68/push  _test-stream/imm32
-454     # . . call
-455     e8/call  clear-stream/disp32
-456     # . . discard args
-457     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-458     # . clear-stream(_test-buffered-file+4)
-459     # . . push args
-460     b8/copy-to-EAX  _test-buffered-file/imm32
-461     05/add-to-EAX  4/imm32
-462     50/push-EAX
-463     # . . call
-464     e8/call  clear-stream/disp32
-465     # . . discard args
-466     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-467     # emit-hex(_test-buffered-file, 0x1234, 2)
-468     # . . push args
-469     68/push  2/imm32
-470     68/push  0x1234/imm32
-471     68/push  _test-buffered-file/imm32
-472     # . . call
-473     e8/call  emit-hex/disp32
-474     # . . discard args
-475     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-476     # flush(_test-buffered-file)
-477     # . . push args
-478     68/push  _test-buffered-file/imm32
-479     # . . call
-480     e8/call  flush/disp32
-481     # . . discard args
-482     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-483     # check(_test-stream->data == '34 12 ')
-484     # . check-ints-equal(_test-stream->data[0..3], '34 1', msg)
-485     # . . push args
-486     68/push  "F - test-emit-hex-multiple-byte/1"/imm32
-487     68/push  0x31203433/imm32
-488     # . . push *_test-stream->data
-489     b8/copy-to-EAX  _test-stream/imm32
-490     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
-491     # . . call
-492     e8/call  check-ints-equal/disp32
-493     # . . discard args
-494     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-495     # . check-ints-equal(_test-stream->data[4..7], '2 ', msg)
-496     # . . push args
-497     68/push  "F - test-emit-hex-multiple-byte/2"/imm32
-498     68/push  0x2032/imm32
-499     # . . push *_test-stream->data
-500     b8/copy-to-EAX  _test-stream/imm32
-501     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0x10/disp8      .                 # push *(EAX+16)
-502     # . . call
-503     e8/call  check-ints-equal/disp32
-504     # . . discard args
-505     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-506     # . end
-507     c3/return
-508     # . end
-509     c3/return
-510 
-511 test-emit-hex-zero-pad:
-512     # setup
-513     # . clear-stream(_test-stream)
-514     # . . push args
-515     68/push  _test-stream/imm32
-516     # . . call
-517     e8/call  clear-stream/disp32
-518     # . . discard args
-519     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-520     # . clear-stream(_test-buffered-file+4)
-521     # . . push args
-522     b8/copy-to-EAX  _test-buffered-file/imm32
-523     05/add-to-EAX  4/imm32
-524     50/push-EAX
-525     # . . call
-526     e8/call  clear-stream/disp32
-527     # . . discard args
-528     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-529     # emit-hex(_test-buffered-file, 0xab, 2)
-530     # . . push args
-531     68/push  2/imm32
-532     68/push  0xab/imm32
-533     68/push  _test-buffered-file/imm32
-534     # . . call
-535     e8/call  emit-hex/disp32
-536     # . . discard args
-537     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-538     # flush(_test-buffered-file)
-539     # . . push args
-540     68/push  _test-buffered-file/imm32
-541     # . . call
-542     e8/call  flush/disp32
-543     # . . discard args
-544     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-545     # check(_test-stream->data == '00 ab')
-546     # . check-ints-equal(*_test-stream->data, 'ab 0', msg)
-547     # . . push args
-548     68/push  "F - test-emit-hex-zero-pad/1"/imm32
-549     68/push  0x30206261/imm32
-550     # . . push *_test-stream->data
-551     b8/copy-to-EAX  _test-stream/imm32
-552     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
-553     # . . call
-554     e8/call  check-ints-equal/disp32
-555     # . . discard args
-556     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-557     # . check-ints-equal(*_test-stream->data[1], '0 ', msg)
-558     # . . push args
-559     68/push  "F - test-emit-hex-zero-pad/2"/imm32
-560     68/push  0x2030/imm32
-561     # . . push *_test-stream->data
-562     b8/copy-to-EAX  _test-stream/imm32
-563     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0x10/disp8      .                 # push *(EAX+16)
-564     # . . call
-565     e8/call  check-ints-equal/disp32
-566     # . . discard args
-567     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-568     # . end
-569     c3/return
-570 
-571 # . . vim:nowrap:textwidth=0
+   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-emit-non-number-with-metadata/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 #   skip segment headers
+  80 #   pack every instruction in the code segment
+  81 #   skip other segments
+  82 
+  83 # - To pack an instruction, following the C++ version:
+  84 # read line
+  85 # parse words
+  86 # read first word as opcode and write-slice
+  87 # if 0f or f2 or f3 read second opcode and write-slice
+  88 # if 'f2 0f' or 'f3 0f' read third opcode and write-slice
+  89 # while true:
+  90 #   word-slice = next-word
+  91 #   if empty(word-slice) break
+  92 #   if has metadata 'mod', parse into mod
+  93 #   if has metadata 'rm32', parse into rm32
+  94 #   if has metadata 'r32', parse into r32
+  95 #   if has metadata 'subop', parse into r32
+  96 # if at least one of the 3 was present, print-byte
+  97 # while true:
+  98 #   word-slice = next-word
+  99 #   if empty(word-slice) break
+ 100 #   if has metadata 'base', parse into base
+ 101 #   if has metadata 'index', parse into index
+ 102 #   if has metadata 'scale', parse into scale
+ 103 # if at least one of the 3 was present, print-byte
+ 104 # parse errors => <abort>
+ 105 # while true:
+ 106 #   word-slice = next-word
+ 107 #   if empty(word-slice) break
+ 108 #   if has metadata 'disp8', emit as 1 byte
+ 109 #   if has metadata 'disp16', emit as 2 bytes
+ 110 #   if has metadata 'disp32', emit as 4 bytes
+ 111 # while true:
+ 112 #   word-slice = next-word
+ 113 #   if empty(word-slice) break
+ 114 #   if has metadata 'imm8', emit
+ 115 #   if has metadata 'imm32', emit as 4 bytes
+ 116 # finally, emit line prefixed with a '  # '
+ 117 
+ 118 # simplifications since we perform zero error handling (continuing to rely on the C++ version for that):
+ 119 #   missing fields are always 0-filled
+ 120 #   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.
+ 121 #   in case of conflict, last operand with a name is recognized
+ 122 #   silently drop extraneous operands
+ 123 #   unceremoniously abort on non-numeric operands except disp or imm
+ 124 
+ 125 # primary state: line
+ 126 #   stream of 512 bytes; abort if it ever overflows
+ 127 #
+ 128 # conceptual hierarchy within a line:
+ 129 #   line = words separated by ' ', maybe followed by comment starting with '#'
+ 130 #   word = name until '/', then 0 or more metadata separated by '/'
+ 131 #
+ 132 # we won't bother saving the internal structure of lines; reparsing should be cheap using two primitives:
+ 133 #   next-token(stream, delim char) -> slice (start, end pointers)
+ 134 #   next-token(stream, slice, delim char) -> slice'
+ 135 #   slice-equal?(slice, string)
+ 136 
+ 137 convert:  # in : (address buffered-file), out : (address buffered-file), err : (address buffered-file), ed : (address exit-descriptor) -> <void>
+ 138     # pseudocode:
+ 139     #   line = new-stream(512, 1)
+ 140     #   repeatedly
+ 141     #     clear-stream(line)
+ 142     #     EAX = read-line(in, line, err, ed)
+ 143     #     if EAX == EOF break
+ 144     #     convert-instruction(line, out, err, ed)
+ 145     #   flush(out)
+ 146     #
+ 147     # . prolog
+ 148     55/push-EBP
+ 149     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+ 150     # . save registers
+ 151     # . restore registers
+ 152     # . epilog
+ 153     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+ 154     5d/pop-to-EBP
+ 155     c3/return
+ 156 
+ 157 # (re)compute the bounds of the next word in the line
+ 158 next-word:  # line : (address stream byte), out : (address slice)
+ 159     # . prolog
+ 160     55/push-EBP
+ 161     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+ 162     # . save registers
+ 163     50/push-EAX
+ 164     51/push-ECX
+ 165     56/push-ESI
+ 166     57/push-EDI
+ 167     # ESI = line
+ 168     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
+ 169     # EDI = out
+ 170     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   0xc/disp8       .                 # copy *(EBP+12) to EDI
+ 171     # skip-chars-matching(line, ' ')
+ 172     # . . push args
+ 173     68/push  0x20/imm32/space
+ 174     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
+ 175     # . . call
+ 176     e8/call  skip-chars-matching/disp32
+ 177     # . . discard args
+ 178     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+ 179     # out->start = &line->data[line->read]
+ 180     8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(ESI+4) to ECX
+ 181     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
+ 182     89/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           0/r32/EAX   .               .                 # copy EAX to *EDI
+ 183     # if line->data[line->read] == '#': out->end = &line->data[line->write]), skip rest of stream and return
+ 184     # . EAX = line->data[line->read]
+ 185     31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
+ 186     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
+ 187     # . compare
+ 188     3d/compare-EAX-with  0x23/imm32/pound
+ 189     75/jump-if-not-equal  $next-word:not-comment/disp8
+ 190     # . out->end = &line->data[line->write]
+ 191     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # copy *ESI to EAX
+ 192     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
+ 193     89/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(EDI+4)
+ 194     # . line->read = line->write
+ 195     89/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(ESI+4)
+ 196     # . return
+ 197     eb/jump  $next-word:end/disp8
+ 198 $next-word:not-comment:
+ 199     # otherwise skip-chars-not-matching(line, ' ')
+ 200     # . . push args
+ 201     68/push  0x20/imm32/space
+ 202     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
+ 203     # . . call
+ 204     e8/call  skip-chars-not-matching/disp32
+ 205     # . . discard args
+ 206     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+ 207     # out->end = &line->data[line->read]
+ 208     8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(ESI+4) to ECX
+ 209     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
+ 210     89/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(EDI+4)
+ 211 $next-word:end:
+ 212     # . restore registers
+ 213     5f/pop-to-EDI
+ 214     5e/pop-to-ESI
+ 215     59/pop-to-ECX
+ 216     58/pop-to-EAX
+ 217     # . epilog
+ 218     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+ 219     5d/pop-to-EBP
+ 220     c3/return
+ 221 
+ 222 test-next-word:
+ 223     # . prolog
+ 224     55/push-EBP
+ 225     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+ 226     # setup
+ 227     # . clear-stream(_test-stream)
+ 228     # . . push args
+ 229     68/push  _test-stream/imm32
+ 230     # . . call
+ 231     e8/call  clear-stream/disp32
+ 232     # . . discard args
+ 233     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+ 234     # var slice/ECX = {0, 0}
+ 235     68/push  0/imm32/end
+ 236     68/push  0/imm32/start
+ 237     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
+ 238     # write(_test-stream, "  ab")
+ 239     # . . push args
+ 240     68/push  "  ab"/imm32
+ 241     68/push  _test-stream/imm32
+ 242     # . . call
+ 243     e8/call  write/disp32
+ 244     # . . discard args
+ 245     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+ 246     # next-word(_test-stream, slice)
+ 247     # . . push args
+ 248     51/push-ECX
+ 249     68/push  _test-stream/imm32
+ 250     # . . call
+ 251     e8/call  next-word/disp32
+ 252     # . . discard args
+ 253     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+ 254     # check-ints-equal(slice->start - _test-stream->data, 2, msg)
+ 255     # . check-ints-equal(slice->start - _test-stream, 14, msg)
+ 256     # . . push args
+ 257     68/push  "F - test-next-word: start"/imm32
+ 258     68/push  0xe/imm32
+ 259     # . . push slice->start - _test-stream
+ 260     8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy *ECX to EAX
+ 261     81          5/subop/subtract    3/mod/direct    0/rm32/EAX    .           .             .           .           .               _test-stream/imm32 # subtract from EAX
+ 262     50/push-EAX
+ 263     # . . call
+ 264     e8/call  check-ints-equal/disp32
+ 265     # . . discard args
+ 266     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+ 267     # check-ints-equal(slice->end - _test-stream->data, 4, msg)
+ 268     # . check-ints-equal(slice->end - _test-stream, 16, msg)
+ 269     # . . push args
+ 270     68/push  "F - test-next-word: end"/imm32
+ 271     68/push  0x10/imm32
+ 272     # . . push slice->end - _test-stream
+ 273     8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ECX+4) to EAX
+ 274     81          5/subop/subtract    3/mod/direct    0/rm32/EAX    .           .             .           .           .               _test-stream/imm32 # subtract from EAX
+ 275     50/push-EAX
+ 276     # . . call
+ 277     e8/call  check-ints-equal/disp32
+ 278     # . . discard args
+ 279     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+ 280     # . epilog
+ 281     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+ 282     5d/pop-to-EBP
+ 283     c3/return
+ 284 
+ 285 test-next-word-returns-whole-comment:
+ 286     # . prolog
+ 287     55/push-EBP
+ 288     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+ 289     # setup
+ 290     # . clear-stream(_test-stream)
+ 291     # . . push args
+ 292     68/push  _test-stream/imm32
+ 293     # . . call
+ 294     e8/call  clear-stream/disp32
+ 295     # . . discard args
+ 296     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+ 297     # var slice/ECX = {0, 0}
+ 298     68/push  0/imm32/end
+ 299     68/push  0/imm32/start
+ 300     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
+ 301     # write(_test-stream, "  # a")
+ 302     # . . push args
+ 303     68/push  "  # a"/imm32
+ 304     68/push  _test-stream/imm32
+ 305     # . . call
+ 306     e8/call  write/disp32
+ 307     # . . discard args
+ 308     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+ 309     # next-word(_test-stream, slice)
+ 310     # . . push args
+ 311     51/push-ECX
+ 312     68/push  _test-stream/imm32
+ 313     # . . call
+ 314     e8/call  next-word/disp32
+ 315     # . . discard args
+ 316     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+ 317     # check-ints-equal(slice->start - _test-stream->data, 2, msg)
+ 318     # . check-ints-equal(slice->start - _test-stream, 14, msg)
+ 319     # . . push args
+ 320     68/push  "F - test-next-word-returns-whole-comment: start"/imm32
+ 321     68/push  0xe/imm32
+ 322     # . . push slice->start - _test-stream
+ 323     8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy *ECX to EAX
+ 324     81          5/subop/subtract    3/mod/direct    0/rm32/EAX    .           .             .           .           .               _test-stream/imm32 # subtract from EAX
+ 325     50/push-EAX
+ 326     # . . call
+ 327     e8/call  check-ints-equal/disp32
+ 328     # . . discard args
+ 329     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+ 330     # check-ints-equal(slice->end - _test-stream->data, 5, msg)
+ 331     # . check-ints-equal(slice->end - _test-stream, 17, msg)
+ 332     # . . push args
+ 333     68/push  "F - test-next-word-returns-whole-comment: end"/imm32
+ 334     68/push  0x11/imm32
+ 335     # . . push slice->end - _test-stream
+ 336     8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ECX+4) to EAX
+ 337     81          5/subop/subtract    3/mod/direct    0/rm32/EAX    .           .             .           .           .               _test-stream/imm32 # subtract from EAX
+ 338     50/push-EAX
+ 339     # . . call
+ 340     e8/call  check-ints-equal/disp32
+ 341     # . . discard args
+ 342     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+ 343     # . epilog
+ 344     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+ 345     5d/pop-to-EBP
+ 346     c3/return
+ 347 
+ 348 has-metadata?:  # word : (address slice), s : (address string) -> EAX : boolean
+ 349     # pseudocode:
+ 350     #   var twig : &slice = next-token-from-slice(word->start, word->end, '/')  # skip name
+ 351     #   curr = twig->end
+ 352     #   while true:
+ 353     #     twig = next-token-from-slice(curr, word->end, '/')
+ 354     #     if twig.empty() break
+ 355     #     if slice-equal?(twig, s) return true
+ 356     #     curr = twig->end
+ 357     #   return false
+ 358     # . prolog
+ 359     55/push-EBP
+ 360     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+ 361     # . save registers
+ 362     51/push-ECX
+ 363     52/push-EDX
+ 364     56/push-ESI
+ 365     57/push-EDI
+ 366     # ESI = word
+ 367     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
+ 368     # EDX = word->end
+ 369     8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           2/r32/EDX   4/disp8         .                 # copy *(ESI+4) to EDX
+ 370     # var twig/EDI : (address slice) = {0, 0}
+ 371     68/push  0/imm32/end
+ 372     68/push  0/imm32/start
+ 373     89/copy                         3/mod/direct    7/rm32/EDI    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDI
+ 374     # next-token-from-slice(word->start, word->end, '/', twig)
+ 375     # . . push args
+ 376     57/push-EDI
+ 377     68/push  0x2f/imm32/slash
+ 378     52/push-EDX
+ 379     ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
+ 380     # . . call
+ 381     e8/call  next-token-from-slice/disp32
+ 382     # . . discard args
+ 383     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
+ 384     # curr/ECX = twig->end
+ 385     8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(EDI+4) to ECX
+ 386 $has-metadata?:loop:
+ 387     # next-token-from-slice(curr, word->end, '/', twig)
+ 388     # . . push args
+ 389     57/push-EDI
+ 390     68/push  0x2f/imm32/slash
+ 391     52/push-EDX
+ 392     51/push-ECX
+ 393     # . . call
+ 394     e8/call  next-token-from-slice/disp32
+ 395     # . . discard args
+ 396     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
+ 397     # if slice-empty?(twig) return false
+ 398     # . EAX = slice-empty?(twig)
+ 399     # . . push args
+ 400     57/push-EDI
+ 401     # . . call
+ 402     e8/call  slice-empty?/disp32
+ 403     # . . discard args
+ 404     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+ 405     # . if (EAX != 0) return false
+ 406     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # compare EAX
+ 407     75/compare-if-not-equal  $has-metadata?:false/disp8
+ 408     # if slice-equal?(twig, s) return true
+ 409     # . EAX = slice-equal?(twig, s)
+ 410     # . . push args
+ 411     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
+ 412     57/push-EDI
+ 413     # . . call
+ 414     e8/call  slice-equal?/disp32
+ 415     # . . discard args
+ 416     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+ 417     # . if (EAX != 0) return true
+ 418     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # compare EAX
+ 419     75/compare-if-not-equal  $has-metadata?:true/disp8
+ 420     # curr = twig->end
+ 421     8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(EDI+4) to ECX
+ 422     eb/jump  $has-metadata?:loop/disp8
+ 423 $has-metadata?:true:
+ 424     b8/copy-to-EAX  1/imm32/true
+ 425     eb/jump  $has-metadata?:end/disp8
+ 426 $has-metadata?:false:
+ 427     b8/copy-to-EAX  0/imm32/false
+ 428 $has-metadata?:end:
+ 429     # . reclaim locals
+ 430     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+ 431     # . restore registers
+ 432     5f/pop-to-EDI
+ 433     5e/pop-to-ESI
+ 434     5a/pop-to-EDX
+ 435     59/pop-to-ECX
+ 436     # . epilog
+ 437     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+ 438     5d/pop-to-EBP
+ 439     c3/return
+ 440 
+ 441 test-has-metadata-true:
+ 442     # . prolog
+ 443     55/push-EBP
+ 444     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+ 445     # (EAX..ECX) = "ab/c"
+ 446     b8/copy-to-EAX  "ab/c"/imm32
+ 447     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
+ 448     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
+ 449     05/add-to-EAX  4/imm32
+ 450     # var in/ESI : (address slice) = {EAX, ECX}
+ 451     51/push-ECX
+ 452     50/push-EAX
+ 453     89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
+ 454     # EAX = has-metadata?(ESI, "c")
+ 455     # . . push args
+ 456     68/push  "c"/imm32
+ 457     56/push-ESI
+ 458     # . . call
+ 459     e8/call  has-metadata?/disp32
+ 460     # . . discard args
+ 461     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x8/imm32         # add to ESP
+ 462     # check-ints-equal(EAX, 1, msg)
+ 463     # . . push args
+ 464     68/push  "F - test-has-metadata-true"/imm32
+ 465     68/push  1/imm32/true
+ 466     50/push-EAX
+ 467     # . . call
+ 468     e8/call  check-ints-equal/disp32
+ 469     # . . discard args
+ 470     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+ 471     # . epilog
+ 472     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+ 473     5d/pop-to-EBP
+ 474     c3/return
+ 475 
+ 476 test-has-metadata-false:
+ 477     # . prolog
+ 478     55/push-EBP
+ 479     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+ 480     # (EAX..ECX) = "ab/c"
+ 481     b8/copy-to-EAX  "ab/c"/imm32
+ 482     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
+ 483     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
+ 484     05/add-to-EAX  4/imm32
+ 485     # var in/ESI : (address slice) = {EAX, ECX}
+ 486     51/push-ECX
+ 487     50/push-EAX
+ 488     89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
+ 489     # EAX = has-metadata?(ESI, "c")
+ 490     # . . push args
+ 491     68/push  "d"/imm32
+ 492     56/push-ESI
+ 493     # . . call
+ 494     e8/call  has-metadata?/disp32
+ 495     # . . discard args
+ 496     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x8/imm32         # add to ESP
+ 497     # check-ints-equal(EAX, 0, msg)
+ 498     # . . push args
+ 499     68/push  "F - test-has-metadata-false"/imm32
+ 500     68/push  0/imm32/false
+ 501     50/push-EAX
+ 502     # . . call
+ 503     e8/call  check-ints-equal/disp32
+ 504     # . . discard args
+ 505     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+ 506     # . epilog
+ 507     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+ 508     5d/pop-to-EBP
+ 509     c3/return
+ 510 
+ 511 test-has-metadata-ignore-name:
+ 512     # . prolog
+ 513     55/push-EBP
+ 514     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+ 515     # (EAX..ECX) = "a/b"
+ 516     b8/copy-to-EAX  "a/b"/imm32
+ 517     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
+ 518     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
+ 519     05/add-to-EAX  4/imm32
+ 520     # var in/ESI : (address slice) = {EAX, ECX}
+ 521     51/push-ECX
+ 522     50/push-EAX
+ 523     89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
+ 524     # EAX = has-metadata?(ESI, "a")
+ 525     # . . push args
+ 526     68/push  "a"/imm32
+ 527     56/push-ESI
+ 528     # . . call
+ 529     e8/call  has-metadata?/disp32
+ 530     # . . discard args
+ 531     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x8/imm32         # add to ESP
+ 532     # check-ints-equal(EAX, 0, msg)
+ 533     # . . push args
+ 534     68/push  "F - test-has-metadata-ignore-name"/imm32
+ 535     68/push  0/imm32/false
+ 536     50/push-EAX
+ 537     # . . call
+ 538     e8/call  check-ints-equal/disp32
+ 539     # . . discard args
+ 540     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+ 541     # . epilog
+ 542     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+ 543     5d/pop-to-EBP
+ 544     c3/return
+ 545 
+ 546 test-has-metadata-multiple-true:
+ 547     # . prolog
+ 548     55/push-EBP
+ 549     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+ 550     # (EAX..ECX) = "a/b/c"
+ 551     b8/copy-to-EAX  "a/b/c"/imm32
+ 552     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
+ 553     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
+ 554     05/add-to-EAX  4/imm32
+ 555     # var in/ESI : (address slice) = {EAX, ECX}
+ 556     51/push-ECX
+ 557     50/push-EAX
+ 558     89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
+ 559     # EAX = has-metadata?(ESI, "c")
+ 560     # . . push args
+ 561     68/push  "c"/imm32
+ 562     56/push-ESI
+ 563     # . . call
+ 564     e8/call  has-metadata?/disp32
+ 565     # . . discard args
+ 566     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x8/imm32         # add to ESP
+ 567     # check-ints-equal(EAX, 1, msg)
+ 568     # . . push args
+ 569     68/push  "F - test-has-metadata-multiple-true"/imm32
+ 570     68/push  1/imm32/true
+ 571     50/push-EAX
+ 572     # . . call
+ 573     e8/call  check-ints-equal/disp32
+ 574     # . . discard args
+ 575     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+ 576     # . epilog
+ 577     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+ 578     5d/pop-to-EBP
+ 579     c3/return
+ 580 
+ 581 test-has-metadata-multiple-false:
+ 582     # . prolog
+ 583     55/push-EBP
+ 584     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+ 585     # (EAX..ECX) = "a/b/c"
+ 586     b8/copy-to-EAX  "a/b/c"/imm32
+ 587     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
+ 588     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
+ 589     05/add-to-EAX  4/imm32
+ 590     # var in/ESI : (address slice) = {EAX, ECX}
+ 591     51/push-ECX
+ 592     50/push-EAX
+ 593     89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
+ 594     # EAX = has-metadata?(ESI, "d")
+ 595     # . . push args
+ 596     68/push  "d"/imm32
+ 597     56/push-ESI
+ 598     # . . call
+ 599     e8/call  has-metadata?/disp32
+ 600     # . . discard args
+ 601     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x8/imm32         # add to ESP
+ 602     # check-ints-equal(EAX, 0, msg)
+ 603     # . . push args
+ 604     68/push  "F - test-has-metadata-multiple-false"/imm32
+ 605     68/push  0/imm32/false
+ 606     50/push-EAX
+ 607     # . . call
+ 608     e8/call  check-ints-equal/disp32
+ 609     # . . discard args
+ 610     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+ 611     # . epilog
+ 612     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+ 613     5d/pop-to-EBP
+ 614     c3/return
+ 615 
+ 616 # if the name of 'word' is all hex digits, parse and emit it in 'width' bytes
+ 617 # of hex otherwise just write-slice
+ 618 emit:  # out : (address buffered-file), word : (address slice), width : int
+ 619     # . prolog
+ 620     55/push-EBP
+ 621     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+ 622     # . save registers
+ 623     50/push-EAX
+ 624     56/push-ESI
+ 625     57/push-EDI
+ 626     # ESI = word
+ 627     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   0xc/disp8       .                 # copy *(EBP+12) to ESI
+ 628     # var name/EDI : (address slice) = {0, 0}
+ 629     68/push  0/imm32/end
+ 630     68/push  0/imm32/start
+ 631     89/copy                         3/mod/direct    7/rm32/EDI    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDI
+ 632     # name = next-token-from-slice(word->start, word->end, '/')
+ 633     # . . push args
+ 634     57/push-EDI
+ 635     68/push  0x2f/imm32/slash
+ 636     ff          6/subop/push        1/mod/*+disp8   6/rm32/ESI    .           .             .           .           4/disp8         .                 # push *(ESI+4)
+ 637     ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
+ 638     # . . call
+ 639     e8/call  next-token-from-slice/disp32
+ 640     # . . discard args
+ 641     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
+ 642     # if !is-hex-int?(name) write-slice(out, word) and return
+ 643     # . is-hex-int?(name)
+ 644     # . . push args
+ 645     57/push-EDI
+ 646     # . . call
+ 647     e8/call  is-hex-int?/disp32
+ 648     # . . discard args
+ 649     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+ 650     # . if EAX == 0
+ 651     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # compare EAX
+ 652     75/jump-if-not-equal  $emit:hex-int/disp8
+ 653     # . write-slice(out, word)
+ 654     # . . push args
+ 655     56/push-ESI
+ 656     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
+ 657     # . . call
+ 658     e8/call  write-slice/disp32
+ 659     # . . discard args
+ 660     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+ 661     # . return
+ 662     eb/jump  $emit:end/disp8
+ 663     # otherwise emit-hex(out, parse-hex-int(name), width)
+ 664 $emit:hex-int:
+ 665     # . n/EAX = parse-hex-int(name)
+ 666     # . . push args
+ 667     57/push-EDI
+ 668     # . . call
+ 669     e8/call  parse-hex-int/disp32
+ 670     # . . discard args
+ 671     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+ 672     # . emit-hex(out, n, width)
+ 673     # . . push args
+ 674     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
+ 675     50/push-EAX
+ 676     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
+ 677     # . . call
+ 678     e8/call  emit-hex/disp32
+ 679     # . . discard args
+ 680     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+ 681 $emit:end:
+ 682     # . reclaim locals
+ 683     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+ 684     # . restore registers
+ 685     5f/pop-to-EDI
+ 686     5e/pop-to-ESI
+ 687     58/pop-to-EAX
+ 688     # . epilog
+ 689     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+ 690     5d/pop-to-EBP
+ 691     c3/return
+ 692 
+ 693 test-emit-number:
+ 694     # . prolog
+ 695     55/push-EBP
+ 696     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+ 697     # setup
+ 698     # . clear-stream(_test-stream)
+ 699     # . . push args
+ 700     68/push  _test-stream/imm32
+ 701     # . . call
+ 702     e8/call  clear-stream/disp32
+ 703     # . . discard args
+ 704     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+ 705     # . clear-stream(_test-buffered-file+4)
+ 706     # . . push args
+ 707     b8/copy-to-EAX  _test-buffered-file/imm32
+ 708     05/add-to-EAX  4/imm32
+ 709     50/push-EAX
+ 710     # . . call
+ 711     e8/call  clear-stream/disp32
+ 712     # . . discard args
+ 713     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+ 714     # var slice/ECX = "-2"
+ 715     68/push  _test-slice-negative-two-end/imm32/end
+ 716     68/push  _test-slice-negative-two/imm32/start
+ 717     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
+ 718     # emit(_test-buffered-file, slice, 2)
+ 719     # . . push args
+ 720     68/push  2/imm32
+ 721     51/push-ECX
+ 722     68/push  _test-buffered-file/imm32
+ 723     # . . call
+ 724     e8/call  emit/disp32
+ 725     # . . discard args
+ 726     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+ 727     # flush(_test-buffered-file)
+ 728     # . . push args
+ 729     68/push  _test-buffered-file/imm32
+ 730     # . . call
+ 731     e8/call  flush/disp32
+ 732     # . . discard args
+ 733     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+ 734     # check(_test-stream->data == 'fe ff ')
+ 735     # . check-ints-equal(_test-stream->data[0..3], 'fe f', msg)
+ 736     # . . push args
+ 737     68/push  "F - test-emit-number/1"/imm32
+ 738     68/push  0x66206566/imm32
+ 739     # . . push *_test-stream->data
+ 740     b8/copy-to-EAX  _test-stream/imm32
+ 741     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
+ 742     # . . call
+ 743     e8/call  check-ints-equal/disp32
+ 744     # . . discard args
+ 745     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+ 746     # . check-ints-equal(_test-stream->data[4..7], 'f ', msg)
+ 747     # . . push args
+ 748     68/push  "F - test-emit-number/2"/imm32
+ 749     68/push  0x2066/imm32
+ 750     # . . push *_test-stream->data
+ 751     b8/copy-to-EAX  _test-stream/imm32
+ 752     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0x10/disp8      .                 # push *(EAX+16)
+ 753     # . . call
+ 754     e8/call  check-ints-equal/disp32
+ 755     # . . discard args
+ 756     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+ 757     # . epilog
+ 758     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+ 759     5d/pop-to-EBP
+ 760     c3/return
+ 761 
+ 762 test-emit-number-with-metadata:
+ 763     # . prolog
+ 764     55/push-EBP
+ 765     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+ 766     # setup
+ 767     # . clear-stream(_test-stream)
+ 768     # . . push args
+ 769     68/push  _test-stream/imm32
+ 770     # . . call
+ 771     e8/call  clear-stream/disp32
+ 772     # . . discard args
+ 773     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+ 774     # . clear-stream(_test-buffered-file+4)
+ 775     # . . push args
+ 776     b8/copy-to-EAX  _test-buffered-file/imm32
+ 777     05/add-to-EAX  4/imm32
+ 778     50/push-EAX
+ 779     # . . call
+ 780     e8/call  clear-stream/disp32
+ 781     # . . discard args
+ 782     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+ 783     # var slice/ECX = "-2/foo"
+ 784     68/push  _test-slice-negative-two-metadata-end/imm32/end
+ 785     68/push  _test-slice-negative-two/imm32/start
+ 786     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
+ 787     # emit(_test-buffered-file, slice, 2)
+ 788     # . . push args
+ 789     68/push  2/imm32
+ 790     51/push-ECX
+ 791     68/push  _test-buffered-file/imm32
+ 792     # . . call
+ 793     e8/call  emit/disp32
+ 794     # . . discard args
+ 795     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+ 796     # flush(_test-buffered-file)
+ 797     # . . push args
+ 798     68/push  _test-buffered-file/imm32
+ 799     # . . call
+ 800     e8/call  flush/disp32
+ 801     # . . discard args
+ 802     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+ 803     # the '/foo' will have no impact on the output
+ 804     # check(_test-stream->data == 'fe ff ')
+ 805     # . check-ints-equal(_test-stream->data[0..3], 'fe f', msg)
+ 806     # . . push args
+ 807     68/push  "F - test-emit-number-with-metadata/1"/imm32
+ 808     68/push  0x66206566/imm32
+ 809     # . . push *_test-stream->data
+ 810     b8/copy-to-EAX  _test-stream/imm32
+ 811     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
+ 812     # . . call
+ 813     e8/call  check-ints-equal/disp32
+ 814     # . . discard args
+ 815     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+ 816     # . check-ints-equal(_test-stream->data[4..7], 'f ', msg)
+ 817     # . . push args
+ 818     68/push  "F - test-emit-number-with-metadata/2"/imm32
+ 819     68/push  0x2066/imm32
+ 820     # . . push *_test-stream->data
+ 821     b8/copy-to-EAX  _test-stream/imm32
+ 822     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0x10/disp8      .                 # push *(EAX+16)
+ 823     # . . call
+ 824     e8/call  check-ints-equal/disp32
+ 825     # . . discard args
+ 826     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+ 827     # . epilog
+ 828     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+ 829     5d/pop-to-EBP
+ 830     c3/return
+ 831 
+ 832 test-emit-non-number:
+ 833     # . prolog
+ 834     55/push-EBP
+ 835     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+ 836     # setup
+ 837     # . clear-stream(_test-stream)
+ 838     # . . push args
+ 839     68/push  _test-stream/imm32
+ 840     # . . call
+ 841     e8/call  clear-stream/disp32
+ 842     # . . discard args
+ 843     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+ 844     # . clear-stream(_test-buffered-file+4)
+ 845     # . . push args
+ 846     b8/copy-to-EAX  _test-buffered-file/imm32
+ 847     05/add-to-EAX  4/imm32
+ 848     50/push-EAX
+ 849     # . . call
+ 850     e8/call  clear-stream/disp32
+ 851     # . . discard args
+ 852     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+ 853     # var slice/ECX = "xyz"
+ 854     68/push  _test-slice-non-number-word-end/imm32/end
+ 855     68/push  _test-slice-non-number-word/imm32/start
+ 856     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
+ 857     # emit(_test-buffered-file, slice, 2)
+ 858     # . . push args
+ 859     68/push  2/imm32
+ 860     51/push-ECX
+ 861     68/push  _test-buffered-file/imm32
+ 862     # . . call
+ 863     e8/call  emit/disp32
+ 864     # . . discard args
+ 865     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+ 866     # flush(_test-buffered-file)
+ 867     # . . push args
+ 868     68/push  _test-buffered-file/imm32
+ 869     # . . call
+ 870     e8/call  flush/disp32
+ 871     # . . discard args
+ 872     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+ 873     # check(_test-stream->data == 'xyz')
+ 874     # . check-ints-equal(_test-stream->data[0..3], 'xyz', msg)
+ 875     # . . push args
+ 876     68/push  "F - test-emit-non-number"/imm32
+ 877     68/push  0x7a7978/imm32
+ 878     # . . push *_test-stream->data
+ 879     b8/copy-to-EAX  _test-stream/imm32
+ 880     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
+ 881     # . . call
+ 882     e8/call  check-ints-equal/disp32
+ 883     # . . discard args
+ 884     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+ 885     # . epilog
+ 886     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+ 887     5d/pop-to-EBP
+ 888     c3/return
+ 889 
+ 890 test-emit-non-number-with-metadata:
+ 891     # . prolog
+ 892     55/push-EBP
+ 893     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+ 894     # setup
+ 895     # . clear-stream(_test-stream)
+ 896     # . . push args
+ 897     68/push  _test-stream/imm32
+ 898     # . . call
+ 899     e8/call  clear-stream/disp32
+ 900     # . . discard args
+ 901     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+ 902     # . clear-stream(_test-buffered-file+4)
+ 903     # . . push args
+ 904     b8/copy-to-EAX  _test-buffered-file/imm32
+ 905     05/add-to-EAX  4/imm32
+ 906     50/push-EAX
+ 907     # . . call
+ 908     e8/call  clear-stream/disp32
+ 909     # . . discard args
+ 910     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+ 911     # var slice/ECX = "xyz/"
+ 912     68/push  _test-slice-non-number-word-metadata-end/imm32/end
+ 913     68/push  _test-slice-non-number-word/imm32/start
+ 914     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
+ 915     # emit(_test-buffered-file, slice, 2)
+ 916     # . . push args
+ 917     68/push  2/imm32
+ 918     51/push-ECX
+ 919     68/push  _test-buffered-file/imm32
+ 920     # . . call
+ 921     e8/call  emit/disp32
+ 922     # . . discard args
+ 923     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+ 924     # flush(_test-buffered-file)
+ 925     # . . push args
+ 926     68/push  _test-buffered-file/imm32
+ 927     # . . call
+ 928     e8/call  flush/disp32
+ 929     # . . discard args
+ 930     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+ 931     # check(_test-stream->data == 'xyz/')
+ 932     # . check-ints-equal(_test-stream->data[0..3], 'xyz/', msg)
+ 933     # . . push args
+ 934     68/push  "F - test-emit-non-number-with-metadata"/imm32
+ 935     68/push  0x2f7a7978/imm32
+ 936     # . . push *_test-stream->data
+ 937     b8/copy-to-EAX  _test-stream/imm32
+ 938     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
+ 939     # . . call
+ 940     e8/call  check-ints-equal/disp32
+ 941     # . . discard args
+ 942     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+ 943     # . epilog
+ 944     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+ 945     5d/pop-to-EBP
+ 946     c3/return
+ 947 
+ 948 # print 'n' in hex in 'width' bytes in lower-endian order, with a space after every byte
+ 949 emit-hex:  # out : (address buffered-file), n : int, width : int
+ 950     # . prolog
+ 951     55/push-EBP
+ 952     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+ 953     # . save registers
+ 954     50/push-EAX
+ 955     51/push-ECX
+ 956     52/push-EDX
+ 957     53/push-EBX
+ 958     57/push-EDI
+ 959     # EDI = out
+ 960     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   8/disp8         .                 # copy *(EBP+8) to EDI
+ 961     # EBX = n
+ 962     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           3/r32/EBX   0xc/disp8       .                 # copy *(EBP+12) to EBX
+ 963     # EDX = width
+ 964     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           2/r32/EDX   0x10/disp8      .                 # copy *(EBP+16) to EDX
+ 965     # var curr/ECX = 0
+ 966     31/xor                          3/mod/direct    1/rm32/ECX    .           .             .           1/r32/ECX   .               .                 # clear ECX
+ 967 $emit-hex:loop:
+ 968     # if (curr >= width) break
+ 969     39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX and EDX
+ 970     7d/jump-if-greater-or-equal  $emit-hex:end/disp8
+ 971     # print-byte(out, EBX)
+ 972     # . . push args
+ 973     53/push-EBX
+ 974     57/push-EDI
+ 975     # . . call
+ 976     e8/call  print-byte/disp32
+ 977     # . . discard args
+ 978     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+ 979     # write-byte(out, ' ')
+ 980     # . . push args
+ 981     68/push  0x20/imm32/space
+ 982     57/push-EDI
+ 983     # . . call
+ 984     e8/call  write-byte/disp32
+ 985     # . . discard args
+ 986     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+ 987     # EBX = EBX >> 8
+ 988     c1/shift    5/subop/logic-right 3/mod/direct    3/rm32/EBX    .           .             .           .           .               8/imm8            # shift EBX right by 8 bits, while padding zeroes
+ 989     # ++curr
+ 990     41/increment-ECX
+ 991     eb/jump  $emit-hex:loop/disp8
+ 992 $emit-hex:end:
+ 993     # . restore registers
+ 994     5f/pop-to-EDI
+ 995     5b/pop-to-EBX
+ 996     5a/pop-to-EAX
+ 997     59/pop-to-ECX
+ 998     58/pop-to-EAX
+ 999     # . epilog
+1000     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+1001     5d/pop-to-EBP
+1002     c3/return
+1003 
+1004 test-emit-hex-single-byte:
+1005     # setup
+1006     # . clear-stream(_test-stream)
+1007     # . . push args
+1008     68/push  _test-stream/imm32
+1009     # . . call
+1010     e8/call  clear-stream/disp32
+1011     # . . discard args
+1012     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+1013     # . clear-stream(_test-buffered-file+4)
+1014     # . . push args
+1015     b8/copy-to-EAX  _test-buffered-file/imm32
+1016     05/add-to-EAX  4/imm32
+1017     50/push-EAX
+1018     # . . call
+1019     e8/call  clear-stream/disp32
+1020     # . . discard args
+1021     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+1022     # emit-hex(_test-buffered-file, 0xab, 1)
+1023     # . . push args
+1024     68/push  1/imm32
+1025     68/push  0xab/imm32
+1026     68/push  _test-buffered-file/imm32
+1027     # . . call
+1028     e8/call  emit-hex/disp32
+1029     # . . discard args
+1030     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+1031     # flush(_test-buffered-file)
+1032     # . . push args
+1033     68/push  _test-buffered-file/imm32
+1034     # . . call
+1035     e8/call  flush/disp32
+1036     # . . discard args
+1037     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+1038     # check-ints-equal(*_test-stream->data, 'ab ', msg)
+1039     # . . push args
+1040     68/push  "F - test-emit-hex-single-byte"/imm32
+1041     68/push  0x206261/imm32
+1042     # . . push *_test-stream->data
+1043     b8/copy-to-EAX  _test-stream/imm32
+1044     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
+1045     # . . call
+1046     e8/call  check-ints-equal/disp32
+1047     # . . discard args
+1048     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+1049     # . end
+1050     c3/return
+1051 
+1052 test-emit-hex-multiple-byte:
+1053     # setup
+1054     # . clear-stream(_test-stream)
+1055     # . . push args
+1056     68/push  _test-stream/imm32
+1057     # . . call
+1058     e8/call  clear-stream/disp32
+1059     # . . discard args
+1060     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+1061     # . clear-stream(_test-buffered-file+4)
+1062     # . . push args
+1063     b8/copy-to-EAX  _test-buffered-file/imm32
+1064     05/add-to-EAX  4/imm32
+1065     50/push-EAX
+1066     # . . call
+1067     e8/call  clear-stream/disp32
+1068     # . . discard args
+1069     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+1070     # emit-hex(_test-buffered-file, 0x1234, 2)
+1071     # . . push args
+1072     68/push  2/imm32
+1073     68/push  0x1234/imm32
+1074     68/push  _test-buffered-file/imm32
+1075     # . . call
+1076     e8/call  emit-hex/disp32
+1077     # . . discard args
+1078     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+1079     # flush(_test-buffered-file)
+1080     # . . push args
+1081     68/push  _test-buffered-file/imm32
+1082     # . . call
+1083     e8/call  flush/disp32
+1084     # . . discard args
+1085     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+1086     # check(_test-stream->data == '34 12 ')
+1087     # . check-ints-equal(_test-stream->data[0..3], '34 1', msg)
+1088     # . . push args
+1089     68/push  "F - test-emit-hex-multiple-byte/1"/imm32
+1090     68/push  0x31203433/imm32
+1091     # . . push *_test-stream->data
+1092     b8/copy-to-EAX  _test-stream/imm32
+1093     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
+1094     # . . call
+1095     e8/call  check-ints-equal/disp32
+1096     # . . discard args
+1097     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+1098     # . check-ints-equal(_test-stream->data[4..7], '2 ', msg)
+1099     # . . push args
+1100     68/push  "F - test-emit-hex-multiple-byte/2"/imm32
+1101     68/push  0x2032/imm32
+1102     # . . push *_test-stream->data
+1103     b8/copy-to-EAX  _test-stream/imm32
+1104     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0x10/disp8      .                 # push *(EAX+16)
+1105     # . . call
+1106     e8/call  check-ints-equal/disp32
+1107     # . . discard args
+1108     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+1109     # . end
+1110     c3/return
+1111 
+1112 test-emit-hex-zero-pad:
+1113     # setup
+1114     # . clear-stream(_test-stream)
+1115     # . . push args
+1116     68/push  _test-stream/imm32
+1117     # . . call
+1118     e8/call  clear-stream/disp32
+1119     # . . discard args
+1120     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+1121     # . clear-stream(_test-buffered-file+4)
+1122     # . . push args
+1123     b8/copy-to-EAX  _test-buffered-file/imm32
+1124     05/add-to-EAX  4/imm32
+1125     50/push-EAX
+1126     # . . call
+1127     e8/call  clear-stream/disp32
+1128     # . . discard args
+1129     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+1130     # emit-hex(_test-buffered-file, 0xab, 2)
+1131     # . . push args
+1132     68/push  2/imm32
+1133     68/push  0xab/imm32
+1134     68/push  _test-buffered-file/imm32
+1135     # . . call
+1136     e8/call  emit-hex/disp32
+1137     # . . discard args
+1138     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+1139     # flush(_test-buffered-file)
+1140     # . . push args
+1141     68/push  _test-buffered-file/imm32
+1142     # . . call
+1143     e8/call  flush/disp32
+1144     # . . discard args
+1145     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+1146     # check(_test-stream->data == '00 ab')
+1147     # . check-ints-equal(*_test-stream->data, 'ab 0', msg)
+1148     # . . push args
+1149     68/push  "F - test-emit-hex-zero-pad/1"/imm32
+1150     68/push  0x30206261/imm32
+1151     # . . push *_test-stream->data
+1152     b8/copy-to-EAX  _test-stream/imm32
+1153     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
+1154     # . . call
+1155     e8/call  check-ints-equal/disp32
+1156     # . . discard args
+1157     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+1158     # . check-ints-equal(*_test-stream->data[1], '0 ', msg)
+1159     # . . push args
+1160     68/push  "F - test-emit-hex-zero-pad/2"/imm32
+1161     68/push  0x2030/imm32
+1162     # . . push *_test-stream->data
+1163     b8/copy-to-EAX  _test-stream/imm32
+1164     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0x10/disp8      .                 # push *(EAX+16)
+1165     # . . call
+1166     e8/call  check-ints-equal/disp32
+1167     # . . discard args
+1168     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+1169     # . end
+1170     c3/return
+1171 
+1172 test-emit-hex-negative:
+1173     # setup
+1174     # . clear-stream(_test-stream)
+1175     # . . push args
+1176     68/push  _test-stream/imm32
+1177     # . . call
+1178     e8/call  clear-stream/disp32
+1179     # . . discard args
+1180     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+1181     # . clear-stream(_test-buffered-file+4)
+1182     # . . push args
+1183     b8/copy-to-EAX  _test-buffered-file/imm32
+1184     05/add-to-EAX  4/imm32
+1185     50/push-EAX
+1186     # . . call
+1187     e8/call  clear-stream/disp32
+1188     # . . discard args
+1189     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+1190     # emit-hex(_test-buffered-file, -1, 2)
+1191     # . . push args
+1192     68/push  2/imm32
+1193     68/push  -1/imm32
+1194     68/push  _test-buffered-file/imm32
+1195     # . . call
+1196     e8/call  emit-hex/disp32
+1197     # . . discard args
+1198     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+1199     # flush(_test-buffered-file)
+1200     # . . push args
+1201     68/push  _test-buffered-file/imm32
+1202     # . . call
+1203     e8/call  flush/disp32
+1204     # . . discard args
+1205     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+1206     # check(_test-stream->data == 'ff ff ')
+1207     # . check-ints-equal(_test-stream->data[0..3], 'ff f', msg)
+1208     # . . push args
+1209     68/push  "F - test-emit-hex-negative/1"/imm32
+1210     68/push  0x66206666/imm32
+1211     # . . push *_test-stream->data
+1212     b8/copy-to-EAX  _test-stream/imm32
+1213     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
+1214     # . . call
+1215     e8/call  check-ints-equal/disp32
+1216     # . . discard args
+1217     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+1218     # . check-ints-equal(_test-stream->data[4..7], 'f ', msg)
+1219     # . . push args
+1220     68/push  "F - test-emit-hex-negative/2"/imm32
+1221     68/push  0x2066/imm32
+1222     # . . push *_test-stream->data
+1223     b8/copy-to-EAX  _test-stream/imm32
+1224     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0x10/disp8      .                 # push *(EAX+16)
+1225     # . . call
+1226     e8/call  check-ints-equal/disp32
+1227     # . . discard args
+1228     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+1229     # . end
+1230     c3/return
+1231 
+1232 == data
+1233 
+1234 _test-slice-negative-two:
+1235     2d/- 32/2
+1236 _test-slice-negative-two-end:
+1237     2f/slash 66/f 6f/o 6f/o
+1238 _test-slice-negative-two-metadata-end:
+1239 
+1240 _test-slice-non-number-word:
+1241     78/x 79/y 7a/z
+1242 _test-slice-non-number-word-end:
+1243     2f/slash
+1244 _test-slice-non-number-word-metadata-end:
+1245 
+1246 # . . vim:nowrap:textwidth=0
 
diff --git a/subx/apps/pack b/subx/apps/pack index 54c40efc..4d10e9bd 100755 Binary files a/subx/apps/pack and b/subx/apps/pack differ diff --git a/subx/apps/pack.subx b/subx/apps/pack.subx index 9ad17b7d..e35e8e20 100644 --- a/subx/apps/pack.subx +++ b/subx/apps/pack.subx @@ -21,7 +21,7 @@ # . 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 # for debugging: run a single test -#? e8/call test-has-metadata-true/disp32 +#? e8/call test-emit-non-number-with-metadata/disp32 #? 8b/copy 0/mod/indirect 5/rm32/.disp32 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX #? eb/jump $main:end/disp8 @@ -134,11 +134,6 @@ $main:end: # next-token(stream, slice, delim char) -> slice' # slice-equal?(slice, string) -# helpers: -# emit(out : &buffered-file, word : &slice, width : int) -# if slice is all hex digits, parse and print appropriate digits -# otherwise just write-slice - convert: # in : (address buffered-file), out : (address buffered-file), err : (address buffered-file), ed : (address exit-descriptor) -> # pseudocode: # line = new-stream(512, 1) @@ -618,6 +613,338 @@ test-has-metadata-multiple-false: 5d/pop-to-EBP c3/return +# if the name of 'word' is all hex digits, parse and emit it in 'width' bytes +# of hex otherwise just write-slice +emit: # out : (address buffered-file), word : (address slice), width : int + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # . save registers + 50/push-EAX + 56/push-ESI + 57/push-EDI + # ESI = word + 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 6/r32/ESI 0xc/disp8 . # copy *(EBP+12) to ESI + # var name/EDI : (address slice) = {0, 0} + 68/push 0/imm32/end + 68/push 0/imm32/start + 89/copy 3/mod/direct 7/rm32/EDI . . . 4/r32/ESP . . # copy ESP to EDI + # name = next-token-from-slice(word->start, word->end, '/') + # . . push args + 57/push-EDI + 68/push 0x2f/imm32/slash + ff 6/subop/push 1/mod/*+disp8 6/rm32/ESI . . . . 4/disp8 . # push *(ESI+4) + ff 6/subop/push 0/mod/indirect 6/rm32/ESI . . . . . . # push *ESI + # . . call + e8/call next-token-from-slice/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0x10/imm32 # add to ESP + # if !is-hex-int?(name) write-slice(out, word) and return + # . is-hex-int?(name) + # . . push args + 57/push-EDI + # . . call + e8/call is-hex-int?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # . if EAX == 0 + 81 7/subop/compare 3/mod/direct 0/rm32/EAX . . . . . 0/imm32 # compare EAX + 75/jump-if-not-equal $emit:hex-int/disp8 + # . write-slice(out, word) + # . . push args + 56/push-ESI + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) + # . . call + e8/call write-slice/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # . return + eb/jump $emit:end/disp8 + # otherwise emit-hex(out, parse-hex-int(name), width) +$emit:hex-int: + # . n/EAX = parse-hex-int(name) + # . . push args + 57/push-EDI + # . . call + e8/call parse-hex-int/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # . emit-hex(out, n, width) + # . . push args + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0x10/disp8 . # push *(EBP+16) + 50/push-EAX + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) + # . . call + e8/call emit-hex/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP +$emit:end: + # . reclaim locals + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # . restore registers + 5f/pop-to-EDI + 5e/pop-to-ESI + 58/pop-to-EAX + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-emit-number: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # setup + # . clear-stream(_test-stream) + # . . push args + 68/push _test-stream/imm32 + # . . call + e8/call clear-stream/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # . clear-stream(_test-buffered-file+4) + # . . push args + b8/copy-to-EAX _test-buffered-file/imm32 + 05/add-to-EAX 4/imm32 + 50/push-EAX + # . . call + e8/call clear-stream/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # var slice/ECX = "-2" + 68/push _test-slice-negative-two-end/imm32/end + 68/push _test-slice-negative-two/imm32/start + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # emit(_test-buffered-file, slice, 2) + # . . push args + 68/push 2/imm32 + 51/push-ECX + 68/push _test-buffered-file/imm32 + # . . call + e8/call emit/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # flush(_test-buffered-file) + # . . push args + 68/push _test-buffered-file/imm32 + # . . call + e8/call flush/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check(_test-stream->data == 'fe ff ') + # . check-ints-equal(_test-stream->data[0..3], 'fe f', msg) + # . . push args + 68/push "F - test-emit-number/1"/imm32 + 68/push 0x66206566/imm32 + # . . push *_test-stream->data + b8/copy-to-EAX _test-stream/imm32 + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) + # . . call + e8/call check-ints-equal/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # . check-ints-equal(_test-stream->data[4..7], 'f ', msg) + # . . push args + 68/push "F - test-emit-number/2"/imm32 + 68/push 0x2066/imm32 + # . . push *_test-stream->data + b8/copy-to-EAX _test-stream/imm32 + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0x10/disp8 . # push *(EAX+16) + # . . call + e8/call check-ints-equal/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-emit-number-with-metadata: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # setup + # . clear-stream(_test-stream) + # . . push args + 68/push _test-stream/imm32 + # . . call + e8/call clear-stream/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # . clear-stream(_test-buffered-file+4) + # . . push args + b8/copy-to-EAX _test-buffered-file/imm32 + 05/add-to-EAX 4/imm32 + 50/push-EAX + # . . call + e8/call clear-stream/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # var slice/ECX = "-2/foo" + 68/push _test-slice-negative-two-metadata-end/imm32/end + 68/push _test-slice-negative-two/imm32/start + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # emit(_test-buffered-file, slice, 2) + # . . push args + 68/push 2/imm32 + 51/push-ECX + 68/push _test-buffered-file/imm32 + # . . call + e8/call emit/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # flush(_test-buffered-file) + # . . push args + 68/push _test-buffered-file/imm32 + # . . call + e8/call flush/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # the '/foo' will have no impact on the output + # check(_test-stream->data == 'fe ff ') + # . check-ints-equal(_test-stream->data[0..3], 'fe f', msg) + # . . push args + 68/push "F - test-emit-number-with-metadata/1"/imm32 + 68/push 0x66206566/imm32 + # . . push *_test-stream->data + b8/copy-to-EAX _test-stream/imm32 + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) + # . . call + e8/call check-ints-equal/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # . check-ints-equal(_test-stream->data[4..7], 'f ', msg) + # . . push args + 68/push "F - test-emit-number-with-metadata/2"/imm32 + 68/push 0x2066/imm32 + # . . push *_test-stream->data + b8/copy-to-EAX _test-stream/imm32 + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0x10/disp8 . # push *(EAX+16) + # . . call + e8/call check-ints-equal/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-emit-non-number: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # setup + # . clear-stream(_test-stream) + # . . push args + 68/push _test-stream/imm32 + # . . call + e8/call clear-stream/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # . clear-stream(_test-buffered-file+4) + # . . push args + b8/copy-to-EAX _test-buffered-file/imm32 + 05/add-to-EAX 4/imm32 + 50/push-EAX + # . . call + e8/call clear-stream/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # var slice/ECX = "xyz" + 68/push _test-slice-non-number-word-end/imm32/end + 68/push _test-slice-non-number-word/imm32/start + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # emit(_test-buffered-file, slice, 2) + # . . push args + 68/push 2/imm32 + 51/push-ECX + 68/push _test-buffered-file/imm32 + # . . call + e8/call emit/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # flush(_test-buffered-file) + # . . push args + 68/push _test-buffered-file/imm32 + # . . call + e8/call flush/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check(_test-stream->data == 'xyz') + # . check-ints-equal(_test-stream->data[0..3], 'xyz', msg) + # . . push args + 68/push "F - test-emit-non-number"/imm32 + 68/push 0x7a7978/imm32 + # . . push *_test-stream->data + b8/copy-to-EAX _test-stream/imm32 + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) + # . . call + e8/call check-ints-equal/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-emit-non-number-with-metadata: + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # setup + # . clear-stream(_test-stream) + # . . push args + 68/push _test-stream/imm32 + # . . call + e8/call clear-stream/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # . clear-stream(_test-buffered-file+4) + # . . push args + b8/copy-to-EAX _test-buffered-file/imm32 + 05/add-to-EAX 4/imm32 + 50/push-EAX + # . . call + e8/call clear-stream/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # var slice/ECX = "xyz/" + 68/push _test-slice-non-number-word-metadata-end/imm32/end + 68/push _test-slice-non-number-word/imm32/start + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # emit(_test-buffered-file, slice, 2) + # . . push args + 68/push 2/imm32 + 51/push-ECX + 68/push _test-buffered-file/imm32 + # . . call + e8/call emit/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # flush(_test-buffered-file) + # . . push args + 68/push _test-buffered-file/imm32 + # . . call + e8/call flush/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # check(_test-stream->data == 'xyz/') + # . check-ints-equal(_test-stream->data[0..3], 'xyz/', msg) + # . . push args + 68/push "F - test-emit-non-number-with-metadata"/imm32 + 68/push 0x2f7a7978/imm32 + # . . push *_test-stream->data + b8/copy-to-EAX _test-stream/imm32 + ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) + # . . call + e8/call check-ints-equal/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + # print 'n' in hex in 'width' bytes in lower-endian order, with a space after every byte emit-hex: # out : (address buffered-file), n : int, width : int # . prolog @@ -907,5 +1234,13 @@ test-emit-hex-negative: _test-slice-negative-two: 2d/- 32/2 _test-slice-negative-two-end: + 2f/slash 66/f 6f/o 6f/o +_test-slice-negative-two-metadata-end: + +_test-slice-non-number-word: + 78/x 79/y 7a/z +_test-slice-non-number-word-end: + 2f/slash +_test-slice-non-number-word-metadata-end: # . . vim:nowrap:textwidth=0