about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--subx/012elf.cc6
-rw-r--r--subx/058stream-equal.subx3
-rw-r--r--subx/071read-line.subx94
-rw-r--r--subx/072slice.subx2
-rwxr-xr-xsubx/apps/crenshaw2-1bin18028 -> 17760 bytes
-rwxr-xr-xsubx/apps/crenshaw2-1bbin18587 -> 18319 bytes
-rwxr-xr-xsubx/apps/factorialbin16946 -> 16678 bytes
-rwxr-xr-xsubx/apps/handlebin17721 -> 17453 bytes
-rwxr-xr-xsubx/apps/hexbin21007 -> 20739 bytes
-rwxr-xr-xsubx/apps/packbin21184 -> 21169 bytes
-rw-r--r--subx/apps/pack.subx616
11 files changed, 412 insertions, 309 deletions
diff --git a/subx/012elf.cc b/subx/012elf.cc
index c9bbfc70..0f058504 100644
--- a/subx/012elf.cc
+++ b/subx/012elf.cc
@@ -145,10 +145,10 @@ void dump_stack() {
   ostringstream out;
   trace(Callstack_depth+1, "run") << "stack:" << end();
   for (uint32_t a = AFTER_STACK-4;  a > Reg[ESP].u;  a -= 4)
-    trace(Callstack_depth+1, "run") << "  0x" << HEXWORD << a << " => 0x" << HEXWORD << read_mem_u32(a) << end();
-  trace(Callstack_depth+1, "run") << "  0x" << HEXWORD << Reg[ESP].u << " => 0x" << HEXWORD << read_mem_u32(Reg[ESP].u) << "  <=== ESP" << end();
+    trace(Callstack_depth+2, "run") << "  0x" << HEXWORD << a << " => 0x" << HEXWORD << read_mem_u32(a) << end();
+  trace(Callstack_depth+2, "run") << "  0x" << HEXWORD << Reg[ESP].u << " => 0x" << HEXWORD << read_mem_u32(Reg[ESP].u) << "  <=== ESP" << end();
   for (uint32_t a = Reg[ESP].u-4;  a > Reg[ESP].u-40;  a -= 4)
-    trace(Callstack_depth+1, "run") << "  0x" << HEXWORD << a << " => 0x" << HEXWORD << read_mem_u32(a) << end();
+    trace(Callstack_depth+2, "run") << "  0x" << HEXWORD << a << " => 0x" << HEXWORD << read_mem_u32(a) << end();
 }
 
 inline uint32_t u32_in(uint8_t* p) {
diff --git a/subx/058stream-equal.subx b/subx/058stream-equal.subx
index 88a09d9c..4716db39 100644
--- a/subx/058stream-equal.subx
+++ b/subx/058stream-equal.subx
@@ -24,8 +24,9 @@ stream-data-equal?:  # f : (address stream), s : (address string) -> EAX : boole
     57/push-EDI
     # ESI = f
     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
-    # max/EDX = f->data + f->write
+    # EAX = f->write
     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # copy *ESI to EAX
+    # max/EDX = f->data + f->write
     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/ESI  0/index/EAX   .           2/r32/EDX   0xc/disp8       .                 # copy ESI+EAX+12 to EDX
     # currf/ESI = f->data
     81          0/subop/add         3/mod/direct    6/rm32/ESI    .           .             .           .           .               0xc/imm32         # add to ESI
diff --git a/subx/071read-line.subx b/subx/071read-line.subx
index eff6f9c4..b16170b6 100644
--- a/subx/071read-line.subx
+++ b/subx/071read-line.subx
@@ -3,10 +3,17 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 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
 
+#? Entry:  # run a single test, while debugging
+#?     e8/call test-read-line/disp32
+#?     # syscall(exit, Num-test-failures)
+#?     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
+#?     b8/copy-to-EAX  1/imm32/exit
+#?     cd/syscall  0x80/imm8
+
 # read bytes from 'f' until (and including) a newline and store them into 's'
-# return true if no data found, false otherwise
+# 's' fails to grow if and only if no data found
 # just abort if 's' is too small
-read-line:  # f : (address buffered-file), s : (address stream byte) -> eof?/EAX
+read-line:  # f : (address buffered-file), s : (address stream byte) -> <void>
     # pseudocode:
     #   loop:
     #     if (s->write >= s->length) abort
@@ -21,6 +28,7 @@ read-line:  # f : (address buffered-file), s : (address stream byte) -> eof?/EAX
     55/push-EBP
     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
     # . save registers
+    50/push-EAX
     51/push-ECX
     52/push-EDX
     56/push-ESI
@@ -58,13 +66,11 @@ $read-line:loop:
     e8/call  read/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # if (f->write == 0) return true
+    # if (f->write == 0) break
     # since f->read was initially 0, EAX is the same as f->write
     # . if (EAX == 0) return true
     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # compare EAX
-    75/jump-if-not-equal  $read-line:from-stream/disp8
-    b8/copy-to-EAX  0xffffffff/imm32/Eof
-    eb/jump  $read-line:end/disp8
+    74/jump-if-equal  $read-line:end/disp8
 $read-line:from-stream:
     # AL = f->data[f->read]
     31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
@@ -75,10 +81,9 @@ $read-line:from-stream:
     41/increment-ECX
     # ++s->write
     42/increment-EDX
-    # if (AL == '\n') return false
+    # if (AL == '\n') return
     81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0xa/imm32         # compare EAX
     75/jump-if-not-equal  $read-line:loop/disp8
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
 $read-line:end:
     # save f->read
     89/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   8/disp8         .                 # copy ECX to *(ESI+8)
@@ -89,6 +94,7 @@ $read-line:end:
     5e/pop-to-ESI
     5a/pop-to-EDX
     59/pop-to-ECX
+    58/pop-to-EAX
     # . epilog
     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
     5d/pop-to-EBP
@@ -103,6 +109,14 @@ $read-line:abort:
     e8/call  _write/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # . _write(2/stderr, Newline)
+    # . . push args
+    68/push  Newline/imm32
+    68/push  2/imm32/stderr
+    # . . call
+    e8/call  _write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
     # . syscall(exit, 1)
     bb/copy-to-EBX  1/imm32
     b8/copy-to-EAX  1/imm32/exit
@@ -169,15 +183,6 @@ test-read-line:
     e8/call  read-line/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check-ints-equal(EAX, 0/not-at-Eof, msg)
-    # . . push args
-    68/push  "F - test-read-line: return value"/imm32
-    68/push  0/imm32/not-at-Eof
-    50/push-EAX
-    # . . 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-next-stream-line-equal(_test-tmp-stream, "ab", msg)
     # . . push args
     68/push  "F - test-read-line"/imm32
@@ -190,52 +195,6 @@ test-read-line:
     # end
     c3/return
 
-test-read-line-returns-true-on-Eof:
-    # 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
-    # . clear-stream(_test-tmp-stream)
-    # . . push args
-    68/push  _test-tmp-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
-    # write nothing
-    # EAX = read-line(_test-buffered-file, _test-tmp-stream)
-    # . . push args
-    68/push  _test-tmp-stream/imm32
-    68/push  _test-buffered-file/imm32
-    # . . call
-    e8/call  read-line/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check-ints-equal(EAX, Eof, msg)
-    # . . push args
-    68/push  "F - test-read-line-returns-true-on-Eof"/imm32
-    68/push  0xffffffff/imm32/Eof
-    50/push-EAX
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # end
-    c3/return
-
 test-read-line-reads-final-line-until-Eof:
     # setup
     # . clear-stream(_test-stream)
@@ -278,15 +237,6 @@ test-read-line-reads-final-line-until-Eof:
     e8/call  read-line/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check-ints-equal(EAX, Eof, msg)
-    # . . push args
-    68/push  "F - test-read-line-reads-final-line-until-Eof: return value"/imm32
-    68/push  0xffffffff/imm32/Eof
-    50/push-EAX
-    # . . 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-stream-equal(_test-tmp-stream, "cd", msg)
     # . . push args
     68/push  "F - test-read-line-reads-final-line-until-Eof"/imm32
diff --git a/subx/072slice.subx b/subx/072slice.subx
index c8834719..a47ef493 100644
--- a/subx/072slice.subx
+++ b/subx/072slice.subx
@@ -828,6 +828,8 @@ write-stream-buffered:  # f : (address buffered-file), s : (address stream)
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 $write-stream-buffered:end:
+    # . restore locals
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
     # . restore registers
     5e/pop-to-ESI
     58/pop-to-EAX
diff --git a/subx/apps/crenshaw2-1 b/subx/apps/crenshaw2-1
index 44887a93..0a3d59ee 100755
--- a/subx/apps/crenshaw2-1
+++ b/subx/apps/crenshaw2-1
Binary files differdiff --git a/subx/apps/crenshaw2-1b b/subx/apps/crenshaw2-1b
index 668b60f3..931bff4c 100755
--- a/subx/apps/crenshaw2-1b
+++ b/subx/apps/crenshaw2-1b
Binary files differdiff --git a/subx/apps/factorial b/subx/apps/factorial
index 7361a59f..9bef95a7 100755
--- a/subx/apps/factorial
+++ b/subx/apps/factorial
Binary files differdiff --git a/subx/apps/handle b/subx/apps/handle
index 63a94ad1..84b1bcf1 100755
--- a/subx/apps/handle
+++ b/subx/apps/handle
Binary files differdiff --git a/subx/apps/hex b/subx/apps/hex
index a9bfcde9..3b183506 100755
--- a/subx/apps/hex
+++ b/subx/apps/hex
Binary files differdiff --git a/subx/apps/pack b/subx/apps/pack
index 77aec7b0..69af4226 100755
--- a/subx/apps/pack
+++ b/subx/apps/pack
Binary files differdiff --git a/subx/apps/pack.subx b/subx/apps/pack.subx
index c6cdaf14..003bc5d5 100644
--- a/subx/apps/pack.subx
+++ b/subx/apps/pack.subx
@@ -23,7 +23,7 @@
 Entry:  # run tests if necessary, convert stdin if not
 
 #?     # for debugging: run a single test
-#?     e8/call test-convert-instruction-passes-labels-through/disp32
+#?     e8/call test-convert-passes-segment-headers-through/disp32
 #?     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
 #?     eb/jump  $main:end/disp8
 
@@ -86,19 +86,22 @@ $main:end:
 convert:  # in : (address buffered-file), out : (address buffered-file) -> <void>
     # pseudocode:
     #   var line = new-stream(512, 1)
+    #   var in-code? = false
     #   repeatedly
     #     clear-stream(line)
-    #     EAX = read-line(in, line)
-    #     if (EAX == Eof) break
-    #     word-slice = next-word(line)
-    #     if (slice-empty?(word-slice)) continue  # just whitespace
-    #     if (starts-with(word-slice, "#")) continue  # ignore comments
-    #     assert(slice-equal?(word-slice, "=="))
-    #     word-slice = next-word(line)
-    #     if (slice-equal?(word-slice, "code"))
-    #       convert-code-segment(in, out)
+    #     read-line(in, line)
+    #     if (line->write == 0) break             # end of file
+    #     var word-slice = next-word(line)
+    #     if (slice-empty?(word-slice))           # whitespace
+    #       write-stream-buffered(out, line)
+    #     else if (slice-equal?(word-slice, "=="))
+    #       word-slice = next-word(line)
+    #       in-code? = slice-equal?(word-slice, "code")
+    #       write-stream-buffered(out, line)
+    #     else if (in-code?)
+    #       convert-instruction(line, out)
     #     else
-    #       convert-data-segment(in, out)
+    #       convert-data(line, out)
     #   flush(out)
     #
     # . prolog
@@ -106,233 +109,194 @@ convert:  # in : (address buffered-file), out : (address buffered-file) -> <void
     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
     # . save registers
     51/push-ECX
+    52/push-EDX
+    53/push-EBX
     # var line/ECX : (address stream byte) = stream(512)
     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x200/imm32       # subtract from ESP
     68/push  0x200/imm32/length
     68/push  0/imm32/read
     68/push  0/imm32/write
     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
+    # var word-slice/EDX = {0, 0}
+    68/push  0/imm32/end
+    68/push  0/imm32/curr
+    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
+    # var in-code?/EBX = false
+    68/push  0/imm32/false
+    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBX
 $convert:loop:
-    # clear-stream(ECX)
+    # clear-stream(line)
     # . . push args
     51/push-ECX
     # . . call
     e8/call  clear-stream/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # EAX = read-line(in, line)
+    # read-line(in, line)
     # . . push args
     51/push-ECX
     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
     # . . call
-    e8/call  convert-instruction/disp32
+    e8/call  read-line/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # if (EAX == Eof) break
-    3d/compare-with-EAX  0xffffffff/imm32/Eof
+$convert:check0:
+    # if (line->write == 0) break
+    81          7/subop/compare     0/mod/indirect  1/rm32/ECX    .           .             .           .           .               0/imm32           # compare *ECX
     74/jump-if-equal  $convert:break/disp8
-    # convert-instruction(line, out)
+    # next-word(line, word-slice)
     # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
+    52/push-EDX
     51/push-ECX
     # . . call
-    e8/call  convert-instruction/disp32
+    e8/call  next-word/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$convert:break:
-    # flush(out)
+$convert:check1:
+    # if (slice-empty?(word-slice)) write-stream-buffered(out, line)
+    # . EAX = slice-empty?(word-slice)
     # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
+    52/push-EDX
     # . . call
-    e8/call  flush/disp32
+    e8/call  slice-empty?/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-$convert:end:
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x20c/imm32       # add to ESP
-    # . restore registers
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-convert-code-segment:  # in : (address buffered-file), out : (address buffered-file) -> <void>
-    # pseudocode:
-    #   var line = new-stream(512, 1)
-    #   repeatedly
-    #     clear-stream(line)
-    #     EAX = read-line(in, line)
-    #     if (EAX == Eof) break
-    #     word-slice = next-word(line)
-    #     if (slice-equal?(word-slice, "=="))
-    #       return
-    #     convert-instruction(line, out)
-
-convert-data-segment:  # in : (address buffered-file), out : (address buffered-file) -> <void>
-    # pseudocode:
-    #   var line = new-stream(512, 1)
-    #   repeatedly
-    #     clear-stream(line)
-    #     EAX = read-line(in, line)
-    #     if (EAX == Eof) break
-    #     word-slice = next-word(line)
-    #     if (slice-equal?(word-slice, "=="))
-    #       return
-    #     convert-data-word(line, out)
-
-# - To pack an instruction, following the C++ version:
-# read first word as opcode and write-slice
-# if 0f or f2 or f3 read second opcode and write-slice
-# if 'f2 0f' or 'f3 0f' read third opcode and write-slice
-# while true:
-#   word-slice = next-word
-#   if empty(word-slice) break
-#   if has metadata 'mod', parse into mod
-#   if has metadata 'rm32', parse into rm32
-#   if has metadata 'r32', parse into r32
-#   if has metadata 'subop', parse into r32
-# if at least one of the 3 was present, print-byte
-# while true:
-#   word-slice = next-word
-#   if empty(word-slice) break
-#   if has metadata 'base', parse into base
-#   if has metadata 'index', parse into index
-#   if has metadata 'scale', parse into scale
-# if at least one of the 3 was present, print-byte
-# parse errors => <abort>
-# while true:
-#   word-slice = next-word
-#   if empty(word-slice) break
-#   if has metadata 'disp8', emit as 1 byte
-#   if has metadata 'disp16', emit as 2 bytes
-#   if has metadata 'disp32', emit as 4 bytes
-# while true:
-#   word-slice = next-word
-#   if empty(word-slice) break
-#   if has metadata 'imm8', emit
-#   if has metadata 'imm32', emit as 4 bytes
-# finally, emit line prefixed with a '  # '
-
-# simplifications since we perform zero error handling (continuing to rely on the C++ version for that):
-#   missing fields are always 0-filled
-#   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.
-#   in case of conflict, last operand with a name is recognized
-#   silently drop extraneous operands
-#   unceremoniously abort on non-numeric operands except disp or imm
-
-# conceptual hierarchy within a line:
-#   line = words separated by ' ', maybe followed by comment starting with '#'
-#   word = name until '/', then 0 or more metadata separated by '/'
-#
-# we won't bother saving the internal structure of lines; reparsing should be cheap using three primitives:
-#   next-token(stream, delim char) -> slice (start, end pointers)
-#   next-token(stream, slice, delim char) -> slice'
-#   slice-equal?(slice, string)
-
-convert-instruction:  # line : (address stream byte), out : (address buffered-file) -> <void>
-    # pseudocode:
-    #   word-slice = next-word
-    #   if (slice-starts-with?(word-slice, '#'))
-    #     write-stream-buffered(out, line)
-    #     return
-    #   if (slice-starts-with?(word-slice, '=='))
-    #     write-stream-buffered(out, line)
-    #     return
-    #
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    51/push-ECX
-    # var word-slice/ECX = {0, 0}
-    68/push  0/imm32/end
-    68/push  0/imm32/start
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # next-word(line, word-slice)
+    # . if (EAX != 0) write-stream-buffered(out, line)
+    3d/compare-EAX  0/imm32
+    75/jump-if-not-equal  $convert:pass-through/disp8
+$convert:check2:
+    # if (slice-equal?(word-slice, "==) && slice-equal?(next-word(line))) write-stream-buffered(out, line)
+    # . EAX = slice-equal?(word-slice, "==")
+    # . . push args
+    68/push  "=="/imm32
+    52/push-EDX
+    # . . call
+    e8/call  slice-equal?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # . if (EAX == 0) goto check3
+    81          7/subop/compare     3/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # compare EAX
+    74/jump-if-equal  $convert:check3/disp8
+    # . next-word(line, word-slice)
     # . . push args
+    52/push-EDX
     51/push-ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
     # . . call
     e8/call  next-word/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$convert-instruction:pass-line-through:
+    # . in-code? = slice-equal?(word-slice, "code")
+    # . . push args
+    68/push  "code"/imm32
+    52/push-EDX
+    # . . call
+    e8/call  slice-equal?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # . . in-code? = EAX
+    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EBX
+    # . goto pass-through
+    eb/jump  $convert:pass-through/disp8
+$convert:check3:
+#?     # convert-instruction(line, out)
+#?     # . . push args
+#?     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
+#?     51/push-ECX
+#?     # . . call
+#?     e8/call  convert-instruction/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    e9/jump  $convert:loop/disp32
+$convert:pass-through:
     # write-stream-buffered(out, line)
     # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
+    51/push-ECX
     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
     # . . call
     e8/call  write-stream-buffered/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-$convert-instruction:end:
+    e9/jump  $convert:loop/disp32
+$convert:break:
+    # flush(out)
+    # . . push args
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
+    # . . call
+    e8/call  flush/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+$convert:end:
+    # . reclaim locals
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x218/imm32       # add to ESP
     # . restore registers
+    5b/pop-to-EBX
+    5a/pop-to-EDX
     59/pop-to-ECX
     # . epilog
     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
     5d/pop-to-EBP
     c3/return
 
-test-convert-instruction-passes-comments-through:
-    # if a line starts with '#', pass it along unchanged
+test-convert-passes-empty-lines-through:
+    # if a line is empty, pass it along unchanged
     # . prolog
     55/push-EBP
     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
     # setup
-    # . clear-stream(_test-stream)
+    # . clear-stream(_test-input-stream)
     # . . push args
-    68/push  _test-stream/imm32
+    68/push  _test-input-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)
+    # . clear-stream(_test-input-buffered-file+4)
     # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
+    b8/copy-to-EAX  _test-input-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
-    # . clear-stream(_test-tmp-stream)
+    # . clear-stream(_test-output-stream)
     # . . push args
-    68/push  _test-tmp-stream/imm32
+    68/push  _test-output-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
-    # initialize input
-    # . write(_test-tmp-stream, "# abcd")
+    # . clear-stream(_test-output-buffered-file+4)
     # . . push args
-    68/push  "# abcd"/imm32
-    68/push  _test-tmp-stream/imm32
+    b8/copy-to-EAX  _test-output-buffered-file/imm32
+    05/add-to-EAX  4/imm32
+    50/push-EAX
     # . . call
-    e8/call  write/disp32
+    e8/call  clear-stream/disp32
     # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-tmp-stream, _test-buffered-file)
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # write nothing to input
+    # convert(_test-input-buffered-file, _test-output-buffered-file)
     # . . push args
-    68/push  _test-buffered-file/imm32
-    68/push  _test-tmp-stream/imm32
+    68/push  _test-output-buffered-file/imm32
+    68/push  _test-input-buffered-file/imm32
     # . . call
-    e8/call  convert-instruction/disp32
+    e8/call  convert/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
     # check that the write happened as expected
-    # . flush(_test-buffered-file)
+    # . flush(_test-output-buffered-file)
     # . . push args
-    68/push  _test-buffered-file/imm32
+    68/push  _test-output-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-stream-equal(_test-stream, "# abcd", msg)
+    # . check-stream-equal(_test-output-stream, "", msg)
     # . . push args
-    68/push  "F - test-convert-instruction-passes-comments-through"/imm32
-    68/push  "# abcd"/imm32
-    68/push  _test-stream/imm32
+    68/push  "F - test-convert-passes-empty-lines-through"/imm32
+    68/push  ""/imm32
+    68/push  _test-output-stream/imm32
     # . . call
     e8/call  check-stream-equal/disp32
     # . . discard args
@@ -342,65 +306,74 @@ test-convert-instruction-passes-comments-through:
     5d/pop-to-EBP
     c3/return
 
-test-convert-instruction-passes-segment-headers-through:
-    # if a line starts with '==', pass it along unchanged
+test-convert-passes-lines-with-just-whitespace-through:
+    # if a line is empty, pass it along unchanged
     # . prolog
     55/push-EBP
     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
     # setup
-    # . clear-stream(_test-stream)
+    # . clear-stream(_test-input-stream)
     # . . push args
-    68/push  _test-stream/imm32
+    68/push  _test-input-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)
+    # . clear-stream(_test-input-buffered-file+4)
     # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
+    b8/copy-to-EAX  _test-input-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
-    # . clear-stream(_test-tmp-stream)
+    # . clear-stream(_test-output-stream)
     # . . push args
-    68/push  _test-tmp-stream/imm32
+    68/push  _test-output-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-output-buffered-file+4)
+    # . . push args
+    b8/copy-to-EAX  _test-output-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
     # initialize input
-    # . write(_test-tmp-stream, "== abcd")
+    # . write(_test-input-stream, "    ")
     # . . push args
-    68/push  "== abcd"/imm32
-    68/push  _test-tmp-stream/imm32
+    68/push  "    "/imm32
+    68/push  _test-input-stream/imm32
     # . . call
     e8/call  write/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-tmp-stream, _test-buffered-file)
+    # convert(_test-input-buffered-file, _test-output-buffered-file)
     # . . push args
-    68/push  _test-buffered-file/imm32
-    68/push  _test-tmp-stream/imm32
+    68/push  _test-output-buffered-file/imm32
+    68/push  _test-input-buffered-file/imm32
     # . . call
-    e8/call  convert-instruction/disp32
+    e8/call  convert/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
     # check that the write happened as expected
-    # . flush(_test-buffered-file)
+    # . flush(_test-output-buffered-file)
     # . . push args
-    68/push  _test-buffered-file/imm32
+    68/push  _test-output-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-stream-equal(_test-stream, "== abcd", msg)
+    # . check-stream-equal(_test-output-stream, "    ", msg)
     # . . push args
-    68/push  "F - test-convert-instruction-passes-segment-headers-through"/imm32
-    68/push  "== abcd"/imm32
-    68/push  _test-stream/imm32
+    68/push  "F - test-convert-passes-with-just-whitespace-through"/imm32
+    68/push  "    "/imm32
+    68/push  _test-output-stream/imm32
     # . . call
     e8/call  check-stream-equal/disp32
     # . . discard args
@@ -410,57 +383,74 @@ test-convert-instruction-passes-segment-headers-through:
     5d/pop-to-EBP
     c3/return
 
-test-convert-instruction-passes-empty-lines-through:
-    # if a line is empty, pass it along unchanged
+test-convert-passes-segment-headers-through:
+    # if a line starts with '==', pass it along unchanged
     # . prolog
     55/push-EBP
     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
     # setup
-    # . clear-stream(_test-stream)
+    # . clear-stream(_test-input-stream)
     # . . push args
-    68/push  _test-stream/imm32
+    68/push  _test-input-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)
+    # . clear-stream(_test-input-buffered-file+4)
     # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
+    b8/copy-to-EAX  _test-input-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
-    # . clear-stream(_test-tmp-stream)
+    # . clear-stream(_test-output-stream)
     # . . push args
-    68/push  _test-tmp-stream/imm32
+    68/push  _test-output-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
-    # write nothing to input
-    # convert-instruction(_test-tmp-stream, _test-buffered-file)
+    # . clear-stream(_test-output-buffered-file+4)
     # . . push args
-    68/push  _test-buffered-file/imm32
-    68/push  _test-tmp-stream/imm32
+    b8/copy-to-EAX  _test-output-buffered-file/imm32
+    05/add-to-EAX  4/imm32
+    50/push-EAX
     # . . call
-    e8/call  convert-instruction/disp32
+    e8/call  clear-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # initialize input
+    # . write(_test-input-stream, "== abcd")
+    # . . push args
+    68/push  "== abcd"/imm32
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # convert(_test-input-buffered-file, _test-output-buffered-file)
+    # . . push args
+    68/push  _test-output-buffered-file/imm32
+    68/push  _test-input-buffered-file/imm32
+    # . . call
+    e8/call  convert/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
     # check that the write happened as expected
-    # . flush(_test-buffered-file)
+    # . flush(_test-output-buffered-file)
     # . . push args
-    68/push  _test-buffered-file/imm32
+    68/push  _test-output-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-stream-equal(_test-stream, "", msg)
+    # . check-stream-equal(_test-output-stream, "== abcd", msg)
     # . . push args
-    68/push  "F - test-convert-instruction-passes-empty-lines-through"/imm32
-    68/push  ""/imm32
-    68/push  _test-stream/imm32
+    68/push  "F - test-convert-passes-segment-headers-through"/imm32
+    68/push  "== abcd"/imm32
+    68/push  _test-output-stream/imm32
     # . . call
     e8/call  check-stream-equal/disp32
     # . . discard args
@@ -470,65 +460,179 @@ test-convert-instruction-passes-empty-lines-through:
     5d/pop-to-EBP
     c3/return
 
-test-convert-instruction-passes-lines-with-just-whitespace-through:
-    # if a line is empty, pass it along unchanged
+convert-code-segment:  # in : (address buffered-file), out : (address buffered-file) -> <void>
+    # pseudocode:
+    #   var line = new-stream(512, 1)
+    #   repeatedly
+    #     clear-stream(line)
+    #     EAX = read-line(in, line)
+    #     if (EAX == Eof) break
+    #     word-slice = next-word(line)
+    #     if (slice-equal?(word-slice, "=="))
+    #       return
+    #     convert-instruction(line, out)
+
+convert-data-segment:  # in : (address buffered-file), out : (address buffered-file) -> <void>
+    # pseudocode:
+    #   var line = new-stream(512, 1)
+    #   repeatedly
+    #     clear-stream(line)
+    #     EAX = read-line(in, line)
+    #     if (EAX == Eof) break
+    #     word-slice = next-word(line)
+    #     if (slice-equal?(word-slice, "=="))
+    #       return
+    #     convert-data-word(line, out)
+
+# - To pack an instruction, following the C++ version:
+# read first word as opcode and write-slice
+# if 0f or f2 or f3 read second opcode and write-slice
+# if 'f2 0f' or 'f3 0f' read third opcode and write-slice
+# while true:
+#   word-slice = next-word
+#   if empty(word-slice) break
+#   if has metadata 'mod', parse into mod
+#   if has metadata 'rm32', parse into rm32
+#   if has metadata 'r32', parse into r32
+#   if has metadata 'subop', parse into r32
+# if at least one of the 3 was present, print-byte
+# while true:
+#   word-slice = next-word
+#   if empty(word-slice) break
+#   if has metadata 'base', parse into base
+#   if has metadata 'index', parse into index
+#   if has metadata 'scale', parse into scale
+# if at least one of the 3 was present, print-byte
+# parse errors => <abort>
+# while true:
+#   word-slice = next-word
+#   if empty(word-slice) break
+#   if has metadata 'disp8', emit as 1 byte
+#   if has metadata 'disp16', emit as 2 bytes
+#   if has metadata 'disp32', emit as 4 bytes
+# while true:
+#   word-slice = next-word
+#   if empty(word-slice) break
+#   if has metadata 'imm8', emit
+#   if has metadata 'imm32', emit as 4 bytes
+# finally, emit line prefixed with a '  # '
+
+# simplifications since we perform zero error handling (continuing to rely on the C++ version for that):
+#   missing fields are always 0-filled
+#   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.
+#   in case of conflict, last operand with a name is recognized
+#   silently drop extraneous operands
+#   unceremoniously abort on non-numeric operands except disp or imm
+
+# conceptual hierarchy within a line:
+#   line = words separated by ' ', maybe followed by comment starting with '#'
+#   word = name until '/', then 0 or more metadata separated by '/'
+#
+# we won't bother saving the internal structure of lines; reparsing should be cheap using three primitives:
+#   next-token(stream, delim char) -> slice (start, end pointers)
+#   next-token(stream, slice, delim char) -> slice'
+#   slice-equal?(slice, string)
+
+convert-instruction:  # line : (address stream byte), out : (address buffered-file) -> <void>
+    # pseudocode:
+    #   word-slice = next-word
+    #   if (starts-with(word-slice, "#"))       # comments
+    #     write-stream-buffered(out, line)
+    #   ...
+    #
+    # . prolog
+    55/push-EBP
+    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+    # . save registers
+    51/push-ECX
+    # var word-slice/ECX = {0, 0}
+    68/push  0/imm32/end
+    68/push  0/imm32/start
+    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
+    # next-word(line, word-slice)
+    # . . push args
+    51/push-ECX
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
+    # . . call
+    e8/call  next-word/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+$convert-instruction:pass-line-through:
+    # write-stream-buffered(out, line)
+    # . . push args
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
+    # . . call
+    e8/call  write-stream-buffered/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+$convert-instruction:end:
+    # . restore registers
+    59/pop-to-ECX
+    # . epilog
+    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+    5d/pop-to-EBP
+    c3/return
+
+test-convert-instruction-passes-comments-through:
+    # if a line starts with '#', pass it along unchanged
     # . prolog
     55/push-EBP
     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
     # setup
-    # . clear-stream(_test-stream)
+    # . clear-stream(_test-input-stream)
     # . . push args
-    68/push  _test-stream/imm32
+    68/push  _test-input-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)
+    # . clear-stream(_test-output-stream)
     # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    50/push-EAX
+    68/push  _test-output-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-tmp-stream)
+    # . clear-stream(_test-output-buffered-file+4)
     # . . push args
-    68/push  _test-tmp-stream/imm32
+    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
     # initialize input
-    # . write(_test-tmp-stream, "    ")
+    # . write(_test-input-stream, "# abcd")
     # . . push args
-    68/push  "    "/imm32
-    68/push  _test-tmp-stream/imm32
+    68/push  "# abcd"/imm32
+    68/push  _test-input-stream/imm32
     # . . call
     e8/call  write/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-tmp-stream, _test-buffered-file)
+    # convert-instruction(_test-input-stream, _test-output-buffered-file)
     # . . push args
-    68/push  _test-buffered-file/imm32
-    68/push  _test-tmp-stream/imm32
+    68/push  _test-output-buffered-file/imm32
+    68/push  _test-input-stream/imm32
     # . . call
     e8/call  convert-instruction/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
     # check that the write happened as expected
-    # . flush(_test-buffered-file)
+    # . flush(_test-output-buffered-file)
     # . . push args
-    68/push  _test-buffered-file/imm32
+    68/push  _test-output-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-stream-equal(_test-stream, "    ", msg)
+    # . check-stream-equal(_test-output-stream, "# abcd", msg)
     # . . push args
-    68/push  "F - test-convert-instruction-passes-with-just-whitespace-through"/imm32
-    68/push  "    "/imm32
-    68/push  _test-stream/imm32
+    68/push  "F - test-convert-instruction-passes-comments-through"/imm32
+    68/push  "# abcd"/imm32
+    68/push  _test-output-stream/imm32
     # . . call
     e8/call  check-stream-equal/disp32
     # . . discard args
@@ -544,59 +648,59 @@ test-convert-instruction-passes-labels-through:
     55/push-EBP
     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
     # setup
-    # . clear-stream(_test-stream)
+    # . clear-stream(_test-input-stream)
     # . . push args
-    68/push  _test-stream/imm32
+    68/push  _test-input-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)
+    # . clear-stream(_test-output-stream)
     # . . push args
-    b8/copy-to-EAX  _test-buffered-file/imm32
-    05/add-to-EAX  4/imm32
-    50/push-EAX
+    68/push  _test-output-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-tmp-stream)
+    # . clear-stream(_test-output-buffered-file+4)
     # . . push args
-    68/push  _test-tmp-stream/imm32
+    b8/copy-to-EAX  _test-output-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
     # initialize input
-    # . write(_test-tmp-stream, "ab: # cd")
+    # . write(_test-input-stream, "ab: # cd")
     # . . push args
     68/push  "ab: # cd"/imm32
-    68/push  _test-tmp-stream/imm32
+    68/push  _test-input-stream/imm32
     # . . call
     e8/call  write/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # convert-instruction(_test-tmp-stream, _test-buffered-file)
+    # convert-instruction(_test-input-stream, _test-output-buffered-file)
     # . . push args
-    68/push  _test-buffered-file/imm32
-    68/push  _test-tmp-stream/imm32
+    68/push  _test-output-buffered-file/imm32
+    68/push  _test-input-stream/imm32
     # . . call
     e8/call  convert-instruction/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
     # check that the write happened as expected
-    # . flush(_test-buffered-file)
+    # . flush(_test-output-buffered-file)
     # . . push args
-    68/push  _test-buffered-file/imm32
+    68/push  _test-output-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-stream-equal(_test-stream, "ab: # cd", msg)
+    # . check-stream-equal(_test-output-stream, "ab: # cd", msg)
     # . . push args
     68/push  "F - test-convert-instruction-passes-labels-through"/imm32
     68/push  "ab: # cd"/imm32
-    68/push  _test-stream/imm32
+    68/push  _test-output-stream/imm32
     # . . call
     e8/call  check-stream-equal/disp32
     # . . discard args
@@ -1695,4 +1799,50 @@ _test-slice-non-number-word-end:
     2f/slash
 _test-slice-non-number-word-metadata-end:
 
+_test-input-stream:
+    # current write index
+    0/imm32
+    # current read index
+    0/imm32
+    # length
+    8/imm32
+    # data
+    00 00 00 00 00 00 00 00  # 8 bytes
+
+# a test buffered file for _test-input-stream
+_test-input-buffered-file:
+    # file descriptor or (address stream)
+    _test-input-stream/imm32
+    # current write index
+    0/imm32
+    # current read index
+    0/imm32
+    # length
+    6/imm32
+    # data
+    00 00 00 00 00 00  # 6 bytes
+
+_test-output-stream:
+    # current write index
+    0/imm32
+    # current read index
+    0/imm32
+    # length
+    8/imm32
+    # data
+    00 00 00 00 00 00 00 00  # 8 bytes
+
+# a test buffered file for _test-output-stream
+_test-output-buffered-file:
+    # file descriptor or (address stream)
+    _test-output-stream/imm32
+    # current write index
+    0/imm32
+    # current read index
+    0/imm32
+    # length
+    6/imm32
+    # data
+    00 00 00 00 00 00  # 6 bytes
+
 # . . vim:nowrap:textwidth=0