From cb82bd2364c302acbb96dd6900779e189b36efac Mon Sep 17 00:00:00 2001 From: Kartik Agaram Date: Sat, 2 Feb 2019 15:21:48 -0800 Subject: [PATCH] 4948 This seems like the final helper we need for Phase 2. Now to build the business logic itself. --- html/subx/apps/pack.subx.html | 1818 ++++++++++++++++++++++----------- subx/apps/pack | Bin 16518 -> 17425 bytes subx/apps/pack.subx | 347 ++++++- 3 files changed, 1588 insertions(+), 577 deletions(-) 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 54c40efc0a771dd2c0afb2089ba74aa92d8219db..4d10e9bda05c87722dd72b67f9b6b77fc94842a0 100755 GIT binary patch literal 17425 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 IGW>-92asn_eE 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