about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--069allocate.subx2
-rw-r--r--070new-stream.subx (renamed from 080new-stream.subx)0
-rw-r--r--071read-line.subx (renamed from 081read-line.subx)0
-rw-r--r--072slice.subx (renamed from 082slice.subx)0
-rw-r--r--073next-token.subx (renamed from 083next-token.subx)0
-rw-r--r--074write-stream-data.subx116
-rw-r--r--075print-int-decimal.subx (renamed from 084print-int-decimal.subx)0
-rw-r--r--076next-word.subx252
-rw-r--r--077subx-words.subx631
-rw-r--r--078emit-hex.subx249
-rw-r--r--079emit.subx484
-rw-r--r--080zero-out.subx (renamed from 086zero-out.subx)0
-rw-r--r--081table.subx (renamed from 088table.subx)0
-rw-r--r--082slurp.subx (renamed from 087slurp.subx)0
-rw-r--r--083subx-widths.subx238
-rw-r--r--084emit-hex-array.subx142
-rw-r--r--092write-int.subx (renamed from 089write-int.subx)0
-rw-r--r--093array-equal.subx (renamed from 085array-equal.subx)0
-rw-r--r--Readme.md12
-rwxr-xr-xapps/assortbin44323 -> 40203 bytes
-rw-r--r--apps/assort.subx52
-rwxr-xr-xapps/bracesbin43717 -> 39565 bytes
-rw-r--r--apps/braces.subx2
-rwxr-xr-xapps/callsbin49078 -> 44926 bytes
-rw-r--r--apps/calls.subx2
-rwxr-xr-xapps/crenshaw2-1bin38083 -> 39614 bytes
-rwxr-xr-xapps/crenshaw2-1bbin38642 -> 40173 bytes
-rwxr-xr-xapps/dquotesbin49512 -> 45445 bytes
-rw-r--r--apps/dquotes.subx110
-rwxr-xr-xapps/factorialbin37095 -> 38626 bytes
-rwxr-xr-xapps/handlebin37988 -> 39519 bytes
-rwxr-xr-xapps/hexbin19496 -> 42569 bytes
-rw-r--r--apps/hex.subx (renamed from 070---hex.subx)34
-rw-r--r--apps/mulisp.subx2
-rwxr-xr-xapps/packbin56994 -> 52864 bytes
-rw-r--r--apps/pack.subx112
-rwxr-xr-xapps/sigilsbin57016 -> 52864 bytes
-rw-r--r--apps/sigils.subx2
-rw-r--r--apps/subx-common.subx2084
-rw-r--r--apps/subx-params.subx (renamed from 071---subx-params.subx)3
-rwxr-xr-xapps/surveybin53596 -> 49460 bytes
-rw-r--r--apps/survey.subx40
-rwxr-xr-xapps/testsbin43140 -> 38988 bytes
-rw-r--r--apps/tests.subx44
-rwxr-xr-xbuild50
-rwxr-xr-xrun_one_test2
-rwxr-xr-xtest_apps108
-rwxr-xr-xtest_layers4
48 files changed, 2347 insertions, 2430 deletions
diff --git a/069allocate.subx b/069allocate.subx
index af3a7885..5e7a170d 100644
--- a/069allocate.subx
+++ b/069allocate.subx
@@ -28,6 +28,8 @@ Heap:
 # a reasonable default
 Heap-size:
   0x200000/imm32/2MB
+#?   # TODO: reclaim space allocated in tests.
+#?   0x2000000/imm32/32MB
 
 == code
 #   instruction                     effective address                                                   register    displacement    immediate
diff --git a/080new-stream.subx b/070new-stream.subx
index c3f204d2..c3f204d2 100644
--- a/080new-stream.subx
+++ b/070new-stream.subx
diff --git a/081read-line.subx b/071read-line.subx
index bce42750..bce42750 100644
--- a/081read-line.subx
+++ b/071read-line.subx
diff --git a/082slice.subx b/072slice.subx
index 09f45e18..09f45e18 100644
--- a/082slice.subx
+++ b/072slice.subx
diff --git a/083next-token.subx b/073next-token.subx
index 73a658c1..73a658c1 100644
--- a/083next-token.subx
+++ b/073next-token.subx
diff --git a/074write-stream-data.subx b/074write-stream-data.subx
new file mode 100644
index 00000000..d5d901b4
--- /dev/null
+++ b/074write-stream-data.subx
@@ -0,0 +1,116 @@
+== code
+#   instruction                     effective address                                                   register    displacement    immediate
+# . 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
+
+# write an entire stream's contents to a buffered-file
+# ways to do this:
+#   - construct a 'maximal slice' and pass it to write-slice-buffered
+#   - flush the buffered-file and pass the stream directly to its fd (disabling buffering)
+# we'll go with the first way for now
+write-stream-data:  # f : (address buffered-file), s : (address stream) -> <void>
+    # . prolog
+    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
+    56/push-esi
+    # esi = s
+    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   0xc/disp8       .                 # copy *(ebp+12) to esi
+    # var slice/ecx = {s->data, s->data + s->write}
+    # . push s->data + s->write
+    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # copy *esi to eax
+    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
+    50/push-eax
+    # . push s->data
+    8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           0/r32/eax   0xc/disp8       .                 # copy esi+12 to eax
+    50/push-eax
+    # . ecx = esp
+    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
+    # write-slice-buffered(f, slice)
+    # . . push args
+    51/push-ecx
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
+    # . . call
+    e8/call  write-slice-buffered/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+$write-stream-data:end:
+    # . restore locals
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . restore registers
+    5e/pop-to-esi
+    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
+    c3/return
+
+test-write-stream-data:
+    # . prolog
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # setup
+    # . clear-stream(_test-output-stream)
+    # . . push args
+    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
+    # . clear-stream(_test-input-stream)
+    # . . push args
+    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
+    # 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
+    # write-stream-data(_test-output-buffered-file, _test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    68/push  _test-output-buffered-file/imm32
+    # . . call
+    e8/call  write-stream-data/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-output-buffered-file)
+    # . . push args
+    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-output-stream, "abcd", msg)
+    # . . push args
+    68/push  "F - test-write-stream-data"/imm32
+    68/push  "abcd"/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-stream-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
diff --git a/084print-int-decimal.subx b/075print-int-decimal.subx
index f7898fe9..f7898fe9 100644
--- a/084print-int-decimal.subx
+++ b/075print-int-decimal.subx
diff --git a/076next-word.subx b/076next-word.subx
new file mode 100644
index 00000000..efd99a64
--- /dev/null
+++ b/076next-word.subx
@@ -0,0 +1,252 @@
+# Tokenize by whitespace.
+
+== code
+#   instruction                     effective address                                                   register    displacement    immediate
+# . 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
+
+# (re)compute the bounds of the next word in the line
+# return empty string on reaching end of file
+next-word:  # line : (address stream byte), out : (address slice)
+    # . prolog
+    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
+    56/push-esi
+    57/push-edi
+    # esi = line
+    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
+    # edi = out
+    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   0xc/disp8       .                 # copy *(ebp+12) to edi
+    # skip-chars-matching(line, ' ')
+    # . . push args
+    68/push  0x20/imm32/space
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
+    # . . call
+    e8/call  skip-chars-matching/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+$next-word:check0:
+    # if (line->read >= line->write) clear out and return
+    # . eax = line->read
+    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           0/r32/eax   4/disp8         .                 # copy *(esi+4) to eax
+    # . if (eax < line->write) goto next check
+    3b/compare                      0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # compare eax with *esi
+    7c/jump-if-lesser  $next-word:check-for-comment/disp8
+    # . return out = {0, 0}
+    c7          0/subop/copy        0/mod/direct    7/rm32/edi    .           .             .           .           .               0/imm32           # copy to *edi
+    c7          0/subop/copy        1/mod/*+disp8   7/rm32/edi    .           .             .           .           4/disp8         0/imm32           # copy to *(edi+4)
+    eb/jump  $next-word:end/disp8
+$next-word:check-for-comment:
+    # out->start = &line->data[line->read]
+    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(esi+4) to ecx
+    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
+    89/copy                         0/mod/indirect  7/rm32/edi    .           .             .           0/r32/eax   .               .                 # copy eax to *edi
+    # if (line->data[line->read] == '#') out->end = &line->data[line->write]), skip rest of stream and return
+    # . eax = line->data[line->read]
+    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
+    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
+    # . compare
+    3d/compare-eax-and  0x23/imm32/pound
+    75/jump-if-not-equal  $next-word:regular-word/disp8
+$next-word:comment:
+    # . out->end = &line->data[line->write]
+    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # copy *esi to eax
+    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
+    89/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           0/r32/eax   4/disp8         .                 # copy eax to *(edi+4)
+    # . line->read = line->write
+    89/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           0/r32/eax   4/disp8         .                 # copy eax to *(esi+4)
+    # . return
+    eb/jump  $next-word:end/disp8
+$next-word:regular-word:
+    # otherwise skip-chars-not-matching-whitespace(line)  # including trailing newline
+    # . . push args
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
+    # . . call
+    e8/call  skip-chars-not-matching-whitespace/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # out->end = &line->data[line->read]
+    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(esi+4) to ecx
+    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
+    89/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           0/r32/eax   4/disp8         .                 # copy eax to *(edi+4)
+$next-word:end:
+    # . restore registers
+    5f/pop-to-edi
+    5e/pop-to-esi
+    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
+    c3/return
+
+test-next-word:
+    # . 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
+    # var 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
+    # write(_test-stream, "  ab")
+    # . . push args
+    68/push  "  ab"/imm32
+    68/push  _test-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # next-word(_test-stream, slice)
+    # . . push args
+    51/push-ecx
+    68/push  _test-stream/imm32
+    # . . call
+    e8/call  next-word/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # check-ints-equal(slice->start - _test-stream->data, 2, msg)
+    # . check-ints-equal(slice->start - _test-stream, 14, msg)
+    # . . push args
+    68/push  "F - test-next-word: start"/imm32
+    68/push  0xe/imm32
+    # . . push slice->start - _test-stream
+    8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy *ecx to eax
+    81          5/subop/subtract    3/mod/direct    0/rm32/eax    .           .             .           .           .               _test-stream/imm32 # subtract from eax
+    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-ints-equal(slice->end - _test-stream->data, 4, msg)
+    # . check-ints-equal(slice->end - _test-stream, 16, msg)
+    # . . push args
+    68/push  "F - test-next-word: end"/imm32
+    68/push  0x10/imm32
+    # . . push slice->end - _test-stream
+    8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   4/disp8         .                 # copy *(ecx+4) to eax
+    81          5/subop/subtract    3/mod/direct    0/rm32/eax    .           .             .           .           .               _test-stream/imm32 # subtract from eax
+    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
+    # . epilog
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+test-next-word-returns-whole-comment:
+    # . 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
+    # var 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
+    # write(_test-stream, "  # a")
+    # . . push args
+    68/push  "  # a"/imm32
+    68/push  _test-stream/imm32
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # next-word(_test-stream, slice)
+    # . . push args
+    51/push-ecx
+    68/push  _test-stream/imm32
+    # . . call
+    e8/call  next-word/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # check-ints-equal(slice->start - _test-stream->data, 2, msg)
+    # . check-ints-equal(slice->start - _test-stream, 14, msg)
+    # . . push args
+    68/push  "F - test-next-word-returns-whole-comment: start"/imm32
+    68/push  0xe/imm32
+    # . . push slice->start - _test-stream
+    8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy *ecx to eax
+    81          5/subop/subtract    3/mod/direct    0/rm32/eax    .           .             .           .           .               _test-stream/imm32 # subtract from eax
+    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-ints-equal(slice->end - _test-stream->data, 5, msg)
+    # . check-ints-equal(slice->end - _test-stream, 17, msg)
+    # . . push args
+    68/push  "F - test-next-word-returns-whole-comment: end"/imm32
+    68/push  0x11/imm32
+    # . . push slice->end - _test-stream
+    8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   4/disp8         .                 # copy *(ecx+4) to eax
+    81          5/subop/subtract    3/mod/direct    0/rm32/eax    .           .             .           .           .               _test-stream/imm32 # subtract from eax
+    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
+    # . epilog
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+test-next-word-returns-empty-string-on-eof:
+    # . 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
+    # var 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
+    # write nothing to _test-stream
+    # next-word(_test-stream, slice)
+    # . . push args
+    51/push-ecx
+    68/push  _test-stream/imm32
+    # . . call
+    e8/call  next-word/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # check-ints-equal(slice->end - slice->start, 0, msg)
+    # . . push args
+    68/push  "F - test-next-word-returns-empty-string-on-eof"/imm32
+    68/push  0/imm32
+    # . . push slice->end - slice->start
+    8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   4/disp8         .                 # copy *(ecx+4) to eax
+    2b/subtract                     0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract *ecx from eax
+    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
+    # . epilog
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
diff --git a/077subx-words.subx b/077subx-words.subx
new file mode 100644
index 00000000..5bdf196a
--- /dev/null
+++ b/077subx-words.subx
@@ -0,0 +1,631 @@
+# Helpers for parsing SubX words, with their rules for hex, labels and metadata.
+
+== code
+#   instruction                     effective address                                                   register    displacement    immediate
+# . 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
+
+has-metadata?:  # word : (address slice), s : (address string) -> eax : boolean
+    # pseudocode:
+    #   var twig : &slice = next-token-from-slice(word->start, word->end, '/')  # skip name
+    #   curr = twig->end
+    #   while true
+    #     twig = next-token-from-slice(curr, word->end, '/')
+    #     if (twig.empty()) break
+    #     if (slice-equal?(twig, s)) return true
+    #     curr = twig->end
+    #   return false
+    # . prolog
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # . save registers
+    51/push-ecx
+    52/push-edx
+    56/push-esi
+    57/push-edi
+    # esi = word
+    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
+    # edx = word->end
+    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           2/r32/edx   4/disp8         .                 # copy *(esi+4) to edx
+    # var twig/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
+    # next-token-from-slice(word->start, word->end, '/', twig)
+    # . . push args
+    57/push-edi
+    68/push  0x2f/imm32/slash
+    52/push-edx
+    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
+    # curr/ecx = twig->end
+    8b/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(edi+4) to ecx
+$has-metadata?:loop:
+    # next-token-from-slice(curr, word->end, '/', twig)
+    # . . push args
+    57/push-edi
+    68/push  0x2f/imm32/slash
+    52/push-edx
+    51/push-ecx
+    # . . 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 (slice-empty?(twig)) return false
+    # . eax = slice-empty?(twig)
+    # . . push args
+    57/push-edi
+    # . . call
+    e8/call  slice-empty?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . if (eax != 0) return false
+    3d/compare-eax-and  0/imm32
+    75/jump-if-not-equal  $has-metadata?:false/disp8
+    # if (slice-equal?(twig, s)) return true
+    # . eax = slice-equal?(twig, s)
+    # . . push args
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
+    57/push-edi
+    # . . 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) return true
+    3d/compare-eax-and  0/imm32
+    75/jump-if-not-equal  $has-metadata?:true/disp8
+    # curr = twig->end
+    8b/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(edi+4) to ecx
+    eb/jump  $has-metadata?:loop/disp8
+$has-metadata?:true:
+    b8/copy-to-eax  1/imm32/true
+    eb/jump  $has-metadata?:end/disp8
+$has-metadata?:false:
+    b8/copy-to-eax  0/imm32/false
+$has-metadata?: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
+    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-has-metadata-true:
+    # . prolog
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # (eax..ecx) = "ab/imm32"
+    b8/copy-to-eax  "ab/imm32"/imm32
+    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
+    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
+    05/add-to-eax  4/imm32
+    # var in/esi : (address slice) = {eax, ecx}
+    51/push-ecx
+    50/push-eax
+    89/copy                         3/mod/direct    6/rm32/esi    .           .             .           4/r32/esp   .               .                 # copy esp to esi
+    # eax = has-metadata?(esi, "imm32")
+    # . . push args
+    68/push  "imm32"/imm32
+    56/push-esi
+    # . . call
+    e8/call  has-metadata?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # check-ints-equal(eax, 1, msg)
+    # . . push args
+    68/push  "F - test-has-metadata-true"/imm32
+    68/push  1/imm32/true
+    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
+    # . epilog
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+test-has-metadata-false:
+    # . prolog
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # (eax..ecx) = "ab/c"
+    b8/copy-to-eax  "ab/c"/imm32
+    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
+    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
+    05/add-to-eax  4/imm32
+    # var in/esi : (address slice) = {eax, ecx}
+    51/push-ecx
+    50/push-eax
+    89/copy                         3/mod/direct    6/rm32/esi    .           .             .           4/r32/esp   .               .                 # copy esp to esi
+    # eax = has-metadata?(esi, "d")
+    # . . push args
+    68/push  "d"/imm32
+    56/push-esi
+    # . . call
+    e8/call  has-metadata?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # check-ints-equal(eax, 0, msg)
+    # . . push args
+    68/push  "F - test-has-metadata-false"/imm32
+    68/push  0/imm32/false
+    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
+    # . epilog
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+test-has-metadata-ignore-name:
+    # . prolog
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # (eax..ecx) = "a/b"
+    b8/copy-to-eax  "a/b"/imm32
+    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
+    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
+    05/add-to-eax  4/imm32
+    # var in/esi : (address slice) = {eax, ecx}
+    51/push-ecx
+    50/push-eax
+    89/copy                         3/mod/direct    6/rm32/esi    .           .             .           4/r32/esp   .               .                 # copy esp to esi
+    # eax = has-metadata?(esi, "a")
+    # . . push args
+    68/push  "a"/imm32
+    56/push-esi
+    # . . call
+    e8/call  has-metadata?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # check-ints-equal(eax, 0, msg)
+    # . . push args
+    68/push  "F - test-has-metadata-ignore-name"/imm32
+    68/push  0/imm32/false
+    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
+    # . epilog
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+test-has-metadata-multiple-true:
+    # . prolog
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # (eax..ecx) = "a/b/c"
+    b8/copy-to-eax  "a/b/c"/imm32
+    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
+    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
+    05/add-to-eax  4/imm32
+    # var in/esi : (address slice) = {eax, ecx}
+    51/push-ecx
+    50/push-eax
+    89/copy                         3/mod/direct    6/rm32/esi    .           .             .           4/r32/esp   .               .                 # copy esp to esi
+    # eax = has-metadata?(esi, "c")
+    # . . push args
+    68/push  "c"/imm32
+    56/push-esi
+    # . . call
+    e8/call  has-metadata?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # check-ints-equal(eax, 1, msg)
+    # . . push args
+    68/push  "F - test-has-metadata-multiple-true"/imm32
+    68/push  1/imm32/true
+    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
+    # . epilog
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+test-has-metadata-multiple-false:
+    # . prolog
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # (eax..ecx) = "a/b/c"
+    b8/copy-to-eax  "a/b/c"/imm32
+    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
+    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
+    05/add-to-eax  4/imm32
+    # var in/esi : (address slice) = {eax, ecx}
+    51/push-ecx
+    50/push-eax
+    89/copy                         3/mod/direct    6/rm32/esi    .           .             .           4/r32/esp   .               .                 # copy esp to esi
+    # eax = has-metadata?(esi, "d")
+    # . . push args
+    68/push  "d"/imm32
+    56/push-esi
+    # . . call
+    e8/call  has-metadata?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # check-ints-equal(eax, 0, msg)
+    # . . push args
+    68/push  "F - test-has-metadata-multiple-false"/imm32
+    68/push  0/imm32/false
+    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
+    # . epilog
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+# conditions for 'valid' names that are not at risk of looking like hex numbers
+# keep in sync with the rules in labels.cc
+#: - if it starts with a digit, it's treated as a number. If it can't be
+#:   parsed as hex it will raise an error.
+#: - if it starts with '-' it's treated as a number.
+#: - if it starts with '0x' it's treated as a number. (redundant)
+#: - if it's two characters long, it can't be a name. Either it's a hex
+#:   byte, or it raises an error.
+is-valid-name?:  # in : (address slice) -> eax : boolean
+    # . prolog
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # . save registers
+    51/push-ecx
+    56/push-esi
+    # esi = in
+    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
+    # start/ecx = in->start
+    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # copy *esi to ecx
+    # end/eax = in->end
+    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           0/r32/eax   4/disp8         .                 # copy *(esi+4) to eax
+$is-valid-name?:check0:
+    # if (start >= end) return false
+    39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # compare ecx with eax
+    73/jump-if-greater-or-equal-unsigned  $is-valid-name?:false/disp8
+$is-valid-name?:check1:
+    # eax -= ecx
+    29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
+    # if (eax == 2) return false
+    3d/compare-eax-and  2/imm32
+    74/jump-if-equal  $is-valid-name?:false/disp8
+$is-valid-name?:check2:
+    # c/eax = *ecx
+    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
+    8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/AL    .               .                 # copy byte at *ecx to AL
+    # if (c == "-") return false
+    3d/compare-eax-and  2d/imm32/-
+    74/jump-if-equal  $is-valid-name?:false/disp8
+$is-valid-name?:check3a:
+    # if (c < "0") return true
+    3d/compare-eax-with  30/imm32/0
+    7c/jump-if-lesser  $is-valid-name?:true/disp8
+$is-valid-name?:check3b:
+    # if (c > "9") return true
+    3d/compare-eax-with  39/imm32/9
+    7f/jump-if-greater  $is-valid-name?:true/disp8
+$is-valid-name?:false:
+    # return false
+    b8/copy-to-eax  0/imm32/false
+    eb/jump  $is-valid-name?:end/disp8
+$is-valid-name?:true:
+    # return true
+    b8/copy-to-eax  1/imm32/true
+$is-valid-name?:end:
+    # . restore registers
+    5e/pop-to-esi
+    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-is-valid-name-digit-prefix:
+    # . prolog
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # (eax..ecx) = "34"
+    b8/copy-to-eax  "34"/imm32
+    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
+    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
+    05/add-to-eax  4/imm32
+    # var slice/ecx = {eax, ecx}
+    51/push-ecx
+    50/push-eax
+    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
+    # eax = is-valid-name?(slice)
+    # . . push args
+    51/push-ecx
+    # . . call
+    e8/call  is-valid-name?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # check-ints-equal(eax, 0, msg)
+    # . . push args
+    68/push  "F - test-is-valid-name-digit-prefix"/imm32
+    68/push  0/imm32/false
+    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
+    # . epilog
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+test-is-valid-name-negative-prefix:
+    # . prolog
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # (eax..ecx) = "-0x34"
+    b8/copy-to-eax  "-0x34"/imm32
+    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
+    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
+    05/add-to-eax  4/imm32
+    # var slice/ecx = {eax, ecx}
+    51/push-ecx
+    50/push-eax
+    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
+    # eax = is-valid-name?(slice)
+    # . . push args
+    51/push-ecx
+    # . . call
+    e8/call  is-valid-name?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # check-ints-equal(eax, 0, msg)
+    # . . push args
+    68/push  "F - test-is-valid-name-negative-prefix"/imm32
+    68/push  0/imm32/false
+    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
+    # . epilog
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+test-is-valid-name-0x-prefix:
+    # . prolog
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # (eax..ecx) = "0x34"
+    b8/copy-to-eax  "0x34"/imm32
+    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
+    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
+    05/add-to-eax  4/imm32
+    # var slice/ecx = {eax, ecx}
+    51/push-ecx
+    50/push-eax
+    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
+    # eax = is-valid-name?(slice)
+    # . . push args
+    51/push-ecx
+    # . . call
+    e8/call  is-valid-name?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # check-ints-equal(eax, 0, msg)
+    # . . push args
+    68/push  "F - test-is-valid-name-0x-prefix"/imm32
+    68/push  0/imm32/false
+    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
+    # . epilog
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+test-is-valid-name-starts-with-pre-digit:
+    # . prolog
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # (eax..ecx) = "/03"
+    b8/copy-to-eax  "/03"/imm32
+    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
+    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
+    05/add-to-eax  4/imm32
+    # var slice/ecx = {eax, ecx}
+    51/push-ecx
+    50/push-eax
+    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
+    # eax = is-valid-name?(slice)
+    # . . push args
+    51/push-ecx
+    # . . call
+    e8/call  is-valid-name?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # check-ints-equal(eax, 1, msg)
+    # . . push args
+    68/push  "F - test-is-valid-name-starts-with-pre-digit"/imm32
+    68/push  1/imm32/true
+    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
+    # . epilog
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+test-is-valid-name-starts-with-post-digit:
+    # . prolog
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # (eax..ecx) = "q34"
+    b8/copy-to-eax  "q34"/imm32
+    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
+    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
+    05/add-to-eax  4/imm32
+    # var slice/ecx = {eax, ecx}
+    51/push-ecx
+    50/push-eax
+    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
+    # eax = is-valid-name?(slice)
+    # . . push args
+    51/push-ecx
+    # . . call
+    e8/call  is-valid-name?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # check-ints-equal(eax, 1, msg)
+    # . . push args
+    68/push  "F - test-is-valid-name-starts-with-post-digit"/imm32
+    68/push  1/imm32/true
+    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
+    # . epilog
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+test-is-valid-name-starts-with-digit:
+    # . prolog
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # (eax..ecx) = "0x34"
+    b8/copy-to-eax  "0x34"/imm32
+    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
+    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
+    05/add-to-eax  4/imm32
+    # var slice/ecx = {eax, ecx}
+    51/push-ecx
+    50/push-eax
+    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
+    # eax = is-valid-name?(slice)
+    # . . push args
+    51/push-ecx
+    # . . call
+    e8/call  is-valid-name?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # check-ints-equal(eax, 0, msg)
+    # . . push args
+    68/push  "F - test-is-valid-name-starts-with-digit"/imm32
+    68/push  0/imm32/false
+    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
+    # . epilog
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+is-label?: # word : (address slice) -> eax : boolean
+    # . prolog
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # . save registers
+    51/push-ecx
+    # ecx = word
+    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
+    # ecx = word->end
+    8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(ecx+4) to ecx
+    # return *(word->end - 1) == ':'
+    # . eax = 0
+    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
+    # . eax = *((char *) word->end - 1)
+    8a/copy-byte                    1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/AL    -1/disp8         .                 # copy byte at *(ecx-1) to AL
+    # . return (eax == ':')
+    3d/compare-eax-and  0x3a/imm32/colon
+    b8/copy-to-eax  1/imm32/true
+    74/jump-if-equal  $is-label?:end/disp8
+    b8/copy-to-eax  0/imm32/false
+$is-label?: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-is-label?:
+    # . prolog
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+$test-is-label?:true:
+    # (eax..ecx) = "AAA:"
+    b8/copy-to-eax  "AAA:"/imm32
+    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
+    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
+    05/add-to-eax  4/imm32
+    # var slice/ecx = {eax, ecx}
+    51/push-ecx
+    50/push-eax
+    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
+    # is-label?(slice/ecx)
+    # . . push args
+    51/push-ecx
+    # . . call
+    e8/call  is-label?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # check-ints-equal(eax, 1, msg)
+    # . . push args
+    68/push  "F - test-is-label?:true"/imm32
+    68/push  1/imm32
+    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
+$test-is-label?:false:
+    # (eax..ecx) = "AAA"
+    b8/copy-to-eax  "AAA"/imm32
+    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
+    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
+    05/add-to-eax  4/imm32
+    # var slice/ecx = {eax, ecx}
+    51/push-ecx
+    50/push-eax
+    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
+    # is-label?(slice/ecx)
+    # . . push args
+    51/push-ecx
+    # . . call
+    e8/call  is-label?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # check-ints-equal(eax, 0, msg)
+    # . . push args
+    68/push  "F - test-is-label?:false"/imm32
+    68/push  0/imm32
+    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
+    # . epilog
+    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
+    5d/pop-to-ebp
+    c3/return
+
+# . . vim:nowrap:textwidth=0
diff --git a/078emit-hex.subx b/078emit-hex.subx
new file mode 100644
index 00000000..73912ea0
--- /dev/null
+++ b/078emit-hex.subx
@@ -0,0 +1,249 @@
+== code
+#   instruction                     effective address                                                   register    displacement    immediate
+# . 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
+
+# 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 -> <void>
+    # . prolog
+    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
+    53/push-ebx
+    57/push-edi
+    # edi = out
+    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   8/disp8         .                 # copy *(ebp+8) to edi
+    # ebx = n
+    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           3/r32/ebx   0xc/disp8       .                 # copy *(ebp+12) to ebx
+    # edx = width
+    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           2/r32/edx   0x10/disp8      .                 # copy *(ebp+16) to edx
+    # var curr/ecx = 0
+    31/xor                          3/mod/direct    1/rm32/ecx    .           .             .           1/r32/ecx   .               .                 # clear ecx
+$emit-hex:loop:
+    # if (curr >= width) break
+    39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
+    7d/jump-if-greater-or-equal  $emit-hex:end/disp8
+    # print-byte-buffered(out, ebx)
+    # . . push args
+    53/push-ebx
+    57/push-edi
+    # . . call
+    e8/call  print-byte-buffered/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # write-byte-buffered(out, ' ')
+    # . . push args
+    68/push  0x20/imm32/space
+    57/push-edi
+    # . . call
+    e8/call  write-byte-buffered/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # ebx = ebx >> 8
+    c1/shift    5/subop/logic-right 3/mod/direct    3/rm32/ebx    .           .             .           .           .               8/imm8            # shift ebx right by 8 bits, while padding zeroes
+$emit-hex:continue:
+    # ++curr
+    41/increment-ecx
+    eb/jump  $emit-hex:loop/disp8
+$emit-hex:end:
+    # . restore registers
+    5f/pop-to-edi
+    5b/pop-to-ebx
+    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
+    c3/return
+
+test-emit-hex-single-byte:
+    # setup
+    # . clear-stream(_test-output-stream)
+    # . . push args
+    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
+    # emit-hex(_test-output-buffered-file, 0xab, 1)
+    # . . push args
+    68/push  1/imm32
+    68/push  0xab/imm32
+    68/push  _test-output-buffered-file/imm32
+    # . . call
+    e8/call  emit-hex/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # flush(_test-output-buffered-file)
+    # . . push args
+    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-ints-equal(*_test-output-stream->data, 'ab ', msg)
+    # . . push args
+    68/push  "F - test-emit-hex-single-byte"/imm32
+    68/push  0x206261/imm32
+    # . . push *_test-output-stream->data
+    b8/copy-to-eax  _test-output-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
+    # . end
+    c3/return
+
+test-emit-hex-multiple-byte:
+    # setup
+    # . clear-stream(_test-output-stream)
+    # . . push args
+    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
+    # emit-hex(_test-output-buffered-file, 0x1234, 2)
+    # . . push args
+    68/push  2/imm32
+    68/push  0x1234/imm32
+    68/push  _test-output-buffered-file/imm32
+    # . . call
+    e8/call  emit-hex/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # flush(_test-output-buffered-file)
+    # . . push args
+    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-output-stream, "34 12 ", msg)
+    # . . push args
+    68/push  "F - test-emit-hex-multiple-byte/1"/imm32
+    68/push  "34 12 "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-stream-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . end
+    c3/return
+
+test-emit-hex-zero-pad:
+    # setup
+    # . clear-stream(_test-output-stream)
+    # . . push args
+    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
+    # emit-hex(_test-output-buffered-file, 0xab, 2)
+    # . . push args
+    68/push  2/imm32
+    68/push  0xab/imm32
+    68/push  _test-output-buffered-file/imm32
+    # . . call
+    e8/call  emit-hex/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # flush(_test-output-buffered-file)
+    # . . push args
+    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(_test-output-stream->data == 'ab 00 ')
+    # . . push args
+    68/push  "F - test-emit-hex-zero-pad/1"/imm32
+    68/push  "ab 00 "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-stream-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . end
+    c3/return
+
+test-emit-hex-negative:
+    # setup
+    # . clear-stream(_test-output-stream)
+    # . . push args
+    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
+    # emit-hex(_test-output-buffered-file, -1, 2)
+    # . . push args
+    68/push  2/imm32
+    68/push  -1/imm32
+    68/push  _test-output-buffered-file/imm32
+    # . . call
+    e8/call  emit-hex/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # flush(_test-output-buffered-file)
+    # . . push args
+    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-output-stream == "ff ff ")
+    # . . push args
+    68/push  "F - test-emit-hex-negative/1"/imm32
+    68/push  "ff ff "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-stream-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # . end
+    c3/return
+
+# . . vim:nowrap:textwidth=0
diff --git a/079emit.subx b/079emit.subx
new file mode 100644
index 00000000..b0c06de6
--- /dev/null
+++ b/079emit.subx
@@ -0,0 +1,484 @@
+== code
+#   instruction                     effective address                                                   register    displacement    immediate
+# . 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
+
+# If datum of 'word' is not a valid name, it must be a hex int. Parse and print
+# it in 'width' bytes of hex, least significant first.
+# Otherwise just print the entire word including metadata.
+# Always print a trailing space.
+emit:  # out : (address buffered-file), word : (address slice), width : int -> <void>
+    # . 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
+    # datum = 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-valid-name?(datum)) write-slice-buffered(out, word) and return
+    # . eax = is-valid-name?(name)
+    # . . push args
+    57/push-edi
+    # . . call
+    e8/call  is-valid-name?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . if (eax != 0)
+    3d/compare-eax-and  0/imm32
+    74/jump-if-equal  $emit:hex-int/disp8
+$emit:name:
+    # . write-slice-buffered(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-buffered/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . write-buffered(out, " ")
+    # . . push args
+    68/push  Space/imm32
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
+    # . . call
+    e8/call  write-buffered/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(datum), width)
+    #   (Weird shit can happen here if the datum of 'word' isn't either a valid
+    #   name or a hex number, but we're only going to be passing in real legal
+    #   programs. We just want to make sure that valid names aren't treated as
+    #   (valid) hex numbers.)
+$emit:hex-int:
+    # . value/eax = parse-hex-int(datum)
+    # . . 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, value, 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-output-stream)
+    # . . push args
+    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
+    # (eax..ecx) = "30"
+    b8/copy-to-eax  "30"/imm32
+    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
+    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
+    05/add-to-eax  4/imm32
+    # var slice/ecx = {eax, ecx}
+    51/push-ecx
+    50/push-eax
+    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
+    # emit(_test-output-buffered-file, slice, 1)
+    # . . push args
+    68/push  1/imm32
+    51/push-ecx
+    68/push  _test-output-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-output-buffered-file)
+    # . . push args
+    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-output-stream, "30 ", msg)
+    # . . push args
+    68/push  "F - test-emit-number/1"/imm32
+    68/push  "30 "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-stream-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-negative-number:
+    # test support for sign-extending negative numbers
+    # . prolog
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # setup
+    # . clear-stream(_test-output-stream)
+    # . . push args
+    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
+    # (eax..ecx) = "-2"
+    b8/copy-to-eax  "-2"/imm32
+    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
+    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
+    05/add-to-eax  4/imm32
+    # var slice/ecx = {eax, ecx}
+    51/push-ecx
+    50/push-eax
+    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
+    # emit(_test-output-buffered-file, slice, 2)
+    # . . push args
+    68/push  2/imm32
+    51/push-ecx
+    68/push  _test-output-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-output-buffered-file)
+    # . . push args
+    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-output-stream, "fe ff ", msg)
+    # . . push args
+    68/push  "F - test-emit-number/1"/imm32
+    68/push  "fe ff "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-stream-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-output-stream)
+    # . . push args
+    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
+    # (eax..ecx) = "-2/foo"
+    b8/copy-to-eax  "-2/foo"/imm32
+    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
+    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
+    05/add-to-eax  4/imm32
+    # var slice/ecx = {eax, ecx}
+    51/push-ecx
+    50/push-eax
+    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
+    # emit(_test-output-buffered-file, slice, 2)
+    # . . push args
+    68/push  2/imm32
+    51/push-ecx
+    68/push  _test-output-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-output-buffered-file)
+    # . . push args
+    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
+    # the '/foo' will have no impact on the output
+    # check-stream-equal(_test-output-stream, "fe ff ", msg)
+    # . . push args
+    68/push  "F - test-emit-number-with-metadata"/imm32
+    68/push  "fe ff "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-stream-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-output-stream)
+    # . . push args
+    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
+    # (eax..ecx) = "xyz"
+    b8/copy-to-eax  "xyz"/imm32
+    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
+    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
+    05/add-to-eax  4/imm32
+    # var slice/ecx = {eax, ecx}
+    51/push-ecx
+    50/push-eax
+    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
+    # emit(_test-output-buffered-file, slice, 2)
+    # . . push args
+    68/push  2/imm32
+    51/push-ecx
+    68/push  _test-output-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-output-buffered-file)
+    # . . push args
+    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-output-stream, "xyz", msg)
+    # . . push args
+    68/push  "F - test-emit-non-number"/imm32
+    68/push  "xyz "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-stream-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-output-stream)
+    # . . push args
+    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
+    # (eax..ecx) = "xyz/"
+    b8/copy-to-eax  "xyz/"/imm32
+    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
+    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
+    05/add-to-eax  4/imm32
+    # var slice/ecx = {eax, ecx}
+    51/push-ecx
+    50/push-eax
+    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
+    # emit(_test-output-buffered-file, slice, 2)
+    # . . push args
+    68/push  2/imm32
+    51/push-ecx
+    68/push  _test-output-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-output-buffered-file)
+    # . . push args
+    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-output-stream, "xyz/", msg)
+    # . . push args
+    68/push  "F - test-emit-non-number-with-metadata"/imm32
+    68/push  "xyz/ "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-stream-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-all-hex-digits-and-metadata:
+    # . prolog
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # setup
+    # . clear-stream(_test-output-stream)
+    # . . push args
+    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
+    # (eax..ecx) = "abcd/xyz"
+    b8/copy-to-eax  "abcd/xyz"/imm32
+    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
+    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
+    05/add-to-eax  4/imm32
+    # var slice/ecx = {eax, ecx}
+    51/push-ecx
+    50/push-eax
+    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
+    # emit(_test-output-buffered-file, slice, 2)
+    # . . push args
+    68/push  2/imm32
+    51/push-ecx
+    68/push  _test-output-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-output-buffered-file)
+    # . . push args
+    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
+#?     # dump output {{{
+#?     # . write(2/stderr, "^")
+#?     # . . push args
+#?     68/push  "^"/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
+#?     # . write-stream(2/stderr, _test-output-stream)
+#?     # . . push args
+#?     68/push  _test-output-stream/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write-stream/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . write(2/stderr, "$\n")
+#?     # . . push args
+#?     68/push  "$\n"/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
+#?     # }}}
+    # check-stream-equal(_test-output-stream, "abcd/xyz")
+    # . . push args
+    68/push  "F - test-emit-non-number-with-all-hex-digits"/imm32
+    68/push  "abcd/xyz "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-stream-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
diff --git a/086zero-out.subx b/080zero-out.subx
index 1a4c73d1..1a4c73d1 100644
--- a/086zero-out.subx
+++ b/080zero-out.subx
diff --git a/088table.subx b/081table.subx
index 602cd872..602cd872 100644
--- a/088table.subx
+++ b/081table.subx
diff --git a/087slurp.subx b/082slurp.subx
index 36a95d48..36a95d48 100644
--- a/087slurp.subx
+++ b/082slurp.subx
diff --git a/083subx-widths.subx b/083subx-widths.subx
new file mode 100644
index 00000000..18fe9833
--- /dev/null
+++ b/083subx-widths.subx
@@ -0,0 +1,238 @@
+== code
+#   instruction                     effective address                                                   register    displacement    immediate
+# . 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
+
+compute-width: # word : (address array byte) -> eax : int
+    # . prolog
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # . save registers
+    51/push-ecx
+    # eax = word
+    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   8/disp8         .                 # copy *(ebp+8) to ecx
+    # ecx = word + word->length
+    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
+    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
+    # eax = word->data
+    05/add-to-eax  4/imm32
+    # var in/ecx : (address slice) = {eax, ecx}
+    51/push-ecx
+    50/push-eax
+    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
+    # return compute-width-of-slice(ecx)
+    # . . push args
+    51/push-ecx
+    # . . call
+    e8/call  compute-width-of-slice/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+$compute-width:end:
+    # . reclaim locals
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/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
+
+compute-width-of-slice: # s : (address slice) -> eax : int
+    # . prolog
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # . save registers
+    51/push-ecx
+    # ecx = s
+    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
+    # if (has-metadata?(word, "imm32")) return 4
+    # . eax = has-metadata?(word, "imm32")
+    # . . push args
+    68/push  "imm32"/imm32
+    51/push-ecx
+    # . . call
+    e8/call  has-metadata?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . if (eax != 0) return 4
+    3d/compare-eax-and  0/imm32
+    b8/copy-to-eax  4/imm32         # ZF is set, so we can overwrite eax now
+    75/jump-if-not-equal  $compute-width-of-slice:end/disp8
+    # if (has-metadata?(word, "disp32")) return 4
+    # . eax = has-metadata?(word, "disp32")
+    # . . push args
+    68/push  "disp32"/imm32
+    51/push-ecx
+    # . . call
+    e8/call  has-metadata?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . if (eax != 0) return 4
+    3d/compare-eax-and  0/imm32
+    b8/copy-to-eax  4/imm32         # ZF is set, so we can overwrite eax now
+    75/jump-if-not-equal  $compute-width-of-slice:end/disp8
+    # if (has-metadata?(word, "imm16")) return 2
+    # . eax = has-metadata?(word, "imm16")
+    # . . push args
+    68/push  "imm16"/imm32
+    51/push-ecx
+    # . . call
+    e8/call  has-metadata?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . if (eax != 0) return 2
+    3d/compare-eax-and  0/imm32
+    b8/copy-to-eax  2/imm32         # ZF is set, so we can overwrite eax now
+    75/jump-if-not-equal  $compute-width-of-slice:end/disp8
+    # if (has-metadata?(word, "disp16")) return 2
+    # . eax = has-metadata?(word, "disp16")
+    # . . push args
+    68/push  "disp16"/imm32
+    51/push-ecx
+    # . . call
+    e8/call  has-metadata?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . if (eax != 0) return 2
+    3d/compare-eax-and  0/imm32
+    b8/copy-to-eax  2/imm32         # ZF is set, so we can overwrite eax now
+    75/jump-if-not-equal  $compute-width-of-slice:end/disp8
+    # otherwise return 1
+    b8/copy-to-eax  1/imm32
+$compute-width-of-slice: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-compute-width:
+    # . prolog
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+$test-compute-width:imm8:
+    # eax = compute-width("0x2/imm8")
+    # . . push args
+    68/push  "0x2/imm8"/imm32
+    # . . call
+    e8/call  compute-width/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # check-ints-equal(eax, 1, msg)
+    # . . push args
+    68/push  "F - test-compute-width: 0x2/imm8"/imm32
+    50/push-eax
+    68/push  1/imm32
+    # . . call
+    e8/call  check-ints-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+$test-compute-width:imm16:
+    # eax = compute-width("4/imm16")
+    # . . push args
+    68/push  "4/imm16"/imm32
+    # . . call
+    e8/call  compute-width/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # check-ints-equal(eax, 2, msg)
+    # . . push args
+    68/push  "F - test-compute-width: 4/imm16"/imm32
+    50/push-eax
+    68/push  2/imm32
+    # . . call
+    e8/call  check-ints-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+$test-compute-width:imm32:
+    # eax = compute-width("4/imm32")
+    # . . push args
+    68/push  "4/imm32"/imm32
+    # . . call
+    e8/call  compute-width/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # check-ints-equal(eax, 4, msg)
+    # . . push args
+    68/push  "F - test-compute-width: 4/imm32"/imm32
+    50/push-eax
+    68/push  4/imm32
+    # . . call
+    e8/call  check-ints-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+$test-compute-width:disp8:
+    # eax = compute-width("foo/disp8")
+    # . . push args
+    68/push  "foo/disp8"/imm32
+    # . . call
+    e8/call  compute-width/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # check-ints-equal(eax, 1, msg)
+    # . . push args
+    68/push  "F - test-compute-width: foo/disp8"/imm32
+    50/push-eax
+    68/push  1/imm32
+    # . . call
+    e8/call  check-ints-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+$test-compute-width:disp16:
+    # eax = compute-width("foo/disp16")
+    # . . push args
+    68/push  "foo/disp16"/imm32
+    # . . call
+    e8/call  compute-width/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # check-ints-equal(eax, 2, msg)
+    # . . push args
+    68/push  "F - test-compute-width: foo/disp16"/imm32
+    50/push-eax
+    68/push  2/imm32
+    # . . call
+    e8/call  check-ints-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+$test-compute-width:disp32:
+    # eax = compute-width("foo/disp32")
+    # . . push args
+    68/push  "foo/disp32"/imm32
+    # . . call
+    e8/call  compute-width/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # check-ints-equal(eax, 4, msg)
+    # . . push args
+    68/push  "F - test-compute-width: foo/disp32"/imm32
+    50/push-eax
+    68/push  4/imm32
+    # . . call
+    e8/call  check-ints-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+$test-compute-width:no-metadata:
+    # eax = compute-width("45")
+    # . . push args
+    68/push  "45"/imm32
+    # . . call
+    e8/call  compute-width/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # check-ints-equal(eax, 1, msg)
+    # . . push args
+    68/push  "F - test-compute-width: 45 (no metadata)"/imm32
+    50/push-eax
+    68/push  1/imm32
+    # . . 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
+
+# . . vim:nowrap:textwidth=0
diff --git a/084emit-hex-array.subx b/084emit-hex-array.subx
new file mode 100644
index 00000000..eb79d07f
--- /dev/null
+++ b/084emit-hex-array.subx
@@ -0,0 +1,142 @@
+== code
+#   instruction                     effective address                                                   register    displacement    immediate
+# . 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
+
+# print 'arr' in hex with a space after every byte
+emit-hex-array:  # out : (address buffered-file), arr : (address array byte) -> <void>
+    # . prolog
+    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
+    57/push-edi
+    # edi = out
+    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   8/disp8         .                 # copy *(ebp+8) to edi
+    # edx = arr  # <== 0xbdffffe4
+    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           2/r32/edx   0xc/disp8       .                 # copy *(ebp+12) to edx
+    # curr/ecx = arr->data
+    8d/copy-address                 1/mod/*+disp8   2/rm32/edx    .           .             .           1/r32/ecx   4/disp8         .                 # copy edx+4 to ecx
+    # max/edx = arr->data + arr->length
+    8b/copy                         0/mod/indirect  2/rm32/edx    .           .             .           2/r32/edx   .               .                 # copy *edx to edx
+    01/add                          3/mod/direct    2/rm32/edx    .           .             .           1/r32/ecx   .               .                 # add ecx to edx
+    # eax = 0
+    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
+$emit-hex-array:loop:
+    # if (curr >= width) break
+    39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
+    73/jump-if-greater-or-equal-unsigned  $emit-hex-array:end/disp8
+    # emit-hex(out, *curr, width=1)
+    # . . push args
+    68/push  1/imm32/width
+    8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/AL    .               .                 # copy byte at *ecx to AL
+    50/push-eax
+    57/push-edi
+    # . . call
+    e8/call  emit-hex/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
+    # ++curr
+    41/increment-ecx
+    eb/jump  $emit-hex-array:loop/disp8
+$emit-hex-array:end:
+    # . restore registers
+    5f/pop-to-edi
+    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
+    c3/return
+
+test-emit-hex-array:
+    # . prolog
+    55/push-ebp
+    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
+    # setup
+    # . clear-stream(_test-output-stream)
+    # . . push args
+    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
+    # var arr/ecx (address array byte) = [01, 02, 03]
+    68/push  0x00030201/imm32  # bytes 01 02 03
+    68/push  3/imm32/length
+    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
+    # emit-hex-array(_test-output-buffered-file, arr)
+    # . . push args
+    51/push-ecx
+    68/push  _test-output-buffered-file/imm32
+    # . . call
+    e8/call  emit-hex-array/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    # . flush(_test-output-buffered-file)
+    # . . push args
+    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
+#?     # dump output {{{
+#?     # . write(2/stderr, "result: ^")
+#?     # . . push args
+#?     68/push  "result: ^"/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
+#?     # . write-stream(2/stderr, _test-output-stream)
+#?     # . . push args
+#?     68/push  _test-output-stream/imm32
+#?     68/push  2/imm32/stderr
+#?     # . . call
+#?     e8/call  write-stream/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+#?     # . write(2/stderr, "$\n")
+#?     # . . push args
+#?     68/push  "$\n"/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
+#?     # . rewind-stream(_test-output-stream)
+#?     # . . push args
+#?     68/push  _test-output-stream/imm32
+#?     # . . call
+#?     e8/call  rewind-stream/disp32
+#?     # . . discard args
+#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+#?     # }}}
+    # check-next-stream-line-equal(_test-output-stream, "01 02 03 ", msg)
+    # . . push args
+    68/push  "F - test-emit-hex-array"/imm32
+    68/push  "01 02 03 "/imm32
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  check-next-stream-line-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
+
+# . . vim:nowrap:textwidth=0
diff --git a/089write-int.subx b/092write-int.subx
index 29ba8d2a..29ba8d2a 100644
--- a/089write-int.subx
+++ b/092write-int.subx
diff --git a/085array-equal.subx b/093array-equal.subx
index d6dec878..d6dec878 100644
--- a/085array-equal.subx
+++ b/093array-equal.subx
diff --git a/Readme.md b/Readme.md
index 733e1bb8..d4eefbcd 100644
--- a/Readme.md
+++ b/Readme.md
@@ -104,12 +104,12 @@ You can use SubX to translate itself. For example, running natively on Linux:
 
   ```sh
   # generate translator phases using the C++ translator
-  $ ./subx translate init.linux 0[0-6]*.subx 070---hex.subx                    -o hex
-  $ ./subx translate init.linux 0*.subx apps/subx-common.subx apps/survey.subx -o survey
-  $ ./subx translate init.linux 0*.subx apps/subx-common.subx apps/pack.subx   -o pack
-  $ ./subx translate init.linux 0*.subx apps/subx-common.subx apps/assort.subx -o assort
-  $ ./subx translate init.linux 0*.subx apps/subx-common.subx apps/dquotes.subx -o dquotes
-  $ ./subx translate init.linux 0*.subx apps/subx-common.subx apps/tests.subx  -o tests
+  $ ./subx translate init.linux 0*.subx apps/subx-params.subx apps/hex.subx    -o hex
+  $ ./subx translate init.linux 0*.subx apps/subx-params.subx apps/survey.subx -o survey
+  $ ./subx translate init.linux 0*.subx apps/subx-params.subx apps/pack.subx   -o pack
+  $ ./subx translate init.linux 0*.subx apps/subx-params.subx apps/assort.subx -o assort
+  $ ./subx translate init.linux 0*.subx apps/subx-params.subx apps/dquotes.subx -o dquotes
+  $ ./subx translate init.linux 0*.subx apps/subx-params.subx apps/tests.subx  -o tests
   $ chmod +x hex survey pack assort dquotes tests
 
   # use the generated translator phases to translate SubX programs
diff --git a/apps/assort b/apps/assort
index bdd302c3..0f876c2d 100755
--- a/apps/assort
+++ b/apps/assort
Binary files differdiff --git a/apps/assort.subx b/apps/assort.subx
index 2e9fee47..6b3eadba 100644
--- a/apps/assort.subx
+++ b/apps/assort.subx
@@ -7,7 +7,7 @@
 # because we don't know if they refer to the line above or the line below.
 #
 # To run:
-#   $ ./subx translate init.linux 0*.subx apps/subx-common.subx apps/assort.subx  -o apps/assort
+#   $ ./subx translate init.linux 0*.subx apps/subx-params.subx apps/assort.subx  -o apps/assort
 #   $ cat x
 #   == code
 #   abc
@@ -38,10 +38,10 @@ Entry:  # run tests if necessary, convert stdin if not
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 
     # - if argc > 1 and argv[1] == "test", then return run_tests()
-    # if (argc <= 1) goto run-main
+    # if (argc <= 1) goto interactive
     81          7/subop/compare     1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0/disp8         1/imm32           # compare *ebp
-    7e/jump-if-lesser-or-equal  $run-main/disp8
-    # if (!kernel-string-equal?(argv[1], "test")) goto run-main
+    7e/jump-if-lesser-or-equal  $subx-assort-main:interactive/disp8
+    # if (!kernel-string-equal?(argv[1], "test")) goto interactive
     # . eax = kernel-string-equal?(argv[1], "test")
     # . . push args
     68/push  "test"/imm32
@@ -50,15 +50,15 @@ Entry:  # run tests if necessary, convert stdin if not
     e8/call  kernel-string-equal?/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . if (eax == 0) goto run-main
+    # . if (eax == 0) goto interactive
     3d/compare-eax-and  0/imm32
-    74/jump-if-equal  $run-main/disp8
+    74/jump-if-equal  $subx-assort-main:interactive/disp8
     # run-tests()
     e8/call  run-tests/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
-    eb/jump  $main:end/disp8
-$run-main:
+    eb/jump  $subx-assort-main:end/disp8
+$subx-assort-main:interactive:
     # - otherwise convert stdin
     # var ed/eax : exit-descriptor
     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # subtract from esp
@@ -66,19 +66,19 @@ $run-main:
     # configure ed to really exit()
     # . ed->target = 0
     c7          0/subop/copy        0/mod/direct    0/rm32/eax    .           .             .           .           .               0/imm32           # copy to *eax
-    # convert(Stdin, Stdout, Stderr, ed)
+    # subx-assort(Stdin, Stdout, Stderr, ed)
     # . . push args
     50/push-eax/ed
     68/push  Stderr/imm32
     68/push  Stdout/imm32
     68/push  Stdin/imm32
     # . . call
-    e8/call  convert/disp32
+    e8/call  subx-assort/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
     # syscall(exit, 0)
     bb/copy-to-ebx  0/imm32
-$main:end:
+$subx-assort-main:end:
     b8/copy-to-eax  1/imm32/exit
     cd/syscall  0x80/imm8
 
@@ -86,7 +86,7 @@ $main:end:
 #   table: (address stream {string, (address stream byte)})     (8 bytes per row)
 # inefficient; uses sequential search for looking up segments by name
 
-convert:  # in : (address buffered-file), out : (address buffered-file) -> <void>
+subx-assort:  # in : (address buffered-file), out : (address buffered-file) -> <void>
     # pseudocode:
     #   var table : (address stream) = new-stream(10 rows, 8 bytes each)
     #   read-segments(in, table)
@@ -110,7 +110,7 @@ convert:  # in : (address buffered-file), out : (address buffered-file) -> <void
     e8/call  clear-stream/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-$convert:read:
+$subx-assort:read:
 #?     # print("read\n") {{{
 #?     # . . push args
 #?     68/push  "read\n"/imm32
@@ -128,7 +128,7 @@ $convert:read:
     e8/call  read-segments/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-$convert:write:
+$subx-assort:write:
 #?     # print("write\n") {{{
 #?     # . . push args
 #?     68/push  "write\n"/imm32
@@ -146,7 +146,7 @@ $convert:write:
     e8/call  write-segments/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-$convert:end:
+$subx-assort:end:
     # . reclaim locals
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x5c/imm32        # add to esp
     # . restore registers
@@ -156,7 +156,7 @@ $convert:end:
     5d/pop-to-ebp
     c3/return
 
-test-convert:
+test-subx-assort:
     # . prolog
     55/push-ebp
     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
@@ -320,12 +320,12 @@ test-convert:
     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)
+    # subx-assort(_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
+    e8/call  subx-assort/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # . flush(_test-output-buffered-file)
@@ -379,7 +379,7 @@ test-convert:
 #?     # }}}
     # . check-next-stream-line-equal(_test-output-stream, "== code 0x09000000", msg)
     # . . push args
-    68/push  "F - test-convert/0"/imm32
+    68/push  "F - test-subx-assort/0"/imm32
     68/push  "== code 0x09000000"/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -388,7 +388,7 @@ test-convert:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "1", msg)
     # . . push args
-    68/push  "F - test-convert/1"/imm32
+    68/push  "F - test-subx-assort/1"/imm32
     68/push  "1"/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -397,7 +397,7 @@ test-convert:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "2 3 # comment 4 inline with other contents", msg)
     # . . push args
-    68/push  "F - test-convert/2"/imm32
+    68/push  "F - test-subx-assort/2"/imm32
     68/push  "2 3 # comment 4 inline with other contents"/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -406,7 +406,7 @@ test-convert:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "6 7", msg)
     # . . push args
-    68/push  "F - test-convert/3"/imm32
+    68/push  "F - test-subx-assort/3"/imm32
     68/push  "6 7"/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -415,7 +415,7 @@ test-convert:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "8 9", msg)
     # . . push args
-    68/push  "F - test-convert/4"/imm32
+    68/push  "F - test-subx-assort/4"/imm32
     68/push  "8 9"/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -424,7 +424,7 @@ test-convert:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "10 11", msg)
     # . . push args
-    68/push  "F - test-convert/5"/imm32
+    68/push  "F - test-subx-assort/5"/imm32
     68/push  "10 11"/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -433,7 +433,7 @@ test-convert:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "== data 0x0a000000", msg)
     # . . push args
-    68/push  "F - test-convert/6"/imm32
+    68/push  "F - test-subx-assort/6"/imm32
     68/push  "== data 0x0a000000"/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -442,7 +442,7 @@ test-convert:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "4 5/imm32", msg)
     # . . push args
-    68/push  "F - test-convert/7"/imm32
+    68/push  "F - test-subx-assort/7"/imm32
     68/push  "4 5/imm32"/imm32
     68/push  _test-output-stream/imm32
     # . . call
diff --git a/apps/braces b/apps/braces
index 0c338864..53f14ae7 100755
--- a/apps/braces
+++ b/apps/braces
Binary files differdiff --git a/apps/braces.subx b/apps/braces.subx
index cb0fa9dd..694f7314 100644
--- a/apps/braces.subx
+++ b/apps/braces.subx
@@ -1,7 +1,7 @@
 # Structured control flow using break/loop rather than jump.
 #
 # To run (on Linux):
-#   $ ./ntranslate init.linux 0*.subx apps/subx-common.subx apps/calls.subx
+#   $ ./ntranslate init.linux 0*.subx apps/subx-params.subx apps/calls.subx
 #   $ mv a.elf apps/calls
 #
 # Example 1:
diff --git a/apps/calls b/apps/calls
index 04c8e297..dce364e3 100755
--- a/apps/calls
+++ b/apps/calls
Binary files differdiff --git a/apps/calls.subx b/apps/calls.subx
index 2f6ff3aa..c5cf74d3 100644
--- a/apps/calls.subx
+++ b/apps/calls.subx
@@ -1,7 +1,7 @@
 # Function calls in a single line.
 #
 # To run (on Linux):
-#   $ ./ntranslate init.linux 0*.subx apps/subx-common.subx apps/calls.subx
+#   $ ./ntranslate init.linux 0*.subx apps/subx-params.subx apps/calls.subx
 #   $ mv a.elf apps/calls
 #
 # Example 1:
diff --git a/apps/crenshaw2-1 b/apps/crenshaw2-1
index 1a895801..bdf3c0de 100755
--- a/apps/crenshaw2-1
+++ b/apps/crenshaw2-1
Binary files differdiff --git a/apps/crenshaw2-1b b/apps/crenshaw2-1b
index 5a1e448f..741d3c74 100755
--- a/apps/crenshaw2-1b
+++ b/apps/crenshaw2-1b
Binary files differdiff --git a/apps/dquotes b/apps/dquotes
index 096a032d..790a6684 100755
--- a/apps/dquotes
+++ b/apps/dquotes
Binary files differdiff --git a/apps/dquotes.subx b/apps/dquotes.subx
index 337992e4..f6976e47 100644
--- a/apps/dquotes.subx
+++ b/apps/dquotes.subx
@@ -2,7 +2,7 @@
 # Replace them with references to new variables in the data segment.
 #
 # To run:
-#   $ ./subx translate init.linux 0*.subx apps/subx-common.subx apps/dquotes.subx  -o apps/dquotes
+#   $ ./subx translate init.linux 0*.subx apps/subx-params.subx apps/dquotes.subx  -o apps/dquotes
 #   $ cat x
 #   == code
 #   ab "cd ef"/imm32
@@ -33,11 +33,11 @@ Entry:  # run tests if necessary, convert stdin if not
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 
-    # - if argc > 1 and argv[1] == "test", then return run_tests()
-    # if (argc <= 1) goto run-main
+    # - if argc > 1 and argv[1] == "test", then return run-tests()
+    # if (argc <= 1) goto interactive
     81          7/subop/compare     1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0/disp8         1/imm32           # compare *ebp
-    7e/jump-if-lesser-or-equal  $run-main/disp8
-    # if (!kernel-string-equal?(argv[1], "test")) goto run-main
+    7e/jump-if-lesser-or-equal  $subx-dquotes-main:interactive/disp8
+    # if (!kernel-string-equal?(argv[1], "test")) goto interactive
     # . eax = kernel-string-equal?(argv[1], "test")
     # . . push args
     68/push  "test"/imm32
@@ -46,15 +46,15 @@ Entry:  # run tests if necessary, convert stdin if not
     e8/call  kernel-string-equal?/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . if (eax == 0) goto run-main
+    # . if (eax == 0) goto interactive
     3d/compare-eax-and  0/imm32
-    74/jump-if-equal  $run-main/disp8
+    74/jump-if-equal  $subx-dquotes-main:interactive/disp8
     # run-tests()
     e8/call  run-tests/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
-    eb/jump  $main:end/disp8
-$run-main:
+    eb/jump  $subx-dquotes-main:end/disp8
+$subx-dquotes-main:interactive:
     # - otherwise convert stdin
     # var ed/eax : exit-descriptor
     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # subtract from esp
@@ -62,19 +62,19 @@ $run-main:
     # configure ed to really exit()
     # . ed->target = 0
     c7          0/subop/copy        0/mod/direct    0/rm32/eax    .           .             .           .           .               0/imm32           # copy to *eax
-    # convert(Stdin, 1/stdout, 2/stderr, ed)
+    # subx-dquotes(Stdin, 1/stdout, 2/stderr, ed)
     # . . push args
     50/push-eax/ed
     68/push  Stderr/imm32
     68/push  Stdout/imm32
     68/push  Stdin/imm32
     # . . call
-    e8/call  convert/disp32
+    e8/call  subx-dquotes/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
     # syscall(exit, 0)
     bb/copy-to-ebx  0/imm32
-$main:end:
+$subx-dquotes-main:end:
     b8/copy-to-eax  1/imm32/exit
     cd/syscall  0x80/imm8
 
@@ -82,7 +82,7 @@ $main:end:
 #   line = words separated by ' ', maybe followed by comment starting with '#'
 #   word = datum until '/', then 0 or more metadata separated by '/'
 
-convert:  # in : (address buffered-file), out : (address buffered-file) -> <void>
+subx-dquotes:  # in : (address buffered-file), out : (address buffered-file) -> <void>
     # pseudocode:
     #   var line = new-stream(512, 1)
     #   var new-data-segment = new-stream(Heap, Segment-size, 1)
@@ -146,7 +146,7 @@ convert:  # in : (address buffered-file), out : (address buffered-file) -> <void
     e8/call  write/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-$convert:line-loop:
+$subx-dquotes:line-loop:
     # clear-stream(line)
     # . . push args
     51/push-ecx
@@ -162,11 +162,11 @@ $convert:line-loop:
     e8/call  read-line-buffered/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-$convert:check0:
+$subx-dquotes:check0:
     # if (line->write == 0) break
     81          7/subop/compare     0/mod/indirect  1/rm32/ecx    .           .             .           .           .               0/imm32           # compare *ecx
-    0f 84/jump-if-equal  $convert:break/disp32
-$convert:word-loop:
+    0f 84/jump-if-equal  $subx-dquotes:break/disp32
+$subx-dquotes:word-loop:
     # next-word-or-string(line, word-slice)
     # . . push args
     52/push-edx
@@ -175,7 +175,7 @@ $convert:word-loop:
     e8/call  next-word-or-string/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-$convert:check1:
+$subx-dquotes:check1:
     # if (slice-empty?(word-slice)) break
     # . eax = slice-empty?(word-slice)
     # . . push args
@@ -186,8 +186,8 @@ $convert:check1:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
     # . if (eax != 0) break
     3d/compare-eax-and  0/imm32
-    0f 85/jump-if-not-equal  $convert:next-line/disp32
-$convert:check-for-comment:
+    0f 85/jump-if-not-equal  $subx-dquotes:next-line/disp32
+$subx-dquotes:check-for-comment:
     # if (slice-starts-with?(word-slice, "#")) continue
     # . start/esi = word-slice->start
     8b/copy                         0/mod/indirect  2/rm32/edx    .           .             .           6/r32/esi   .               .                 # copy *edx to esi
@@ -196,12 +196,12 @@ $convert:check-for-comment:
     8a/copy-byte                    0/mod/indirect  6/rm32/esi    .           .             .           0/r32/AL    .               .                 # copy byte at *esi to AL
     # . if (eax == '#') continue
     3d/compare-eax-and  0x23/imm32/hash
-    74/jump-if-equal  $convert:word-loop/disp8
-$convert:check-for-string-literal:
+    74/jump-if-equal  $subx-dquotes:word-loop/disp8
+$subx-dquotes:check-for-string-literal:
     # if (slice-starts-with?(word-slice, '"')) continue
     3d/compare-eax-and  0x22/imm32/dquote
-    75/jump-if-not-equal  $convert:regular-word/disp8
-$convert:string-literal:
+    75/jump-if-not-equal  $subx-dquotes:regular-word/disp8
+$subx-dquotes:string-literal:
     # process-string-literal(word-slice, out, new-data-segment)
     # . . push args
     57/push-edi
@@ -212,8 +212,8 @@ $convert:string-literal:
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # continue
-    eb/jump  $convert:next-word/disp8
-$convert:regular-word:
+    eb/jump  $subx-dquotes:next-word/disp8
+$subx-dquotes:regular-word:
     # write-slice-buffered(out, word-slice)
     # . . push args
     52/push-edx
@@ -223,7 +223,7 @@ $convert:regular-word:
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # fall through
-$convert:next-word:
+$subx-dquotes:next-word:
     # write-buffered(out, " ")
     # . . push args
     68/push  Space/imm32
@@ -233,8 +233,8 @@ $convert:next-word:
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # loop
-    eb/jump  $convert:word-loop/disp8
-$convert:next-line:
+    eb/jump  $subx-dquotes:word-loop/disp8
+$subx-dquotes:next-line:
     # write-buffered(out, "\n")
     # . . push args
     68/push  Newline/imm32
@@ -244,8 +244,8 @@ $convert:next-line:
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # loop
-    e9/jump  $convert:line-loop/disp32
-$convert:break:
+    e9/jump  $subx-dquotes:line-loop/disp32
+$subx-dquotes:break:
     # write-stream-data(out, new-data-segment)
     # . . push args
     57/push-edi
@@ -261,7 +261,7 @@ $convert:break:
     e8/call  flush/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-$convert:end:
+$subx-dquotes:end:
     # . reclaim locals
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x214/imm32       # add to esp
     # . restore registers
@@ -383,7 +383,7 @@ $process-string-literal:end:
     5d/pop-to-ebp
     c3/return
 
-test-convert-is-idempotent-by-default:
+test-subx-dquotes-is-idempotent-by-default:
     # . prolog
     55/push-ebp
     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
@@ -502,12 +502,12 @@ test-convert-is-idempotent-by-default:
     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)
+    # subx-dquotes(_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
+    e8/call  subx-dquotes/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # . flush(_test-output-buffered-file)
@@ -556,7 +556,7 @@ test-convert-is-idempotent-by-default:
 #?     # }}}
     # . check-next-stream-line-equal(_test-output-stream, "", msg)
     # . . push args
-    68/push  "F - test-convert-is-idempotent-by-default/0"/imm32
+    68/push  "F - test-subx-dquotes-is-idempotent-by-default/0"/imm32
     68/push  ""/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -565,7 +565,7 @@ test-convert-is-idempotent-by-default:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "", msg)
     # . . push args
-    68/push  "F - test-convert-is-idempotent-by-default/1"/imm32
+    68/push  "F - test-subx-dquotes-is-idempotent-by-default/1"/imm32
     68/push  ""/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -574,7 +574,7 @@ test-convert-is-idempotent-by-default:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "== code 0x1 ", msg)
     # . . push args
-    68/push  "F - test-convert-is-idempotent-by-default/2"/imm32
+    68/push  "F - test-subx-dquotes-is-idempotent-by-default/2"/imm32
     68/push  "== code 0x1 "/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -583,7 +583,7 @@ test-convert-is-idempotent-by-default:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "", msg)
     # . . push args
-    68/push  "F - test-convert-is-idempotent-by-default/3"/imm32
+    68/push  "F - test-subx-dquotes-is-idempotent-by-default/3"/imm32
     68/push  ""/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -592,7 +592,7 @@ test-convert-is-idempotent-by-default:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "1 ", msg)
     # . . push args
-    68/push  "F - test-convert-is-idempotent-by-default/4"/imm32
+    68/push  "F - test-subx-dquotes-is-idempotent-by-default/4"/imm32
     68/push  "1 "/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -601,7 +601,7 @@ test-convert-is-idempotent-by-default:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "", msg)
     # . . push args
-    68/push  "F - test-convert-is-idempotent-by-default/5"/imm32
+    68/push  "F - test-subx-dquotes-is-idempotent-by-default/5"/imm32
     68/push  ""/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -610,7 +610,7 @@ test-convert-is-idempotent-by-default:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "2 3 ", msg)
     # . . push args
-    68/push  "F - test-convert-is-idempotent-by-default/6"/imm32
+    68/push  "F - test-subx-dquotes-is-idempotent-by-default/6"/imm32
     68/push  "2 3 "/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -619,7 +619,7 @@ test-convert-is-idempotent-by-default:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "== data 0x2 ", msg)
     # . . push args
-    68/push  "F - test-convert-is-idempotent-by-default/7"/imm32
+    68/push  "F - test-subx-dquotes-is-idempotent-by-default/7"/imm32
     68/push  "== data 0x2 "/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -628,7 +628,7 @@ test-convert-is-idempotent-by-default:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "4 5/imm32 ", msg)
     # . . push args
-    68/push  "F - test-convert-is-idempotent-by-default/8"/imm32
+    68/push  "F - test-subx-dquotes-is-idempotent-by-default/8"/imm32
     68/push  "4 5/imm32 "/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -640,7 +640,7 @@ test-convert-is-idempotent-by-default:
     5d/pop-to-ebp
     c3/return
 
-test-convert-processes-string-literals:
+test-subx-dquotes-processes-string-literals:
     # . prolog
     55/push-ebp
     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
@@ -703,12 +703,12 @@ test-convert-processes-string-literals:
     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)
+    # subx-dquotes(_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
+    e8/call  subx-dquotes/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # . flush(_test-output-buffered-file)
@@ -769,7 +769,7 @@ test-convert-processes-string-literals:
 #?     # }}}
     # . check-next-stream-line-equal(_test-output-stream, "== code 0x1 ", msg)
     # . . push args
-    68/push  "F - test-convert-processes-string-literals/0"/imm32
+    68/push  "F - test-subx-dquotes-processes-string-literals/0"/imm32
     68/push  "== code 0x1 "/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -778,7 +778,7 @@ test-convert-processes-string-literals:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "1 _string1/x ", msg)
     # . . push args
-    68/push  "F - test-convert-processes-string-literals/1"/imm32
+    68/push  "F - test-subx-dquotes-processes-string-literals/1"/imm32
     68/push  "1 _string1/x "/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -787,7 +787,7 @@ test-convert-processes-string-literals:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "2 _string2/y ", msg)
     # . . push args
-    68/push  "F - test-convert-processes-string-literals/2"/imm32
+    68/push  "F - test-subx-dquotes-processes-string-literals/2"/imm32
     68/push  "2 _string2/y "/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -796,7 +796,7 @@ test-convert-processes-string-literals:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "== data", msg)
     # . . push args
-    68/push  "F - test-convert-processes-string-literals/3"/imm32
+    68/push  "F - test-subx-dquotes-processes-string-literals/3"/imm32
     68/push  "== data"/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -805,7 +805,7 @@ test-convert-processes-string-literals:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "_string1: ", msg)
     # . . push args
-    68/push  "F - test-convert-processes-string-literals/4"/imm32
+    68/push  "F - test-subx-dquotes-processes-string-literals/4"/imm32
     68/push  "_string1:"/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -814,7 +814,7 @@ test-convert-processes-string-literals:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "1/imm32 61/a ", msg)
     # . . push args
-    68/push  "F - test-convert-processes-string-literals/5"/imm32
+    68/push  "F - test-subx-dquotes-processes-string-literals/5"/imm32
     68/push  "0x00000001/imm32 61/a "/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -823,7 +823,7 @@ test-convert-processes-string-literals:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "_string2: ", msg)
     # . . push args
-    68/push  "F - test-convert-processes-string-literals/6"/imm32
+    68/push  "F - test-subx-dquotes-processes-string-literals/6"/imm32
     68/push  "_string2:"/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -832,7 +832,7 @@ test-convert-processes-string-literals:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "2/imm32 62/b 63/c ", msg)
     # . . push args
-    68/push  "F - test-convert-processes-string-literals/7"/imm32
+    68/push  "F - test-subx-dquotes-processes-string-literals/7"/imm32
     68/push  "0x00000002/imm32 62/b 63/c "/imm32
     68/push  _test-output-stream/imm32
     # . . call
diff --git a/apps/factorial b/apps/factorial
index 2641faa9..07792615 100755
--- a/apps/factorial
+++ b/apps/factorial
Binary files differdiff --git a/apps/handle b/apps/handle
index 21e66ccb..237003dc 100755
--- a/apps/handle
+++ b/apps/handle
Binary files differdiff --git a/apps/hex b/apps/hex
index 9dd432b7..64218e10 100755
--- a/apps/hex
+++ b/apps/hex
Binary files differdiff --git a/070---hex.subx b/apps/hex.subx
index ffe72d06..e5f13077 100644
--- a/070---hex.subx
+++ b/apps/hex.subx
@@ -3,7 +3,7 @@
 # comments between '#' and newline.
 #
 # To run:
-#   $ ./subx translate init.linux 0*.subx apps/subx-common.subx apps/hex.subx  -o apps/hex
+#   $ ./subx translate init.linux 0*.subx apps/subx-params.subx apps/hex.subx  -o apps/hex
 #   $ echo '80 81 82  # comment'  |./subx run apps/hex  |xxd -
 # Expected output:
 #   00000000: 8081 82
@@ -32,10 +32,10 @@ Entry:  # run tests if necessary, convert stdin if not
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 
     # - if argc > 1 and argv[1] == "test", then return run_tests()
-    # if (argc <= 1) goto run-main
+    # if (argc <= 1) goto interactive
     81          7/subop/compare     1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0/disp8         1/imm32           # compare *ebp
-    7e/jump-if-lesser-or-equal  $hex:run-main/disp8
-    # if (!kernel-string-equal?(argv[1], "test")) goto run-main
+    7e/jump-if-lesser-or-equal  $subx-hex-main:interactive/disp8
+    # if (!kernel-string-equal?(argv[1], "test")) goto interactive
     # . eax = kernel-string-equal?(argv[1], "test")
     # . . push args
     68/push  "test"/imm32
@@ -44,15 +44,15 @@ Entry:  # run tests if necessary, convert stdin if not
     e8/call  kernel-string-equal?/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . if (eax == 0) goto run-main
+    # . if (eax == 0) goto interactive
     3d/compare-eax-and  0/imm32
-    74/jump-if-equal  $hex:run-main/disp8
+    74/jump-if-equal  $subx-hex-main:interactive/disp8
     # run-tests()
     e8/call  run-tests/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
-    eb/jump  $hex:end/disp8
-$hex:run-main:
+    eb/jump  $subx-hex-main:end/disp8
+$subx-hex-main:interactive:
     # - otherwise convert stdin
     # var ed/eax : exit-descriptor
     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # subtract from esp
@@ -60,24 +60,24 @@ $hex:run-main:
     # configure ed to really exit()
     # . ed->target = 0
     c7          0/subop/copy        0/mod/direct    0/rm32/eax    .           .             .           .           .               0/imm32           # copy to *eax
-    # convert-hex(Stdin, 1/stdout, 2/stderr, ed)
+    # subx-hex(Stdin, 1/stdout, 2/stderr, ed)
     # . . push args
     50/push-eax/ed
     68/push  Stderr/imm32
     68/push  Stdout/imm32
     68/push  Stdin/imm32
     # . . call
-    e8/call  convert-hex/disp32
+    e8/call  subx-hex/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
     # syscall(exit, 0)
     bb/copy-to-ebx  0/imm32
-$hex:end:
+$subx-hex-main:end:
     b8/copy-to-eax  1/imm32/exit
     cd/syscall  0x80/imm8
 
 # the main entry point
-convert-hex:  # in : (address buffered-file), out : (address buffered-file), err : (address buffered-file), ed : (address exit-descriptor) -> <void>
+subx-hex:  # in : (address buffered-file), out : (address buffered-file), err : (address buffered-file), ed : (address exit-descriptor) -> <void>
     # pseudocode:
     #   while true
     #     eax = convert-next-octet(in, err, ed)
@@ -90,7 +90,7 @@ convert-hex:  # in : (address buffered-file), out : (address buffered-file), err
     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
     # . save registers
     50/push-eax
-$convert-hex:loop:
+$subx-hex:loop:
     # eax = convert-next-octet(in, err, ed)
     # . . push args
     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x14/disp8      .                 # push *(ebp+20)
@@ -102,7 +102,7 @@ $convert-hex:loop:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # if (eax == Eof) break
     3d/compare-eax-and  0xffffffff/imm32/Eof
-    74/jump-if-equal  $convert-hex:loop-end/disp8
+    74/jump-if-equal  $subx-hex:loop-end/disp8
     # write-byte-buffered(out, AL)
     # . . push args
     50/push-eax
@@ -112,8 +112,8 @@ $convert-hex:loop:
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # loop
-    eb/jump  $convert-hex:loop/disp8
-$convert-hex:loop-end:
+    eb/jump  $subx-hex:loop/disp8
+$subx-hex:loop-end:
     # flush(out)
     # . . push args
     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
@@ -121,7 +121,7 @@ $convert-hex:loop-end:
     e8/call  flush/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-$convert-hex:end:
+$subx-hex:end:
     # . restore registers
     58/pop-to-eax
     # . epilog
diff --git a/apps/mulisp.subx b/apps/mulisp.subx
index 4ae26e82..9bc2d0b7 100644
--- a/apps/mulisp.subx
+++ b/apps/mulisp.subx
@@ -1,7 +1,7 @@
 # Toy lisp interpreter
 #
 # To run:
-#   $ ./ntranslate init.linux 0*.subx apps/subx-common.subx apps/mulisp.subx
+#   $ ./ntranslate init.linux 0*.subx apps/subx-params.subx apps/mulisp.subx
 #   $ ./a.elf
 #   42
 #   => 42
diff --git a/apps/pack b/apps/pack
index f9907948..95782b8b 100755
--- a/apps/pack
+++ b/apps/pack
Binary files differdiff --git a/apps/pack.subx b/apps/pack.subx
index 69c37bd4..1be0c4c4 100644
--- a/apps/pack.subx
+++ b/apps/pack.subx
@@ -3,7 +3,7 @@
 # uses are left untouched.
 #
 # To run:
-#   $ ./subx translate init.linux 0*.subx apps/subx-common.subx apps/pack.subx  -o apps/pack
+#   $ ./subx translate init.linux 0*.subx apps/subx-params.subx apps/pack.subx  -o apps/pack
 #   $ echo '05/add-to-eax 0x20/imm32'  |./subx run apps/pack
 # Expected output:
 #   05 20 00 00 00  # 05/add-to-eax 0x20/imm32
@@ -33,10 +33,10 @@ Entry:  # run tests if necessary, convert stdin if not
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 
     # - if argc > 1 and argv[1] == "test", then return run_tests()
-    # if (argc <= 1) goto run-main
+    # if (argc <= 1) goto interactive
     81          7/subop/compare     1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0/disp8         1/imm32           # compare *ebp
-    7e/jump-if-lesser-or-equal  $run-main/disp8
-    # if (!kernel-string-equal?(argv[1], "test")) goto run-main
+    7e/jump-if-lesser-or-equal  $subx-pack-main:interactive/disp8
+    # if (!kernel-string-equal?(argv[1], "test")) goto interactive
     # . eax = kernel-string-equal?(argv[1], "test")
     # . . push args
     68/push  "test"/imm32
@@ -45,15 +45,15 @@ Entry:  # run tests if necessary, convert stdin if not
     e8/call  kernel-string-equal?/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . if (eax == 0) goto run-main
+    # . if (eax == 0) goto interactive
     3d/compare-eax-and  0/imm32
-    74/jump-if-equal  $run-main/disp8
+    74/jump-if-equal  $subx-pack-main:interactive/disp8
     # run-tests()
     e8/call  run-tests/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
-    eb/jump  $main:end/disp8
-$run-main:
+    eb/jump  $subx-pack-main:end/disp8
+$subx-pack-main:interactive:
     # - otherwise convert stdin
     # var ed/eax : exit-descriptor
     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # subtract from esp
@@ -61,19 +61,19 @@ $run-main:
     # configure ed to really exit()
     # . ed->target = 0
     c7          0/subop/copy        0/mod/direct    0/rm32/eax    .           .             .           .           .               0/imm32           # copy to *eax
-    # convert(Stdin, Stdout, Stderr, ed)
+    # subx-pack(Stdin, Stdout, Stderr, ed)
     # . . push args
     50/push-eax/ed
     68/push  Stderr/imm32
     68/push  Stdout/imm32
     68/push  Stdin/imm32
     # . . call
-    e8/call  convert/disp32
+    e8/call  subx-pack/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
     # syscall(exit, 0)
     bb/copy-to-ebx  0/imm32
-$main:end:
+$subx-pack-main:end:
     b8/copy-to-eax  1/imm32/exit
     cd/syscall  0x80/imm8
 
@@ -97,7 +97,7 @@ $main:end:
 #   next-token-from-slice(start, end, delim char) -> slice
 #   slice-equal?(slice, string)
 
-convert:  # in : (address buffered-file), out : (address buffered-file) -> <void>
+subx-pack:  # in : (address buffered-file), out : (address buffered-file) -> <void>
     # pseudocode:
     #   var line = new-stream(512, 1)
     #   var in-code? = false
@@ -140,7 +140,7 @@ convert:  # in : (address buffered-file), out : (address buffered-file) -> <void
     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
     # var in-code?/ebx = false
     31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
-$convert:loop:
+$subx-pack:loop:
     # clear-stream(line)
     # . . push args
     51/push-ecx
@@ -156,10 +156,10 @@ $convert:loop:
     e8/call  read-line-buffered/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-$convert:check0:
+$subx-pack:check0:
     # if (line->write == 0) break
     81          7/subop/compare     0/mod/indirect  1/rm32/ecx    .           .             .           .           .               0/imm32           # compare *ecx
-    0f 84/jump-if-equal  $convert:break/disp32
+    0f 84/jump-if-equal  $subx-pack:break/disp32
 #?     # dump line {{{
 #?     # . write(2/stderr, "LL: ")
 #?     # . . push args
@@ -194,7 +194,7 @@ $convert:check0:
     e8/call  next-word/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-$convert:check1:
+$subx-pack:check1:
     # if (slice-empty?(word-slice)) write-stream-data(out, line)
     # . eax = slice-empty?(word-slice)
     # . . push args
@@ -205,8 +205,8 @@ $convert:check1:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
     # . if (eax != 0) write-stream-data(out, line)
     3d/compare-eax-and  0/imm32
-    0f 85/jump-if-not-equal  $convert:pass-through/disp32
-$convert:check2:
+    0f 85/jump-if-not-equal  $subx-pack:pass-through/disp32
+$subx-pack:check2:
 #?     # dump word-slice {{{
 #?     # . write(2/stderr, "AA: ")
 #?     # . . push args
@@ -260,7 +260,7 @@ $convert:check2:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # . if (eax == 0) goto check3
     3d/compare-eax-and  0/imm32
-    0f 84/jump-if-equal  $convert:check3/disp32
+    0f 84/jump-if-equal  $subx-pack:check3/disp32
     # word-slice = next-word(line)
     # . . push args
     52/push-edx
@@ -322,8 +322,8 @@ $convert:check2:
     # . . in-code? = eax
     89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           0/r32/eax   .               .                 # copy eax to ebx
     # write-stream-data(out, line)
-    eb/jump  $convert:pass-through/disp8
-$convert:check3:
+    eb/jump  $subx-pack:pass-through/disp8
+$subx-pack:check3:
     # else rewind-stream(line)
     # . rewind-stream(line)
     # . . push args
@@ -334,8 +334,8 @@ $convert:check3:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
     # if (in-code? != 0) convert-instruction(line, out)
     81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0/imm32           # compare ebx
-    74/jump-if-equal  $convert:data/disp8
-$convert:code:
+    74/jump-if-equal  $subx-pack:data/disp8
+$subx-pack:code:
     # . convert-instruction(line, out)
     # . . push args
     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
@@ -345,8 +345,8 @@ $convert:code:
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # . loop
-    e9/jump  $convert:loop/disp32
-$convert:data:
+    e9/jump  $subx-pack:loop/disp32
+$subx-pack:data:
     # else convert-data(line, out)
     # . . push args
     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
@@ -356,8 +356,8 @@ $convert:data:
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # . loop
-    e9/jump  $convert:loop/disp32
-$convert:pass-through:
+    e9/jump  $subx-pack:loop/disp32
+$subx-pack:pass-through:
     # write-stream-data(out, line)
     # . . push args
     51/push-ecx
@@ -367,8 +367,8 @@ $convert:pass-through:
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # . loop
-    e9/jump  $convert:loop/disp32
-$convert:break:
+    e9/jump  $subx-pack:loop/disp32
+$subx-pack:break:
     # flush(out)
     # . . push args
     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
@@ -376,7 +376,7 @@ $convert:break:
     e8/call  flush/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-$convert:end:
+$subx-pack:end:
     # . reclaim locals
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x214/imm32       # add to esp
     # . restore registers
@@ -389,7 +389,7 @@ $convert:end:
     5d/pop-to-ebp
     c3/return
 
-test-convert-passes-empty-lines-through:
+test-subx-pack-passes-empty-lines-through:
     # if a line is empty, pass it along unchanged
     # . prolog
     55/push-ebp
@@ -428,12 +428,12 @@ test-convert-passes-empty-lines-through:
     # . . discard args
     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)
+    # subx-pack(_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
+    e8/call  subx-pack/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # check that the line just passed through
@@ -446,7 +446,7 @@ test-convert-passes-empty-lines-through:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
     # . check-stream-equal(_test-output-stream, "", msg)
     # . . push args
-    68/push  "F - test-convert-passes-empty-lines-through"/imm32
+    68/push  "F - test-subx-pack-passes-empty-lines-through"/imm32
     68/push  ""/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -458,7 +458,7 @@ test-convert-passes-empty-lines-through:
     5d/pop-to-ebp
     c3/return
 
-test-convert-passes-lines-with-just-whitespace-through:
+test-subx-pack-passes-lines-with-just-whitespace-through:
     # if a line is empty, pass it along unchanged
     # . prolog
     55/push-ebp
@@ -505,12 +505,12 @@ test-convert-passes-lines-with-just-whitespace-through:
     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)
+    # subx-pack(_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
+    e8/call  subx-pack/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # check that the line just passed through
@@ -523,7 +523,7 @@ test-convert-passes-lines-with-just-whitespace-through:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "    ", msg)
     # . . push args
-    68/push  "F - test-convert-passes-with-just-whitespace-through"/imm32
+    68/push  "F - test-subx-pack-passes-with-just-whitespace-through"/imm32
     68/push  "    "/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -535,7 +535,7 @@ test-convert-passes-lines-with-just-whitespace-through:
     5d/pop-to-ebp
     c3/return
 
-test-convert-passes-segment-headers-through:
+test-subx-pack-passes-segment-headers-through:
     # if a line starts with '==', pass it along unchanged
     # . prolog
     55/push-ebp
@@ -582,12 +582,12 @@ test-convert-passes-segment-headers-through:
     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)
+    # subx-pack(_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
+    e8/call  subx-pack/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # check that the line just passed through
@@ -600,7 +600,7 @@ test-convert-passes-segment-headers-through:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
     # . check-stream-equal(_test-output-stream, "== abcd 0x1", msg)
     # . . push args
-    68/push  "F - test-convert-passes-segment-headers-through"/imm32
+    68/push  "F - test-subx-pack-passes-segment-headers-through"/imm32
     68/push  "== abcd 0x1"/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -612,7 +612,7 @@ test-convert-passes-segment-headers-through:
     5d/pop-to-ebp
     c3/return
 
-test-convert-in-data-segment:
+test-subx-pack-in-data-segment:
     # correctly process lines in the data segment
     # . prolog
     55/push-ebp
@@ -678,12 +678,12 @@ test-convert-in-data-segment:
     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)
+    # subx-pack(_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
+    e8/call  subx-pack/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # check output
@@ -722,7 +722,7 @@ test-convert-in-data-segment:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "== code 0x1", msg)
     # . . push args
-    68/push  "F - test-convert-in-data-segment/0"/imm32
+    68/push  "F - test-subx-pack-in-data-segment/0"/imm32
     68/push  "== code 0x1"/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -731,7 +731,7 @@ test-convert-in-data-segment:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "== data 0x2", msg)
     # . . push args
-    68/push  "F - test-convert-in-data-segment/1"/imm32
+    68/push  "F - test-subx-pack-in-data-segment/1"/imm32
     68/push  "== data 0x2"/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -740,7 +740,7 @@ test-convert-in-data-segment:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "03 04 00 00 00 ", msg)
     # . . push args
-    68/push  "F - test-convert-in-data-segment/2"/imm32
+    68/push  "F - test-subx-pack-in-data-segment/2"/imm32
     68/push  "03 04 00 00 00 "/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -752,7 +752,7 @@ test-convert-in-data-segment:
     5d/pop-to-ebp
     c3/return
 
-test-convert-code-and-data-segments:
+test-subx-pack-code-and-data-segments:
     # correctly process lines in both code and data segments
     # . prolog
     55/push-ebp
@@ -836,12 +836,12 @@ test-convert-code-and-data-segments:
     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)
+    # subx-pack(_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
+    e8/call  subx-pack/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # check output
@@ -885,7 +885,7 @@ test-convert-code-and-data-segments:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "== code 0x1", msg)
     # . . push args
-    68/push  "F - test-convert-code-and-data-segments/0"/imm32
+    68/push  "F - test-subx-pack-code-and-data-segments/0"/imm32
     68/push  "== code 0x1"/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -894,7 +894,7 @@ test-convert-code-and-data-segments:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "e8 20 00 00 00  # e8/call 20/disp32", msg)
     # . . push args
-    68/push  "F - test-convert-code-and-data-segments/1"/imm32
+    68/push  "F - test-subx-pack-code-and-data-segments/1"/imm32
     68/push  "e8 20 00 00 00  # e8/call 20/disp32"/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -903,7 +903,7 @@ test-convert-code-and-data-segments:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "68 20  # 68/push 0x20/imm8", msg)
     # . . push args
-    68/push  "F - test-convert-code-and-data-segments/2"/imm32
+    68/push  "F - test-subx-pack-code-and-data-segments/2"/imm32
     68/push  "68 20  # 68/push 0x20/imm8"/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -912,7 +912,7 @@ test-convert-code-and-data-segments:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "== data 0x2", msg)
     # . . push args
-    68/push  "F - test-convert-code-and-data-segments/3"/imm32
+    68/push  "F - test-subx-pack-code-and-data-segments/3"/imm32
     68/push  "== data 0x2"/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -921,7 +921,7 @@ test-convert-code-and-data-segments:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     # . check-next-stream-line-equal(_test-output-stream, "03 04 00 00 00 ", msg)
     # . . push args
-    68/push  "F - test-convert-code-and-data-segments/4"/imm32
+    68/push  "F - test-subx-pack-code-and-data-segments/4"/imm32
     68/push  "03 04 00 00 00 "/imm32
     68/push  _test-output-stream/imm32
     # . . call
diff --git a/apps/sigils b/apps/sigils
index fff9e7cd..c87a99f1 100755
--- a/apps/sigils
+++ b/apps/sigils
Binary files differdiff --git a/apps/sigils.subx b/apps/sigils.subx
index 8c8beba7..f05bc6d4 100644
--- a/apps/sigils.subx
+++ b/apps/sigils.subx
@@ -2,7 +2,7 @@
 # other related arguments.
 #
 # To run:
-#   $ ./subx translate init.linux 0*.subx apps/subx-common.subx apps/sigils.subx  -o apps/sigils
+#   $ ./subx translate init.linux 0*.subx apps/subx-params.subx apps/sigils.subx  -o apps/sigils
 #
 # We currently support the following notations:
 #
diff --git a/apps/subx-common.subx b/apps/subx-common.subx
deleted file mode 100644
index 829ed277..00000000
--- a/apps/subx-common.subx
+++ /dev/null
@@ -1,2084 +0,0 @@
-== code
-#   instruction                     effective address                                                   register    displacement    immediate
-# . 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
-
-# (re)compute the bounds of the next word in the line
-# return empty string on reaching end of file
-next-word:  # line : (address stream byte), out : (address slice)
-    # . prolog
-    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
-    56/push-esi
-    57/push-edi
-    # esi = line
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
-    # edi = out
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   0xc/disp8       .                 # copy *(ebp+12) to edi
-    # skip-chars-matching(line, ' ')
-    # . . push args
-    68/push  0x20/imm32/space
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
-    # . . call
-    e8/call  skip-chars-matching/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-$next-word:check0:
-    # if (line->read >= line->write) clear out and return
-    # . eax = line->read
-    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           0/r32/eax   4/disp8         .                 # copy *(esi+4) to eax
-    # . if (eax < line->write) goto next check
-    3b/compare                      0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # compare eax with *esi
-    7c/jump-if-lesser  $next-word:check-for-comment/disp8
-    # . return out = {0, 0}
-    c7          0/subop/copy        0/mod/direct    7/rm32/edi    .           .             .           .           .               0/imm32           # copy to *edi
-    c7          0/subop/copy        1/mod/*+disp8   7/rm32/edi    .           .             .           .           4/disp8         0/imm32           # copy to *(edi+4)
-    eb/jump  $next-word:end/disp8
-$next-word:check-for-comment:
-    # out->start = &line->data[line->read]
-    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(esi+4) to ecx
-    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
-    89/copy                         0/mod/indirect  7/rm32/edi    .           .             .           0/r32/eax   .               .                 # copy eax to *edi
-    # if (line->data[line->read] == '#') out->end = &line->data[line->write]), skip rest of stream and return
-    # . eax = line->data[line->read]
-    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
-    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
-    # . compare
-    3d/compare-eax-and  0x23/imm32/pound
-    75/jump-if-not-equal  $next-word:regular-word/disp8
-$next-word:comment:
-    # . out->end = &line->data[line->write]
-    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # copy *esi to eax
-    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
-    89/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           0/r32/eax   4/disp8         .                 # copy eax to *(edi+4)
-    # . line->read = line->write
-    89/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           0/r32/eax   4/disp8         .                 # copy eax to *(esi+4)
-    # . return
-    eb/jump  $next-word:end/disp8
-$next-word:regular-word:
-    # otherwise skip-chars-not-matching-whitespace(line)  # including trailing newline
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
-    # . . call
-    e8/call  skip-chars-not-matching-whitespace/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # out->end = &line->data[line->read]
-    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(esi+4) to ecx
-    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
-    89/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           0/r32/eax   4/disp8         .                 # copy eax to *(edi+4)
-$next-word:end:
-    # . restore registers
-    5f/pop-to-edi
-    5e/pop-to-esi
-    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
-    c3/return
-
-test-next-word:
-    # . 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
-    # var 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
-    # write(_test-stream, "  ab")
-    # . . push args
-    68/push  "  ab"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # next-word(_test-stream, slice)
-    # . . push args
-    51/push-ecx
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(slice->start - _test-stream->data, 2, msg)
-    # . check-ints-equal(slice->start - _test-stream, 14, msg)
-    # . . push args
-    68/push  "F - test-next-word: start"/imm32
-    68/push  0xe/imm32
-    # . . push slice->start - _test-stream
-    8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy *ecx to eax
-    81          5/subop/subtract    3/mod/direct    0/rm32/eax    .           .             .           .           .               _test-stream/imm32 # subtract from eax
-    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-ints-equal(slice->end - _test-stream->data, 4, msg)
-    # . check-ints-equal(slice->end - _test-stream, 16, msg)
-    # . . push args
-    68/push  "F - test-next-word: end"/imm32
-    68/push  0x10/imm32
-    # . . push slice->end - _test-stream
-    8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   4/disp8         .                 # copy *(ecx+4) to eax
-    81          5/subop/subtract    3/mod/direct    0/rm32/eax    .           .             .           .           .               _test-stream/imm32 # subtract from eax
-    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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-test-next-word-returns-whole-comment:
-    # . 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
-    # var 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
-    # write(_test-stream, "  # a")
-    # . . push args
-    68/push  "  # a"/imm32
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # next-word(_test-stream, slice)
-    # . . push args
-    51/push-ecx
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(slice->start - _test-stream->data, 2, msg)
-    # . check-ints-equal(slice->start - _test-stream, 14, msg)
-    # . . push args
-    68/push  "F - test-next-word-returns-whole-comment: start"/imm32
-    68/push  0xe/imm32
-    # . . push slice->start - _test-stream
-    8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy *ecx to eax
-    81          5/subop/subtract    3/mod/direct    0/rm32/eax    .           .             .           .           .               _test-stream/imm32 # subtract from eax
-    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-ints-equal(slice->end - _test-stream->data, 5, msg)
-    # . check-ints-equal(slice->end - _test-stream, 17, msg)
-    # . . push args
-    68/push  "F - test-next-word-returns-whole-comment: end"/imm32
-    68/push  0x11/imm32
-    # . . push slice->end - _test-stream
-    8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   4/disp8         .                 # copy *(ecx+4) to eax
-    81          5/subop/subtract    3/mod/direct    0/rm32/eax    .           .             .           .           .               _test-stream/imm32 # subtract from eax
-    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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-test-next-word-returns-empty-string-on-eof:
-    # . 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
-    # var 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
-    # write nothing to _test-stream
-    # next-word(_test-stream, slice)
-    # . . push args
-    51/push-ecx
-    68/push  _test-stream/imm32
-    # . . call
-    e8/call  next-word/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(slice->end - slice->start, 0, msg)
-    # . . push args
-    68/push  "F - test-next-word-returns-empty-string-on-eof"/imm32
-    68/push  0/imm32
-    # . . push slice->end - slice->start
-    8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   4/disp8         .                 # copy *(ecx+4) to eax
-    2b/subtract                     0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract *ecx from eax
-    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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-# write an entire stream's contents to a buffered-file
-# ways to do this:
-#   - construct a 'maximal slice' and pass it to write-slice-buffered
-#   - flush the buffered-file and pass the stream directly to its fd (disabling buffering)
-# we'll go with the first way for now
-write-stream-data:  # f : (address buffered-file), s : (address stream) -> <void>
-    # . prolog
-    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
-    56/push-esi
-    # esi = s
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   0xc/disp8       .                 # copy *(ebp+12) to esi
-    # var slice/ecx = {s->data, s->data + s->write}
-    # . push s->data + s->write
-    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # copy *esi to eax
-    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
-    50/push-eax
-    # . push s->data
-    8d/copy-address                 1/mod/*+disp8   6/rm32/esi    .           .             .           0/r32/eax   0xc/disp8       .                 # copy esi+12 to eax
-    50/push-eax
-    # . ecx = esp
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # write-slice-buffered(f, slice)
-    # . . push args
-    51/push-ecx
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
-    # . . call
-    e8/call  write-slice-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-$write-stream-data:end:
-    # . restore locals
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . restore registers
-    5e/pop-to-esi
-    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
-    c3/return
-
-test-write-stream-data:
-    # . prolog
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    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
-    # . clear-stream(_test-input-stream)
-    # . . push args
-    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
-    # 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
-    # write-stream-data(_test-output-buffered-file, _test-input-stream)
-    # . . push args
-    68/push  _test-input-stream/imm32
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  write-stream-data/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-output-buffered-file)
-    # . . push args
-    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-output-stream, "abcd", msg)
-    # . . push args
-    68/push  "F - test-write-stream-data"/imm32
-    68/push  "abcd"/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-stream-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
-
-has-metadata?:  # word : (address slice), s : (address string) -> eax : boolean
-    # pseudocode:
-    #   var twig : &slice = next-token-from-slice(word->start, word->end, '/')  # skip name
-    #   curr = twig->end
-    #   while true
-    #     twig = next-token-from-slice(curr, word->end, '/')
-    #     if (twig.empty()) break
-    #     if (slice-equal?(twig, s)) return true
-    #     curr = twig->end
-    #   return false
-    # . prolog
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # . save registers
-    51/push-ecx
-    52/push-edx
-    56/push-esi
-    57/push-edi
-    # esi = word
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
-    # edx = word->end
-    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           2/r32/edx   4/disp8         .                 # copy *(esi+4) to edx
-    # var twig/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
-    # next-token-from-slice(word->start, word->end, '/', twig)
-    # . . push args
-    57/push-edi
-    68/push  0x2f/imm32/slash
-    52/push-edx
-    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
-    # curr/ecx = twig->end
-    8b/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(edi+4) to ecx
-$has-metadata?:loop:
-    # next-token-from-slice(curr, word->end, '/', twig)
-    # . . push args
-    57/push-edi
-    68/push  0x2f/imm32/slash
-    52/push-edx
-    51/push-ecx
-    # . . 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 (slice-empty?(twig)) return false
-    # . eax = slice-empty?(twig)
-    # . . push args
-    57/push-edi
-    # . . call
-    e8/call  slice-empty?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . if (eax != 0) return false
-    3d/compare-eax-and  0/imm32
-    75/jump-if-not-equal  $has-metadata?:false/disp8
-    # if (slice-equal?(twig, s)) return true
-    # . eax = slice-equal?(twig, s)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
-    57/push-edi
-    # . . 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) return true
-    3d/compare-eax-and  0/imm32
-    75/jump-if-not-equal  $has-metadata?:true/disp8
-    # curr = twig->end
-    8b/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(edi+4) to ecx
-    eb/jump  $has-metadata?:loop/disp8
-$has-metadata?:true:
-    b8/copy-to-eax  1/imm32/true
-    eb/jump  $has-metadata?:end/disp8
-$has-metadata?:false:
-    b8/copy-to-eax  0/imm32/false
-$has-metadata?: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
-    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-has-metadata-true:
-    # . prolog
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "ab/imm32"
-    b8/copy-to-eax  "ab/imm32"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
-    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
-    05/add-to-eax  4/imm32
-    # var in/esi : (address slice) = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    6/rm32/esi    .           .             .           4/r32/esp   .               .                 # copy esp to esi
-    # eax = has-metadata?(esi, "imm32")
-    # . . push args
-    68/push  "imm32"/imm32
-    56/push-esi
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-has-metadata-true"/imm32
-    68/push  1/imm32/true
-    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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-test-has-metadata-false:
-    # . prolog
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "ab/c"
-    b8/copy-to-eax  "ab/c"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
-    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
-    05/add-to-eax  4/imm32
-    # var in/esi : (address slice) = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    6/rm32/esi    .           .             .           4/r32/esp   .               .                 # copy esp to esi
-    # eax = has-metadata?(esi, "d")
-    # . . push args
-    68/push  "d"/imm32
-    56/push-esi
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-has-metadata-false"/imm32
-    68/push  0/imm32/false
-    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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-test-has-metadata-ignore-name:
-    # . prolog
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "a/b"
-    b8/copy-to-eax  "a/b"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
-    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
-    05/add-to-eax  4/imm32
-    # var in/esi : (address slice) = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    6/rm32/esi    .           .             .           4/r32/esp   .               .                 # copy esp to esi
-    # eax = has-metadata?(esi, "a")
-    # . . push args
-    68/push  "a"/imm32
-    56/push-esi
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-has-metadata-ignore-name"/imm32
-    68/push  0/imm32/false
-    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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-test-has-metadata-multiple-true:
-    # . prolog
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "a/b/c"
-    b8/copy-to-eax  "a/b/c"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
-    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
-    05/add-to-eax  4/imm32
-    # var in/esi : (address slice) = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    6/rm32/esi    .           .             .           4/r32/esp   .               .                 # copy esp to esi
-    # eax = has-metadata?(esi, "c")
-    # . . push args
-    68/push  "c"/imm32
-    56/push-esi
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-has-metadata-multiple-true"/imm32
-    68/push  1/imm32/true
-    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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-test-has-metadata-multiple-false:
-    # . prolog
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "a/b/c"
-    b8/copy-to-eax  "a/b/c"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
-    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
-    05/add-to-eax  4/imm32
-    # var in/esi : (address slice) = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    6/rm32/esi    .           .             .           4/r32/esp   .               .                 # copy esp to esi
-    # eax = has-metadata?(esi, "d")
-    # . . push args
-    68/push  "d"/imm32
-    56/push-esi
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-has-metadata-multiple-false"/imm32
-    68/push  0/imm32/false
-    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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-# If datum of 'word' is not a valid name, it must be a hex int. Parse and print
-# it in 'width' bytes of hex, least significant first.
-# Otherwise just print the entire word including metadata.
-# Always print a trailing space.
-emit:  # out : (address buffered-file), word : (address slice), width : int -> <void>
-    # . 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
-    # datum = 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-valid-name?(datum)) write-slice-buffered(out, word) and return
-    # . eax = is-valid-name?(name)
-    # . . push args
-    57/push-edi
-    # . . call
-    e8/call  is-valid-name?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # . if (eax != 0)
-    3d/compare-eax-and  0/imm32
-    74/jump-if-equal  $emit:hex-int/disp8
-$emit:name:
-    # . write-slice-buffered(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-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . write-buffered(out, " ")
-    # . . push args
-    68/push  Space/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
-    # . . call
-    e8/call  write-buffered/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(datum), width)
-    #   (Weird shit can happen here if the datum of 'word' isn't either a valid
-    #   name or a hex number, but we're only going to be passing in real legal
-    #   programs. We just want to make sure that valid names aren't treated as
-    #   (valid) hex numbers.)
-$emit:hex-int:
-    # . value/eax = parse-hex-int(datum)
-    # . . 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, value, 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-output-stream)
-    # . . push args
-    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
-    # (eax..ecx) = "30"
-    b8/copy-to-eax  "30"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
-    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
-    05/add-to-eax  4/imm32
-    # var slice/ecx = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # emit(_test-output-buffered-file, slice, 1)
-    # . . push args
-    68/push  1/imm32
-    51/push-ecx
-    68/push  _test-output-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-output-buffered-file)
-    # . . push args
-    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-output-stream, "30 ", msg)
-    # . . push args
-    68/push  "F - test-emit-number/1"/imm32
-    68/push  "30 "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-stream-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-negative-number:
-    # test support for sign-extending negative numbers
-    # . prolog
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    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
-    # (eax..ecx) = "-2"
-    b8/copy-to-eax  "-2"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
-    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
-    05/add-to-eax  4/imm32
-    # var slice/ecx = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # emit(_test-output-buffered-file, slice, 2)
-    # . . push args
-    68/push  2/imm32
-    51/push-ecx
-    68/push  _test-output-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-output-buffered-file)
-    # . . push args
-    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-output-stream, "fe ff ", msg)
-    # . . push args
-    68/push  "F - test-emit-number/1"/imm32
-    68/push  "fe ff "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-stream-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-output-stream)
-    # . . push args
-    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
-    # (eax..ecx) = "-2/foo"
-    b8/copy-to-eax  "-2/foo"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
-    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
-    05/add-to-eax  4/imm32
-    # var slice/ecx = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # emit(_test-output-buffered-file, slice, 2)
-    # . . push args
-    68/push  2/imm32
-    51/push-ecx
-    68/push  _test-output-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-output-buffered-file)
-    # . . push args
-    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
-    # the '/foo' will have no impact on the output
-    # check-stream-equal(_test-output-stream, "fe ff ", msg)
-    # . . push args
-    68/push  "F - test-emit-number-with-metadata"/imm32
-    68/push  "fe ff "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-stream-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-output-stream)
-    # . . push args
-    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
-    # (eax..ecx) = "xyz"
-    b8/copy-to-eax  "xyz"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
-    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
-    05/add-to-eax  4/imm32
-    # var slice/ecx = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # emit(_test-output-buffered-file, slice, 2)
-    # . . push args
-    68/push  2/imm32
-    51/push-ecx
-    68/push  _test-output-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-output-buffered-file)
-    # . . push args
-    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-output-stream, "xyz", msg)
-    # . . push args
-    68/push  "F - test-emit-non-number"/imm32
-    68/push  "xyz "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-stream-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-output-stream)
-    # . . push args
-    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
-    # (eax..ecx) = "xyz/"
-    b8/copy-to-eax  "xyz/"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
-    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
-    05/add-to-eax  4/imm32
-    # var slice/ecx = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # emit(_test-output-buffered-file, slice, 2)
-    # . . push args
-    68/push  2/imm32
-    51/push-ecx
-    68/push  _test-output-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-output-buffered-file)
-    # . . push args
-    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-output-stream, "xyz/", msg)
-    # . . push args
-    68/push  "F - test-emit-non-number-with-metadata"/imm32
-    68/push  "xyz/ "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-stream-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-all-hex-digits-and-metadata:
-    # . prolog
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    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
-    # (eax..ecx) = "abcd/xyz"
-    b8/copy-to-eax  "abcd/xyz"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
-    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
-    05/add-to-eax  4/imm32
-    # var slice/ecx = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # emit(_test-output-buffered-file, slice, 2)
-    # . . push args
-    68/push  2/imm32
-    51/push-ecx
-    68/push  _test-output-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-output-buffered-file)
-    # . . push args
-    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
-#?     # dump output {{{
-#?     # . write(2/stderr, "^")
-#?     # . . push args
-#?     68/push  "^"/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
-#?     # . write-stream(2/stderr, _test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/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
-#?     # }}}
-    # check-stream-equal(_test-output-stream, "abcd/xyz")
-    # . . push args
-    68/push  "F - test-emit-non-number-with-all-hex-digits"/imm32
-    68/push  "abcd/xyz "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-stream-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
-
-# conditions for 'valid' names that are not at risk of looking like hex numbers
-# keep in sync with the rules in labels.cc
-#: - if it starts with a digit, it's treated as a number. If it can't be
-#:   parsed as hex it will raise an error.
-#: - if it starts with '-' it's treated as a number.
-#: - if it starts with '0x' it's treated as a number. (redundant)
-#: - if it's two characters long, it can't be a name. Either it's a hex
-#:   byte, or it raises an error.
-is-valid-name?:  # in : (address slice) -> eax : boolean
-    # . prolog
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # . save registers
-    51/push-ecx
-    56/push-esi
-    # esi = in
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
-    # start/ecx = in->start
-    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # copy *esi to ecx
-    # end/eax = in->end
-    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           0/r32/eax   4/disp8         .                 # copy *(esi+4) to eax
-$is-valid-name?:check0:
-    # if (start >= end) return false
-    39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # compare ecx with eax
-    73/jump-if-greater-or-equal-unsigned  $is-valid-name?:false/disp8
-$is-valid-name?:check1:
-    # eax -= ecx
-    29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
-    # if (eax == 2) return false
-    3d/compare-eax-and  2/imm32
-    74/jump-if-equal  $is-valid-name?:false/disp8
-$is-valid-name?:check2:
-    # c/eax = *ecx
-    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
-    8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/AL    .               .                 # copy byte at *ecx to AL
-    # if (c == "-") return false
-    3d/compare-eax-and  2d/imm32/-
-    74/jump-if-equal  $is-valid-name?:false/disp8
-$is-valid-name?:check3a:
-    # if (c < "0") return true
-    3d/compare-eax-with  30/imm32/0
-    7c/jump-if-lesser  $is-valid-name?:true/disp8
-$is-valid-name?:check3b:
-    # if (c > "9") return true
-    3d/compare-eax-with  39/imm32/9
-    7f/jump-if-greater  $is-valid-name?:true/disp8
-$is-valid-name?:false:
-    # return false
-    b8/copy-to-eax  0/imm32/false
-    eb/jump  $is-valid-name?:end/disp8
-$is-valid-name?:true:
-    # return true
-    b8/copy-to-eax  1/imm32/true
-$is-valid-name?:end:
-    # . restore registers
-    5e/pop-to-esi
-    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-is-valid-name-digit-prefix:
-    # . prolog
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "34"
-    b8/copy-to-eax  "34"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
-    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
-    05/add-to-eax  4/imm32
-    # var slice/ecx = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = is-valid-name?(slice)
-    # . . push args
-    51/push-ecx
-    # . . call
-    e8/call  is-valid-name?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-is-valid-name-digit-prefix"/imm32
-    68/push  0/imm32/false
-    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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-test-is-valid-name-negative-prefix:
-    # . prolog
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "-0x34"
-    b8/copy-to-eax  "-0x34"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
-    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
-    05/add-to-eax  4/imm32
-    # var slice/ecx = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = is-valid-name?(slice)
-    # . . push args
-    51/push-ecx
-    # . . call
-    e8/call  is-valid-name?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-is-valid-name-negative-prefix"/imm32
-    68/push  0/imm32/false
-    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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-test-is-valid-name-0x-prefix:
-    # . prolog
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "0x34"
-    b8/copy-to-eax  "0x34"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
-    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
-    05/add-to-eax  4/imm32
-    # var slice/ecx = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = is-valid-name?(slice)
-    # . . push args
-    51/push-ecx
-    # . . call
-    e8/call  is-valid-name?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-is-valid-name-0x-prefix"/imm32
-    68/push  0/imm32/false
-    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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-test-is-valid-name-starts-with-pre-digit:
-    # . prolog
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "/03"
-    b8/copy-to-eax  "/03"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
-    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
-    05/add-to-eax  4/imm32
-    # var slice/ecx = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = is-valid-name?(slice)
-    # . . push args
-    51/push-ecx
-    # . . call
-    e8/call  is-valid-name?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-is-valid-name-starts-with-pre-digit"/imm32
-    68/push  1/imm32/true
-    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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-test-is-valid-name-starts-with-post-digit:
-    # . prolog
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "q34"
-    b8/copy-to-eax  "q34"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
-    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
-    05/add-to-eax  4/imm32
-    # var slice/ecx = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = is-valid-name?(slice)
-    # . . push args
-    51/push-ecx
-    # . . call
-    e8/call  is-valid-name?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-is-valid-name-starts-with-post-digit"/imm32
-    68/push  1/imm32/true
-    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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-test-is-valid-name-starts-with-digit:
-    # . prolog
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # (eax..ecx) = "0x34"
-    b8/copy-to-eax  "0x34"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
-    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
-    05/add-to-eax  4/imm32
-    # var slice/ecx = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # eax = is-valid-name?(slice)
-    # . . push args
-    51/push-ecx
-    # . . call
-    e8/call  is-valid-name?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-is-valid-name-starts-with-digit"/imm32
-    68/push  0/imm32/false
-    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
-    # . 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 -> <void>
-    # . prolog
-    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
-    53/push-ebx
-    57/push-edi
-    # edi = out
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   8/disp8         .                 # copy *(ebp+8) to edi
-    # ebx = n
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           3/r32/ebx   0xc/disp8       .                 # copy *(ebp+12) to ebx
-    # edx = width
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           2/r32/edx   0x10/disp8      .                 # copy *(ebp+16) to edx
-    # var curr/ecx = 0
-    31/xor                          3/mod/direct    1/rm32/ecx    .           .             .           1/r32/ecx   .               .                 # clear ecx
-$emit-hex:loop:
-    # if (curr >= width) break
-    39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
-    7d/jump-if-greater-or-equal  $emit-hex:end/disp8
-    # print-byte-buffered(out, ebx)
-    # . . push args
-    53/push-ebx
-    57/push-edi
-    # . . call
-    e8/call  print-byte-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # write-byte-buffered(out, ' ')
-    # . . push args
-    68/push  0x20/imm32/space
-    57/push-edi
-    # . . call
-    e8/call  write-byte-buffered/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # ebx = ebx >> 8
-    c1/shift    5/subop/logic-right 3/mod/direct    3/rm32/ebx    .           .             .           .           .               8/imm8            # shift ebx right by 8 bits, while padding zeroes
-$emit-hex:continue:
-    # ++curr
-    41/increment-ecx
-    eb/jump  $emit-hex:loop/disp8
-$emit-hex:end:
-    # . restore registers
-    5f/pop-to-edi
-    5b/pop-to-ebx
-    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
-    c3/return
-
-test-emit-hex-single-byte:
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    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
-    # emit-hex(_test-output-buffered-file, 0xab, 1)
-    # . . push args
-    68/push  1/imm32
-    68/push  0xab/imm32
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  emit-hex/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # flush(_test-output-buffered-file)
-    # . . push args
-    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-ints-equal(*_test-output-stream->data, 'ab ', msg)
-    # . . push args
-    68/push  "F - test-emit-hex-single-byte"/imm32
-    68/push  0x206261/imm32
-    # . . push *_test-output-stream->data
-    b8/copy-to-eax  _test-output-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
-    # . end
-    c3/return
-
-test-emit-hex-multiple-byte:
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    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
-    # emit-hex(_test-output-buffered-file, 0x1234, 2)
-    # . . push args
-    68/push  2/imm32
-    68/push  0x1234/imm32
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  emit-hex/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # flush(_test-output-buffered-file)
-    # . . push args
-    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-output-stream, "34 12 ", msg)
-    # . . push args
-    68/push  "F - test-emit-hex-multiple-byte/1"/imm32
-    68/push  "34 12 "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-stream-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . end
-    c3/return
-
-test-emit-hex-zero-pad:
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    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
-    # emit-hex(_test-output-buffered-file, 0xab, 2)
-    # . . push args
-    68/push  2/imm32
-    68/push  0xab/imm32
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  emit-hex/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # flush(_test-output-buffered-file)
-    # . . push args
-    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(_test-output-stream->data == 'ab 00 ')
-    # . . push args
-    68/push  "F - test-emit-hex-zero-pad/1"/imm32
-    68/push  "ab 00 "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-stream-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . end
-    c3/return
-
-test-emit-hex-negative:
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    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
-    # emit-hex(_test-output-buffered-file, -1, 2)
-    # . . push args
-    68/push  2/imm32
-    68/push  -1/imm32
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  emit-hex/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # flush(_test-output-buffered-file)
-    # . . push args
-    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-output-stream == "ff ff ")
-    # . . push args
-    68/push  "F - test-emit-hex-negative/1"/imm32
-    68/push  "ff ff "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-stream-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # . end
-    c3/return
-
-# print 'arr' in hex with a space after every byte
-emit-hex-array:  # out : (address buffered-file), arr : (address array byte) -> <void>
-    # . prolog
-    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
-    57/push-edi
-    # edi = out
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   8/disp8         .                 # copy *(ebp+8) to edi
-    # edx = arr  # <== 0xbdffffe4
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           2/r32/edx   0xc/disp8       .                 # copy *(ebp+12) to edx
-    # curr/ecx = arr->data
-    8d/copy-address                 1/mod/*+disp8   2/rm32/edx    .           .             .           1/r32/ecx   4/disp8         .                 # copy edx+4 to ecx
-    # max/edx = arr->data + arr->length
-    8b/copy                         0/mod/indirect  2/rm32/edx    .           .             .           2/r32/edx   .               .                 # copy *edx to edx
-    01/add                          3/mod/direct    2/rm32/edx    .           .             .           1/r32/ecx   .               .                 # add ecx to edx
-    # eax = 0
-    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
-$emit-hex-array:loop:
-    # if (curr >= width) break
-    39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
-    73/jump-if-greater-or-equal-unsigned  $emit-hex-array:end/disp8
-    # emit-hex(out, *curr, width=1)
-    # . . push args
-    68/push  1/imm32/width
-    8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/AL    .               .                 # copy byte at *ecx to AL
-    50/push-eax
-    57/push-edi
-    # . . call
-    e8/call  emit-hex/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-    # ++curr
-    41/increment-ecx
-    eb/jump  $emit-hex-array:loop/disp8
-$emit-hex-array:end:
-    # . restore registers
-    5f/pop-to-edi
-    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
-    c3/return
-
-test-emit-hex-array:
-    # . prolog
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # setup
-    # . clear-stream(_test-output-stream)
-    # . . push args
-    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
-    # var arr/ecx (address array byte) = [01, 02, 03]
-    68/push  0x00030201/imm32  # bytes 01 02 03
-    68/push  3/imm32/length
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # emit-hex-array(_test-output-buffered-file, arr)
-    # . . push args
-    51/push-ecx
-    68/push  _test-output-buffered-file/imm32
-    # . . call
-    e8/call  emit-hex-array/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . flush(_test-output-buffered-file)
-    # . . push args
-    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
-#?     # dump output {{{
-#?     # . write(2/stderr, "result: ^")
-#?     # . . push args
-#?     68/push  "result: ^"/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
-#?     # . write-stream(2/stderr, _test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     68/push  2/imm32/stderr
-#?     # . . call
-#?     e8/call  write-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-#?     # . write(2/stderr, "$\n")
-#?     # . . push args
-#?     68/push  "$\n"/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
-#?     # . rewind-stream(_test-output-stream)
-#?     # . . push args
-#?     68/push  _test-output-stream/imm32
-#?     # . . call
-#?     e8/call  rewind-stream/disp32
-#?     # . . discard args
-#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-#?     # }}}
-    # check-next-stream-line-equal(_test-output-stream, "01 02 03 ", msg)
-    # . . push args
-    68/push  "F - test-emit-hex-array"/imm32
-    68/push  "01 02 03 "/imm32
-    68/push  _test-output-stream/imm32
-    # . . call
-    e8/call  check-next-stream-line-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
-
-compute-width: # word : (address array byte) -> eax : int
-    # . prolog
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # . save registers
-    51/push-ecx
-    # eax = word
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   8/disp8         .                 # copy *(ebp+8) to ecx
-    # ecx = word + word->length
-    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
-    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
-    # eax = word->data
-    05/add-to-eax  4/imm32
-    # var in/ecx : (address slice) = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # return compute-width-of-slice(ecx)
-    # . . push args
-    51/push-ecx
-    # . . call
-    e8/call  compute-width-of-slice/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-$compute-width:end:
-    # . reclaim locals
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/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
-
-compute-width-of-slice: # s : (address slice) -> eax : int
-    # . prolog
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # . save registers
-    51/push-ecx
-    # ecx = s
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
-    # if (has-metadata?(word, "imm32")) return 4
-    # . eax = has-metadata?(word, "imm32")
-    # . . push args
-    68/push  "imm32"/imm32
-    51/push-ecx
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . if (eax != 0) return 4
-    3d/compare-eax-and  0/imm32
-    b8/copy-to-eax  4/imm32         # ZF is set, so we can overwrite eax now
-    75/jump-if-not-equal  $compute-width-of-slice:end/disp8
-    # if (has-metadata?(word, "disp32")) return 4
-    # . eax = has-metadata?(word, "disp32")
-    # . . push args
-    68/push  "disp32"/imm32
-    51/push-ecx
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . if (eax != 0) return 4
-    3d/compare-eax-and  0/imm32
-    b8/copy-to-eax  4/imm32         # ZF is set, so we can overwrite eax now
-    75/jump-if-not-equal  $compute-width-of-slice:end/disp8
-    # if (has-metadata?(word, "imm16")) return 2
-    # . eax = has-metadata?(word, "imm16")
-    # . . push args
-    68/push  "imm16"/imm32
-    51/push-ecx
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . if (eax != 0) return 2
-    3d/compare-eax-and  0/imm32
-    b8/copy-to-eax  2/imm32         # ZF is set, so we can overwrite eax now
-    75/jump-if-not-equal  $compute-width-of-slice:end/disp8
-    # if (has-metadata?(word, "disp16")) return 2
-    # . eax = has-metadata?(word, "disp16")
-    # . . push args
-    68/push  "disp16"/imm32
-    51/push-ecx
-    # . . call
-    e8/call  has-metadata?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . if (eax != 0) return 2
-    3d/compare-eax-and  0/imm32
-    b8/copy-to-eax  2/imm32         # ZF is set, so we can overwrite eax now
-    75/jump-if-not-equal  $compute-width-of-slice:end/disp8
-    # otherwise return 1
-    b8/copy-to-eax  1/imm32
-$compute-width-of-slice: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-compute-width:
-    # . prolog
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-$test-compute-width:imm8:
-    # eax = compute-width("0x2/imm8")
-    # . . push args
-    68/push  "0x2/imm8"/imm32
-    # . . call
-    e8/call  compute-width/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-compute-width: 0x2/imm8"/imm32
-    50/push-eax
-    68/push  1/imm32
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-$test-compute-width:imm16:
-    # eax = compute-width("4/imm16")
-    # . . push args
-    68/push  "4/imm16"/imm32
-    # . . call
-    e8/call  compute-width/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 2, msg)
-    # . . push args
-    68/push  "F - test-compute-width: 4/imm16"/imm32
-    50/push-eax
-    68/push  2/imm32
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-$test-compute-width:imm32:
-    # eax = compute-width("4/imm32")
-    # . . push args
-    68/push  "4/imm32"/imm32
-    # . . call
-    e8/call  compute-width/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 4, msg)
-    # . . push args
-    68/push  "F - test-compute-width: 4/imm32"/imm32
-    50/push-eax
-    68/push  4/imm32
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-$test-compute-width:disp8:
-    # eax = compute-width("foo/disp8")
-    # . . push args
-    68/push  "foo/disp8"/imm32
-    # . . call
-    e8/call  compute-width/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-compute-width: foo/disp8"/imm32
-    50/push-eax
-    68/push  1/imm32
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-$test-compute-width:disp16:
-    # eax = compute-width("foo/disp16")
-    # . . push args
-    68/push  "foo/disp16"/imm32
-    # . . call
-    e8/call  compute-width/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 2, msg)
-    # . . push args
-    68/push  "F - test-compute-width: foo/disp16"/imm32
-    50/push-eax
-    68/push  2/imm32
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-$test-compute-width:disp32:
-    # eax = compute-width("foo/disp32")
-    # . . push args
-    68/push  "foo/disp32"/imm32
-    # . . call
-    e8/call  compute-width/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 4, msg)
-    # . . push args
-    68/push  "F - test-compute-width: foo/disp32"/imm32
-    50/push-eax
-    68/push  4/imm32
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
-$test-compute-width:no-metadata:
-    # eax = compute-width("45")
-    # . . push args
-    68/push  "45"/imm32
-    # . . call
-    e8/call  compute-width/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-compute-width: 45 (no metadata)"/imm32
-    50/push-eax
-    68/push  1/imm32
-    # . . 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
-
-is-label?: # word : (address slice) -> eax : boolean
-    # . prolog
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-    # . save registers
-    51/push-ecx
-    # ecx = word
-    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
-    # ecx = word->end
-    8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(ecx+4) to ecx
-    # return *(word->end - 1) == ':'
-    # . eax = 0
-    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
-    # . eax = *((char *) word->end - 1)
-    8a/copy-byte                    1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/AL    -1/disp8         .                 # copy byte at *(ecx-1) to AL
-    # . return (eax == ':')
-    3d/compare-eax-and  0x3a/imm32/colon
-    b8/copy-to-eax  1/imm32/true
-    74/jump-if-equal  $is-label?:end/disp8
-    b8/copy-to-eax  0/imm32/false
-$is-label?: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-is-label?:
-    # . prolog
-    55/push-ebp
-    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
-$test-is-label?:true:
-    # (eax..ecx) = "AAA:"
-    b8/copy-to-eax  "AAA:"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
-    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
-    05/add-to-eax  4/imm32
-    # var slice/ecx = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # is-label?(slice/ecx)
-    # . . push args
-    51/push-ecx
-    # . . call
-    e8/call  is-label?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 1, msg)
-    # . . push args
-    68/push  "F - test-is-label?:true"/imm32
-    68/push  1/imm32
-    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
-$test-is-label?:false:
-    # (eax..ecx) = "AAA"
-    b8/copy-to-eax  "AAA"/imm32
-    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
-    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
-    05/add-to-eax  4/imm32
-    # var slice/ecx = {eax, ecx}
-    51/push-ecx
-    50/push-eax
-    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
-    # is-label?(slice/ecx)
-    # . . push args
-    51/push-ecx
-    # . . call
-    e8/call  is-label?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 0, msg)
-    # . . push args
-    68/push  "F - test-is-label?:false"/imm32
-    68/push  0/imm32
-    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
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
-    5d/pop-to-ebp
-    c3/return
-
-== data
-
-_test-data-segment:
-  64/d 61/a 74/t 61/a
-_test-data-segment-end:
-
-# . . vim:nowrap:textwidth=0
diff --git a/071---subx-params.subx b/apps/subx-params.subx
index aefb6639..38a34314 100644
--- a/071---subx-params.subx
+++ b/apps/subx-params.subx
@@ -1,5 +1,4 @@
-# Normally we introduce names in the layers that need them, but we'll make an
-# exception to colocate various knobs for translating SubX programs using SubX.
+# Various knobs for translating SubX programs using SubX.
 
 == data
 
diff --git a/apps/survey b/apps/survey
index cc94c6ca..cc9da808 100755
--- a/apps/survey
+++ b/apps/survey
Binary files differdiff --git a/apps/survey.subx b/apps/survey.subx
index 678ed116..b943fe72 100644
--- a/apps/survey.subx
+++ b/apps/survey.subx
@@ -5,7 +5,7 @@
 #   b) add segment headers with addresses and offsets correctly filled in
 #
 # To build:
-#   $ ./subx translate init.linux 0*.subx apps/subx-common.subx apps/survey.subx  -o apps/survey
+#   $ ./subx translate init.linux 0*.subx apps/subx-params.subx apps/survey.subx  -o apps/survey
 #
 # The expected input is a stream of bytes with segment headers, comments and
 # some interspersed labels.
@@ -62,10 +62,10 @@ Entry:  # run tests if necessary, convert stdin if not
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 
     # - if argc > 1 and argv[1] == "test", then return run_tests()
-    # if (argc <= 1) goto run-main
+    # if (argc <= 1) goto interactive
     81          7/subop/compare     1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0/disp8         1/imm32           # compare *ebp
-    7e/jump-if-lesser-or-equal  $run-main/disp8
-    # if (!kernel-string-equal?(argv[1], "test")) goto run-main
+    7e/jump-if-lesser-or-equal  $subx-survey-main:interactive/disp8
+    # if (!kernel-string-equal?(argv[1], "test")) goto interactive
     # . eax = kernel-string-equal?(argv[1], "test")
     # . . push args
     68/push  "test"/imm32
@@ -74,22 +74,22 @@ Entry:  # run tests if necessary, convert stdin if not
     e8/call  kernel-string-equal?/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-    # . if (eax == 0) goto run-main
+    # . if (eax == 0) goto interactive
     3d/compare-eax-and  0/imm32
-    74/jump-if-equal  $run-main/disp8
+    74/jump-if-equal  $subx-survey-main:interactive/disp8
     # run-tests()
     e8/call  run-tests/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
-    eb/jump  $main:end/disp8
-$run-main:
+    eb/jump  $subx-survey-main:end/disp8
+$subx-survey-main:interactive:
     # - otherwise convert stdin
-    # convert(Stdin, Stdout)
+    # subx-survey(Stdin, Stdout)
     # . . push args
     68/push  Stdout/imm32
     68/push  Stdin/imm32
     # . . call
-    e8/call  convert/disp32
+    e8/call  subx-survey/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 #?     # . write-stream(2/stderr, Trace-stream)
@@ -102,7 +102,7 @@ $run-main:
 #?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # syscall(exit, 0)
     bb/copy-to-ebx  0/imm32
-$main:end:
+$subx-survey-main:end:
     b8/copy-to-eax  1/imm32/exit
     cd/syscall  0x80/imm8
 
@@ -113,7 +113,7 @@ $main:end:
 #   labels: (address stream {string, label-info})         (16 bytes per row)
 # these are all inefficient; use sequential scans for lookups
 
-convert:  # infile : (address buffered-file), out : (address buffered-file) -> <void>
+subx-survey:  # infile : (address buffered-file), out : (address buffered-file) -> <void>
     # pseudocode
     #   var in : (address stream byte) = stream(4096)
     #   slurp(infile, in)
@@ -440,7 +440,7 @@ convert:  # infile : (address buffered-file), out : (address buffered-file) -> <
     e8/call  flush/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-$convert:end:
+$subx-survey:end:
     # . reclaim locals
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x30a0/imm32      # add to esp
     # . restore registers
@@ -452,7 +452,7 @@ $convert:end:
     5d/pop-to-ebp
     c3/return
 
-test-convert-computes-addresses:
+test-subx-survey-computes-addresses:
     # input:
     #   == code 0x1
     #   Entry:
@@ -552,12 +552,12 @@ test-convert-computes-addresses:
     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)
+    # subx-survey(_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
+    e8/call  subx-survey/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # check trace
@@ -589,7 +589,7 @@ test-convert-computes-addresses:
 #?     # }}}
     # . check-trace-contains("label 'x' is at address 0x00001079.", msg)
     # . . push args
-    68/push  "F - test-convert-computes-addresses/0"/imm32
+    68/push  "F - test-subx-survey-computes-addresses/0"/imm32
     68/push  "label 'x' is at address 0x00001079."/imm32
     # . . call
     e8/call  check-trace-contains/disp32
@@ -597,7 +597,7 @@ test-convert-computes-addresses:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # . check-trace-contains("segment 'code' starts at address 0x00000074.", msg)
     # . . push args
-    68/push  "F - test-convert-computes-addresses/1"/imm32
+    68/push  "F - test-subx-survey-computes-addresses/1"/imm32
     68/push  "segment 'code' starts at address 0x00000074."/imm32
     # . . call
     e8/call  check-trace-contains/disp32
@@ -605,7 +605,7 @@ test-convert-computes-addresses:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # . check-trace-contains("segment 'code' has size 0x00000005.", msg)
     # . . push args
-    68/push  "F - test-convert-computes-addresses/2"/imm32
+    68/push  "F - test-subx-survey-computes-addresses/2"/imm32
     68/push  "segment 'code' has size 0x00000005."/imm32
     # . . call
     e8/call  check-trace-contains/disp32
@@ -613,7 +613,7 @@ test-convert-computes-addresses:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # . check-trace-contains("segment 'data' starts at address 0x00001079.", msg)
     # . . push args
-    68/push  "F - test-convert-computes-addresses/3"/imm32
+    68/push  "F - test-subx-survey-computes-addresses/3"/imm32
     68/push  "segment 'data' starts at address 0x00001079."/imm32
     # . . call
     e8/call  check-trace-contains/disp32
diff --git a/apps/tests b/apps/tests
index cb6f4a7f..30ef5fcd 100755
--- a/apps/tests
+++ b/apps/tests
Binary files differdiff --git a/apps/tests.subx b/apps/tests.subx
index cb6bd034..290483f0 100644
--- a/apps/tests.subx
+++ b/apps/tests.subx
@@ -2,7 +2,7 @@
 # all functions starting with 'test-'.
 #
 # To build:
-#   $ ./subx translate init.linux 0*.subx apps/subx-common.subx apps/tests.subx  -o apps/tests
+#   $ ./subx translate init.linux 0*.subx apps/subx-params.subx apps/tests.subx  -o apps/tests
 
 == code
 #   instruction                     effective address                                                   register    displacement    immediate
@@ -33,7 +33,7 @@ Entry:  # run tests if necessary, convert stdin if not
     # - if argc > 1 and argv[1] == "test", then return run_tests()
     # if (argc <= 1) goto run-main
     81          7/subop/compare     1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0/disp8         1/imm32           # compare *ebp
-    7e/jump-if-lesser-or-equal  $run-main/disp8
+    7e/jump-if-lesser-or-equal  $subx-tests-main:interactive/disp8
     # if (!kernel-string-equal?(argv[1], "test")) goto run-main
     # . eax = kernel-string-equal?(argv[1], "test")
     # . . push args
@@ -45,29 +45,29 @@ Entry:  # run tests if necessary, convert stdin if not
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # . if (eax == 0) goto run-main
     3d/compare-eax-and  0/imm32
-    74/jump-if-equal  $run-main/disp8
+    74/jump-if-equal  $subx-tests-main:interactive/disp8
     # run-tests()
     e8/call  run-tests/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
-    eb/jump  $main:end/disp8
-$run-main:
+    eb/jump  $subx-tests-main:end/disp8
+$subx-tests-main:interactive:
     # - otherwise convert stdin
-    # convert(Stdin, Stdout)
+    # subx-gen-run-tests(Stdin, Stdout)
     # . . push args
     68/push  Stdout/imm32
     68/push  Stdin/imm32
     # . . call
-    e8/call  convert/disp32
+    e8/call  subx-gen-run-tests/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # syscall(exit, 0)
     bb/copy-to-ebx  0/imm32
-$main:end:
+$subx-tests-main:end:
     b8/copy-to-eax  1/imm32/exit
     cd/syscall  0x80/imm8
 
-convert:  # in : (address buffered-file), out : (address buffered-file) -> <void>
+subx-gen-run-tests:  # in : (address buffered-file), out : (address buffered-file) -> <void>
     # pseudocode
     #   bool tests-found = false
     #   var line = new-stream(512, 1)
@@ -141,7 +141,7 @@ convert:  # in : (address buffered-file), out : (address buffered-file) -> <void
     e8/call  write/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-$convert:loop:
+$subx-gen-run-tests:loop:
     # clear-stream(line)
     # . . push args
     51/push-ecx
@@ -157,10 +157,10 @@ $convert:loop:
     e8/call  read-line-buffered/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-$convert:check0:
+$subx-gen-run-tests:check0:
     # if (line->write == 0) break
     81          7/subop/compare     0/mod/indirect  1/rm32/ecx    .           .             .           .           .               0/imm32           # compare *ecx
-    0f 84/jump-if-equal  $convert:break/disp32
+    0f 84/jump-if-equal  $subx-gen-run-tests:break/disp32
     # next-word(line, word-slice)
     # . . push args
     52/push-edx
@@ -169,7 +169,7 @@ $convert:check0:
     e8/call  next-word/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-$convert:check-for-label:
+$subx-gen-run-tests:check-for-label:
     # if (!is-label?(word-slice)) continue
     # . eax = is-label?(word-slice)
     # . . push args
@@ -180,8 +180,8 @@ $convert:check-for-label:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
     # . if (eax == 0) continue
     3d/compare-eax-and  0/imm32
-    74/jump-if-equal  $convert:continue/disp8
-$convert:check-label-prefix:
+    74/jump-if-equal  $subx-gen-run-tests:continue/disp8
+$subx-gen-run-tests:check-label-prefix:
     # strip trailing ':' from word-slice
     ff          1/subop/decrement   1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # decrement *(edx+4)
     # if !slice-starts-with?(word-slice, "test-") continue
@@ -194,8 +194,8 @@ $convert:check-label-prefix:
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # . if (eax == 0) break
     3d/compare-eax-and  0/imm32
-    74/jump-if-equal  $convert:continue/disp8
-$convert:call-test-function:
+    74/jump-if-equal  $subx-gen-run-tests:continue/disp8
+$subx-gen-run-tests:call-test-function:
     # tests-found? = true
     bb/copy-to-ebx  1/imm32/true
     # write(new-code-segment, "  e8/call  ")
@@ -222,7 +222,7 @@ $convert:call-test-function:
     e8/call  write/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-$convert:continue:
+$subx-gen-run-tests:continue:
     # rewind-stream(line)
     # . . push args
     51/push-ecx
@@ -239,11 +239,11 @@ $convert:continue:
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     # loop
-    e9/jump  $convert:loop/disp32
-$convert:break:
+    e9/jump  $subx-gen-run-tests:loop/disp32
+$subx-gen-run-tests:break:
     # if (!tests-found?) goto end
     81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0/imm32           # compare ebx
-    74/jump-if-equal  $convert:end/disp8
+    74/jump-if-equal  $subx-gen-run-tests:end/disp8
     # write(new-code-segment, "  c3/return\n")
     # . . push args
     68/push  "  c3/return\n"/imm32
@@ -260,7 +260,7 @@ $convert:break:
     e8/call  write-stream-data/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
-$convert:end:
+$subx-gen-run-tests:end:
     # flush(out)
     # . . push args
     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
diff --git a/build b/build
index 0d723656..9e95bb34 100755
--- a/build
+++ b/build
@@ -106,54 +106,4 @@ older_than subx_bin subx.cc *_list && {
   $CXX $CFLAGS subx.cc -o subx_bin
 }
 
-# We ought to always rebuild all apps if any .subx layers are updated.
-# But during development it's too slow to update _all_ apps when we're
-# repeatedly running a single one.
-if [ ! $ONLY_CPP ]
-then
-
-  # Assumption: SubX programs don't need to be retranslated every time we
-  # rebuild the C++ bootstrap.
-
-  OS=${OS:-linux}
-
-  # simple example programs
-  for n in `seq 1 12`
-  do
-    older_than examples/ex$n init.$OS examples/ex$n.subx && {
-      ./subx_bin translate init.$OS examples/ex$n.subx -o examples/ex$n
-    }
-  done
-
-  # simple apps that use the standard library
-  for app in factorial crenshaw2-1 crenshaw2-1b handle
-  do
-    older_than apps/$app init.$OS [0-9]*.subx apps/$app.subx && {
-      ./subx_bin translate init.$OS [0-9]*.subx apps/$app.subx -o apps/$app
-    }
-  done
-
-  # self-hosting translator
-
-  older_than apps/hex init.$OS 0[0-6]*.subx 070---hex.subx && {
-    ./subx_bin translate init.$OS 0[0-6]*.subx 070---hex.subx -o apps/hex
-  }
-
-  for phase in hex survey pack assort dquotes tests
-  do
-    older_than apps/$phase init.$OS [0-9]*.subx apps/subx-common.subx apps/$phase.subx && {
-      ./subx_bin translate init.$OS [0-9]*.subx apps/subx-common.subx apps/$phase.subx -o apps/$phase
-    }
-  done
-
-  # higher-level syntax
-  for phase in sigils
-  do
-    older_than apps/$phase init.$OS [0-9]*.subx apps/subx-common.subx apps/$phase.subx && {
-      ./subx_bin translate init.$OS [0-9]*.subx apps/subx-common.subx apps/$phase.subx -o apps/$phase
-    }
-  done
-
-fi
-
 exit 0
diff --git a/run_one_test b/run_one_test
index b8eeaad5..04af6ba0 100755
--- a/run_one_test
+++ b/run_one_test
@@ -7,7 +7,7 @@
 if [[ $2 == 'test-'* ]]
 then
   TEST_NAME=$2 envsubst '$TEST_NAME' < run_one_test.subx > /tmp/run_one_test.subx
-  FILES=$(ls [0-9]*.subx apps/subx-common.subx $1 |sort |uniq)
+  FILES=$(ls [0-9]*.subx apps/subx-params.subx $1 |sort |uniq)
   echo $FILES > /tmp/last_run_files
 elif [[ -e /tmp/last_run_files ]]
 then
diff --git a/test_apps b/test_apps
index bb645e97..d69e5b83 100755
--- a/test_apps
+++ b/test_apps
@@ -225,82 +225,28 @@ test $NATIVE  &&  {
 
 # Phases of the self-hosted SubX translator.
 
-echo hex
-./subx translate init.$OS $(enumerate/enumerate --until 070---hex.subx |grep '\.subx$') -o apps/hex
-test "$1" = 'record'  ||  git diff --exit-code apps/hex
-test $EMULATED  &&  {
-  ./subx run apps/hex test
-  echo
-}
-test $NATIVE  &&  {
-  apps/hex test
-  echo
-}
-
-echo survey
-./subx translate init.$OS 0*.subx apps/subx-common.subx apps/survey.subx  -o apps/survey
-test "$1" = 'record'  ||  git diff --exit-code apps/survey
-test $EMULATED  &&  {
-  ./subx run apps/survey test
-  echo
-}
-test $NATIVE  &&  {
-  apps/survey test
-  echo
-}
-
-echo pack
-./subx translate init.$OS 0*.subx apps/subx-common.subx apps/pack.subx  -o apps/pack
-test "$1" = 'record'  ||  git diff --exit-code apps/pack
-test $EMULATED  &&  {
-  ./subx run apps/pack test
-  echo
-}
-test $NATIVE  &&  {
-  apps/pack test
-  echo
-}
-
-echo assort
-./subx translate init.$OS 0*.subx apps/subx-common.subx apps/assort.subx  -o apps/assort
-test "$1" = 'record'  ||  git diff --exit-code apps/assort
-test $EMULATED  &&  {
-  ./subx run apps/assort test
-  echo
-}
-test $NATIVE  &&  {
-  apps/assort test
-  echo
-}
-
-echo dquotes
-./subx translate init.$OS 0*.subx apps/subx-common.subx apps/dquotes.subx  -o apps/dquotes
-test "$1" = 'record'  ||  git diff --exit-code apps/dquotes
-test $EMULATED  &&  {
-  ./subx run apps/dquotes test
-  echo
-}
-test $NATIVE  &&  {
-  apps/dquotes test
-  echo
-}
-
-echo tests
-./subx translate init.$OS 0*.subx apps/subx-common.subx apps/tests.subx  -o apps/tests
-test "$1" = 'record'  ||  git diff --exit-code apps/tests
-test $EMULATED  &&  {
-  ./subx run apps/tests test
-  echo
-}
-test $NATIVE  &&  {
-  apps/tests test
-  echo
-}
+for phase in hex survey pack assort dquotes tests
+do
+  echo $phase
+  ./subx translate init.$OS 0*.subx apps/subx-params.subx apps/$phase.subx -o apps/$phase
+  test "$1" = 'record'  ||  git diff --exit-code apps/hex
+  test $EMULATED  &&  {
+    ./subx run apps/$phase test
+    echo
+  }
+  test $NATIVE  &&  {
+    apps/$phase test
+    echo
+  }
+done
 
 # Higher-level syntax.
 
+# Certain phases of translation run native beyond this point. We're starting
+# to go beyond functionality of the C++ bootstrap.
+
 echo sigils
-./subx translate init.$OS 0*.subx apps/subx-common.subx apps/sigils.subx  -o apps/sigils
+./subx translate init.$OS 0*.subx apps/subx-params.subx apps/sigils.subx  -o apps/sigils
 [ "$1" != record ]  &&  git diff --exit-code apps/sigils
 ./subx run apps/sigils test
 echo
@@ -310,7 +256,7 @@ test `uname` = 'Linux'  &&  {
 }
 
 echo calls
-cat init.$OS 0*.subx apps/subx-common.subx apps/calls.subx  |  apps/sigils  > a.sigils
+cat init.$OS 0*.subx apps/subx-params.subx apps/calls.subx  |  apps/sigils  > a.sigils
 ./subx translate a.sigils -o apps/calls
 [ "$1" != record ]  &&  git diff --exit-code apps/calls
 ./subx run apps/calls test
@@ -321,7 +267,7 @@ test `uname` = 'Linux'  &&  {
 }
 
 echo braces
-cat init.$OS 0*.subx apps/subx-common.subx apps/braces.subx  |  apps/calls  |  apps/sigils  > a.sigils
+cat init.$OS 0*.subx apps/subx-params.subx apps/braces.subx  |  apps/calls  |  apps/sigils  > a.sigils
 ./subx translate a.sigils -o apps/braces
 [ "$1" != record ]  &&  git diff --exit-code apps/braces
 ./subx run apps/braces test
@@ -331,12 +277,8 @@ test `uname` = 'Linux'  &&  {
   echo
 }
 
-# Only native runs beyond this point. We start using syntax that the emulator
-# doesn't support.
-test $EMULATED  &&  echo "skipping remaining runs in emulated mode"
 test $NATIVE  ||  exit 0
-
-echo "== translating using SubX"
+echo "== translating using SubX (native only)"
 
 # example programs
 
@@ -358,14 +300,10 @@ done
 
 # Phases of the self-hosted SubX translator.
 
-echo hex
-./ntranslate init.$OS $(enumerate/enumerate --until 070---hex.subx |grep '\.subx$')
-diff apps/hex a.elf
-
-for app in survey pack assort dquotes tests sigils calls braces
+for app in hex survey pack assort dquotes tests sigils calls braces
 do
   echo $app
-  ./ntranslate init.$OS 0*.subx apps/subx-common.subx apps/$app.subx
+  ./ntranslate init.$OS 0*.subx apps/subx-params.subx apps/$app.subx
   diff apps/$app a.elf
 done
 
diff --git a/test_layers b/test_layers
index ea7551df..522e413b 100755
--- a/test_layers
+++ b/test_layers
@@ -22,11 +22,11 @@ for f in [0-9]*.subx
 do
   echo "=== $f"
   ./subx translate init.linux $(enumerate/enumerate --until $f |grep '\.subx$') -o a.elf
-  ./subx run a.elf
+  ./subx run a.elf test
   echo
   test `uname` = 'Linux'  &&  {
     chmod +x a.elf
-    ./a.elf
+    ./a.elf test
     echo
   } || true
 done