about summary refs log tree commit diff stats
path: root/subx/apps
diff options
context:
space:
mode:
authorKartik Agaram <vc@akkartik.com>2019-07-03 23:56:48 -0700
committerKartik Agaram <vc@akkartik.com>2019-07-03 23:56:48 -0700
commit06abba2607926e07e265f5b056a23a38ef9bf45d (patch)
tree20e985102ac7d9ddc455e3292184f26a331db1d7 /subx/apps
parent76aec0e63692ab9411bdda37eb50a778475e0c40 (diff)
parentcdf85518dfb364d816e0db1448e20bf30b2b65f9 (diff)
downloadmu-06abba2607926e07e265f5b056a23a38ef9bf45d.tar.gz
Merge branch 'master' into survey
High time we pulled in the final changes to dquotes.

In the process we fix one recently introduced duplicate symbol.
Diffstat (limited to 'subx/apps')
-rwxr-xr-xsubx/apps/assortbin28457 -> 28463 bytes
-rwxr-xr-xsubx/apps/crenshaw2-1bin23301 -> 23307 bytes
-rwxr-xr-xsubx/apps/crenshaw2-1bbin23860 -> 23866 bytes
-rwxr-xr-xsubx/apps/dquotesbin25223 -> 34772 bytes
-rw-r--r--subx/apps/dquotes.subx1095
-rwxr-xr-xsubx/apps/factorialbin22217 -> 22223 bytes
-rw-r--r--subx/apps/factorial.subx2
-rwxr-xr-xsubx/apps/handlebin23023 -> 23082 bytes
-rw-r--r--subx/apps/handle.subx52
-rwxr-xr-xsubx/apps/hexbin26310 -> 26316 bytes
-rwxr-xr-xsubx/apps/packbin43371 -> 43377 bytes
11 files changed, 913 insertions, 236 deletions
diff --git a/subx/apps/assort b/subx/apps/assort
index 37585b8c..9d27fd9a 100755
--- a/subx/apps/assort
+++ b/subx/apps/assort
Binary files differdiff --git a/subx/apps/crenshaw2-1 b/subx/apps/crenshaw2-1
index 7caeb9af..159ed81a 100755
--- a/subx/apps/crenshaw2-1
+++ b/subx/apps/crenshaw2-1
Binary files differdiff --git a/subx/apps/crenshaw2-1b b/subx/apps/crenshaw2-1b
index c065c1f5..f4e02da6 100755
--- a/subx/apps/crenshaw2-1b
+++ b/subx/apps/crenshaw2-1b
Binary files differdiff --git a/subx/apps/dquotes b/subx/apps/dquotes
index 4f7a6bec..6774b8df 100755
--- a/subx/apps/dquotes
+++ b/subx/apps/dquotes
Binary files differdiff --git a/subx/apps/dquotes.subx b/subx/apps/dquotes.subx
index b9c06c16..64742599 100644
--- a/subx/apps/dquotes.subx
+++ b/subx/apps/dquotes.subx
@@ -90,7 +90,7 @@ convert:  # in : (address buffered-file), out : (address buffered-file) -> <void
     #     read-line-buffered(in, line)
     #     if (line->write == 0) break               # end of file
     #     while true
-    #       var word-slice = next-word(line)
+    #       var word-slice = next-word-or-string(line)
     #       if slice-empty?(word-slice)             # end of line
     #         break
     #       if slice-starts-with?(word-slice, "#")  # comment
@@ -165,12 +165,12 @@ $convert:check0:
     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:
-    # next-word(line, word-slice)
+    # next-word-or-string(line, word-slice)
     # . . push args
     52/push-EDX
     51/push-ECX
     # . . call
-    e8/call  next-word/disp32
+    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:
@@ -731,39 +731,39 @@ test-convert-processes-string-literals:
     # called. We just want to make sure instructions using string literals
     # switch to a string variable with the right value.
     # (Modifying string literals completely off the radar for now.)
-    # 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
-    # }}}
+#?     # 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, "== code 0x1 ", msg)
     # . . push args
     68/push  "F - test-convert-processes-string-literals/0"/imm32
@@ -791,10 +791,10 @@ test-convert-processes-string-literals:
     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
-    # . check-next-stream-line-equal(_test-output-stream, "== data ", msg)
+    # . check-next-stream-line-equal(_test-output-stream, "== data", msg)
     # . . push args
     68/push  "F - test-convert-processes-string-literals/3"/imm32
-    68/push  "== data "/imm32
+    68/push  "== data"/imm32
     68/push  _test-output-stream/imm32
     # . . call
     e8/call  check-next-stream-line-equal/disp32
@@ -844,15 +844,15 @@ test-convert-processes-string-literals:
 # generate the data segment contents byte by byte for a given slice
 emit-string-literal-data:  # out : (address stream), word : (address slice)
     # pseudocode
-    #   var len = word->end - word->start - 2  # ignore the double-quotes
-    #   append-int32-hex(out, len)
-    #   write(out, "/imm32")
+    #   len = string-length-at-start-of-slice(word->start, word->end)
+    #   print(out, "#{len}/imm32 ")
     #   curr = word->start
     #   ++curr  # skip '"'
     #   while true
     #     if (curr >= word->end) break
     #     c = *curr
     #     if (c == '"') break
+    #     if (c == '\') ++curr, c = *curr
     #     append-byte-hex(out, c)
     #     if c is alphanumeric:
     #       write(out, "/")
@@ -872,16 +872,21 @@ emit-string-literal-data:  # out : (address stream), word : (address slice)
     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   0xc/disp8       .                 # copy *(EBP+12) to ESI
     # curr/EDX = word->start
     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
+    # max/ESI = word->end
+    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           6/r32/ESI   4/disp8         .                 # copy *(ESI+4) to ESI
 $emit-string-literal-data:emit-length:
-    # TODO: handle metadata here
+    # len/EAX = string-length-at-start-of-slice(word->start, word->end)
+    # . . push args
+    56/push-ESI
+    52/push-EDX
+    # . . call
+    e8/call  string-length-at-start-of-slice/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
     # print(out, "#{len}/imm32 ")
-    # . len/ECX = word->end - word->start - 2
-    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(ESI+4) to ECX
-    29/subtract                     3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # subtract EDX from ECX
-    81          5/subop/subtract    3/mod/direct    1/rm32/ECX    .           .             .           .           .               2/imm32           # subtract from ECX
     # . print-int32(out, len)
     # . . push args
-    51/push-ECX
+    50/push-EAX
     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
     # . . call
     e8/call  print-int32/disp32
@@ -898,9 +903,7 @@ $emit-string-literal-data:emit-length:
 $emit-string-literal-data:loop-init:
     # ++curr  # skip initial '"'
     42/increment-EDX
-    # max/ESI = word->end
-    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           6/r32/ESI   4/disp8         .                 # copy *(ESI+4) to ESI
-    # ECX = 0
+    # c/ECX = 0
     31/xor                          3/mod/direct    1/rm32/ECX    .           .             .           1/r32/ECX   .               .                 # clear ECX
 $emit-string-literal-data:loop:
     # if (curr >= max) break
@@ -911,6 +914,17 @@ $emit-string-literal-data:loop:
     # if (ECX == '"') break
     81          7/subop/compare     3/mod/direct    1/rm32/ECX    .           .             .           .           .               0x22/imm32/dquote # compare ECX
     74/jump-if-equal  $emit-string-literal-data:end/disp8
+    # if (ECX == '\') ++curr, ECX = *curr
+    81          7/subop/compare     3/mod/direct    1/rm32/ECX    .           .             .           .           .               0x5c/imm32/backslash  # compare ECX
+    75/jump-if-not-equal  $emit-string-literal-data:emit/disp8
+    # . ++curr
+    42/increment-EDX
+    # . if (curr >= max) break
+    39/compare                      3/mod/direct    2/rm32/EDX    .           .             .           6/r32/ESI   .               .                 # compare EDX with ESI
+    7d/jump-if-greater-or-equal  $emit-string-literal-data:end/disp8
+    # . CL = *curr
+    8a/copy-byte                    0/mod/indirect  2/rm32/EDX    .           .             .           1/r32/CL    .               .                 # copy byte at *EDX to CL
+$emit-string-literal-data:emit:
     # append-byte-hex(out, CL)
     # . . push args
     51/push-ECX
@@ -1017,8 +1031,8 @@ test-emit-string-literal-data:
     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 = '"abc"'
-    68/push  _test-slice-abc-end/imm32
+    # var slice/ECX = '"abc"/d'
+    68/push  _test-slice-abc-metadata-end/imm32
     68/push  _test-slice-abc/imm32
     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
     # emit-string-literal-data(_test-output-stream, slice)
@@ -1251,7 +1265,7 @@ test-emit-string-literal-data-handles-escape-sequences:
     # . check-stream-equal(_test-output-stream, "3/imm32 61/a 22 62/b ", msg)
     # . . push args
     68/push  "F - test-emit-string-literal-data-handles-escape-sequences"/imm32
-    68/push  "3/imm32 61/a 22 62/b "/imm32
+    68/push  "0x00000003/imm32 61/a 22 62/b "/imm32
     68/push  _test-output-stream/imm32
     # . . call
     e8/call  check-stream-equal/disp32
@@ -1264,6 +1278,21 @@ test-emit-string-literal-data-handles-escape-sequences:
 
 # emit everything from a word except the initial datum
 emit-metadata:  # out : (address buffered-file), word : (address slice)
+    # pseudocode
+    #   var slice = {0, word->end}
+    #   curr = word->start
+    #   if *curr == '"'
+    #     curr = skip-string-in-slice(curr, word->end)
+    #   else
+    #     while true
+    #       if curr == word->end
+    #         return
+    #       if *curr == '/'
+    #         break
+    #       ++curr
+    #   slice->curr = curr
+    #   write-slice-buffered(out, slice)
+    #
     # . prolog
     55/push-EBP
     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
@@ -1271,59 +1300,66 @@ emit-metadata:  # out : (address buffered-file), word : (address slice)
     50/push-EAX
     51/push-ECX
     52/push-EDX
-
-    # PSEUDOCODE
-    # ECX = (char *) word->start
-    # while true:
-    #   if ECX == word->end: return
-    #   if *(ECX++) == '/': break
-    # write-slice-buffered(out, {ECX, word->end})
-
-    # ECX = word
-    8b/copy/word                    1/mod/*+disp8   5/rm32/EBP    .           .             .           1/r32/ECX   0xc/disp8       .                 # copy *(EBP+12) to ECX
-    # EDX = word->end
-    8b/copy/word->end               1/mod/*+disp8   1/rm32/ECX    .           .             .           2/r32/EDX   4/disp8         .                 # copy *(ECX+4) to EDX
-    # ECX = word->start
-    8b/copy/word->start             0/mod/indirect  1/rm32/ECX    .           .             .           1/r32/ECX   .               .                 # copy *ECX to ECX
-
-    # clear out EAX
-    b8/copy-to-EAX 0/imm32
-    # while *start != '/':
-$skip-datum-loop:
-    # . start == end?
-    39/compare-ECX-and              3/mod/direct    2/rm32/EDX    .           .             .           1/r32/ECX   .               .                 # EDX == ECX
-    # . if so, return from function (it's only datum, or empty)
-    74/jump-if-equal  $emit-metadata:end/disp8
-
-    # . start++
-    41/increment-ECX                                                                                                                                  # ECX++
-
-    # . EAX = *start
-    8a/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy *ECX to EAX
-
-    # . EAX != '/'?
-    3d/compare-EAX-and  0x2f/imm32
-    # . if so, continue looping
-    75/jump-if-not-equal  $skip-datum-loop/disp8
-    # end
-
-    # write-slice-buffered(out, &{start, end})
-    # . push end
+    53/push-EBX
+    56/push-ESI
+    # ESI = word
+    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   0xc/disp8       .                 # copy *(EBP+12) to ESI
+    # curr/ECX = word->start
+    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # copy *ESI to ECX
+    # end/EDX = word->end
+    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           2/r32/EDX   4/disp8         .                 # copy *(ESI+4) to EDX
+    # var slice/EBX = {0, end}
+    52/push-EDX
+    68/push  0/imm32
+    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBX
+    # EAX = 0
+    b8/copy-to-EAX  0/imm32
+$emit-metadata:check-for-string-literal:
+    # -  if (*curr == '"') curr = skip-string-in-slice(curr, end)
+    8a/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/AL    .               .                 # copy byte at *ECX to AL
+    3d/compare-EAX-and  0x22/imm32/dquote
+    75/jump-if-not-equal  $emit-metadata:skip-datum-loop/disp8
+$emit-metadata:skip-string-literal:
+    # . EAX = skip-string-in-slice(curr, end)
+    # . . push args
     52/push-EDX
-    # . push start
     51/push-ECX
-    # . push &{start, end}
-    54/push-ESP
-
-    # . push out
+    # . . call
+    e8/call  skip-string-in-slice/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # . curr = EAX
+    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy EAX to ECX
+    eb/jump  $emit-metadata:emit/disp8
+$emit-metadata:skip-datum-loop:
+    # - otherwise scan for '/'
+    # if (curr == end) return
+    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX and EDX
+    74/jump-if-equal  $emit-metadata:end/disp8
+    # if (*curr == '/') break
+    8a/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/AL    .               .                 # copy byte at *ECX to AL
+    3d/compare-EAX-and  0x2f/imm32/slash
+    74/jump-if-equal  $emit-metadata:emit/disp8
+    # ++curr
+    41/increment-ECX
+    eb/jump  $emit-metadata:skip-datum-loop/disp8
+$emit-metadata:emit:
+    # slice->curr = ECX
+    89/copy                         0/mod/indirect  3/rm32/EBX    .           .             .           1/r32/ECX   .               .                 # copy ECX to *EBX
+    # write-slice-buffered(out, slice)
+    # . . push args
+    53/push-EBX
     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    .           .             .           .           0x10/imm32      .                 # add 16 to ESP
-
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           8/imm32      .                    # add to ESP
 $emit-metadata:end:
+    # . reclaim locals
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           8/imm32      .                    # add to ESP
     # . restore registers
+    5e/pop-to-ESI
+    5b/pop-to-EBX
     5a/pop-to-EDX
     59/pop-to-ECX
     58/pop-to-EAX
@@ -1494,9 +1530,150 @@ test-emit-metadata-multiple:
     5d/pop-to-EBP
     c3/return
 
+test-emit-metadata-when-no-datum:
+    # . 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 slice/ECX = "/abc"
+    b8/copy-to-EAX  "/abc"/imm32
+    # . push end/ECX
+    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
+    51/push-ECX
+    # . push curr/EAX
+    05/add-to-EAX  4/imm32
+    50/push-EAX
+    # . save stack pointer
+    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
+    # emit-metadata(_test-output-buffered-file, slice)
+    # . . push args
+    51/push-ECX
+    68/push  _test-output-buffered-file/imm32
+    # . . call
+    e8/call  emit-metadata/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
+    # check-stream-equal(_test-output-stream, "/abc", msg)  # nothing skipped
+    # . . push args
+    68/push  "F - test-emit-metadata-when-no-datum"/imm32
+    68/push  "/abc"/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-metadata-in-string-literal:
+    # . 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 slice/ECX = "\"abc/def\"/ghi"
+    68/push  _test-slice-literal-string-with-metadata-end/imm32
+    68/push  _test-slice-literal-string/imm32/start
+    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
+    # emit-metadata(_test-output-buffered-file, slice)
+    # . . push args
+    51/push-ECX
+    68/push  _test-output-buffered-file/imm32
+    # . . call
+    e8/call  emit-metadata/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
+#?     # }}}
+    # check-stream-equal(_test-output-stream, "/ghi", msg)  # important that there's no leading space
+    # . . push args
+    68/push  "F - test-emit-metadata-in-string-literal"/imm32
+    68/push  "/ghi"/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
+
 # (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)
+next-word-or-string:  # 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
@@ -1517,18 +1694,18 @@ next-word:  # line : (address stream byte), out : (address slice)
     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:
+$next-word-or-string: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
+    7c/jump-if-lesser  $next-word-or-string: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:
+    eb/jump  $next-word-or-string:end/disp8
+$next-word-or-string: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
@@ -1539,8 +1716,8 @@ $next-word:check-for-comment:
     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:check-for-string-literal/disp8
-$next-word:comment:
+    75/jump-if-not-equal  $next-word-or-string:check-for-string-literal/disp8
+$next-word-or-string: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
@@ -1549,32 +1726,26 @@ $next-word:comment:
     8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # copy *ESI to EAX
     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:check-for-string-literal:
+    eb/jump  $next-word-or-string:end/disp8
+$next-word-or-string:check-for-string-literal:
     # if line->data[line->read] == '"'
     # . 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  0x22/imm32/dquote
-    75/jump-if-not-equal  $next-word:regular-word/disp8
-$next-word:string-literal:
-    # ++line->read  # skip '"'
-    # . persist line->read
-    89/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # copy ECX to *(ESI+4)
-    # . ++line->read
-    ff          0/subop/increment   1/mod/*+disp8   6/rm32/ESI    .           .             .           .           4/disp8         .                 # increment *(ESI+4)
-    # parse-string(line, out)
+    75/jump-if-not-equal  $next-word-or-string:regular-word/disp8
+$next-word-or-string:string-literal:
+    # skip-string(line)
     # . . push args
-    57/push-EDI
     56/push-ESI
     # . . call
-    e8/call  parse-string/disp32
+    e8/call  skip-string/disp32
     # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
     # fall through
-$next-word:regular-word:
-    # otherwise skip-chars-not-matching-whitespace(line)  # including trailing newline
+$next-word-or-string:regular-word:
+    # 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
@@ -1585,7 +1756,7 @@ $next-word:regular-word:
     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:
+$next-word-or-string:end:
     # . restore registers
     5f/pop-to-EDI
     5e/pop-to-ESI
@@ -1596,81 +1767,7 @@ $next-word:end:
     5d/pop-to-EBP
     c3/return
 
-parse-string:  # line : (address stream byte), out : (address slice)
-    # pseudocode:
-    #   ESI = line
-    #   curr/ECX = line->data[line->read]
-    #   max/EDX = line->data[line->write]
-    #   while curr >= max
-    #     if (*curr == '"') ++curr, break
-    #     if (*curr == '\\') curr+=2, continue
-    #     ++curr
-    #   line->read = curr - line->data
-    #   out->end = curr
-    #
-    # . 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
-    56/push-ESI
-    # ESI = line
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
-    # curr/ECX = &table->data[table->read]
-    # . ECX = table->read
-    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(ESI+4) to ECX
-    # . ECX = table->data + ECX
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/ESI  1/index/ECX   .           1/r32/ECX   0xc/disp8       .                 # copy ESI+ECX+12 to ECX
-    # max/EDX = &table->data[table->write]
-    # . EDX = table->write
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
-    # . EDX = table->data + EDX
-    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/ESI  2/index/EDX   .           2/r32/EDX   0xc/disp8       .                 # copy ESI+EDX+12 to EDX
-    # clear EAX
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-$parse-string:loop:
-    # if (curr >= max) break
-    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
-    7d/jump-if-greater-or-equal  $parse-string:break/disp8
-    # c/EAX = *curr
-    8a/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/AL    .               .                 # copy byte at *ECX to AL
-$parse-string:check1:
-    # if (c == '"') break  # rely on caller to skip trailing non-whitespace
-    3d/compare-EAX-and  0x22/imm32/dquote
-    74/jump-if-equal  $parse-string:break/disp8
-$parse-string:check2:
-    # if (c == '\\') ++curr
-    3d/compare-EAX-and  0x5c/imm32/backslash
-    75/jump-if-not-equal  $parse-string:continue/disp8
-    # . ++curr
-    41/increment-ECX
-$parse-string:continue:
-    # ++curr
-    41/increment-ECX
-    # loop
-    eb/jump  $parse-string:loop/disp8
-$parse-string:break:
-    # out->end = curr
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   0xc/disp8       .                 # copy *(EBP+12) to EAX
-    89/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           1/r32/ECX   4/disp8         .                 # copy ECX to *(EAX+4)
-    # line->read = curr - line - 12
-    29/subtract                     3/mod/direct    1/rm32/ECX    .           .             .           6/r32/ESI   .               .                 # subtract ESI from ECX
-    81          5/subop/subtract    3/mod/direct    1/rm32/ECX    .           .             .           .           .               0xc/imm32         # subtract from ECX
-    89/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   4/disp8         .                 # copy ECX to *(ESI+4)
-$parse-string:end:
-    # . restore registers
-    5e/pop-to-ESI
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    58/pop-to-EAX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-test-next-word:
+test-next-word-or-string:
     # . prolog
     55/push-EBP
     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
@@ -1694,17 +1791,17 @@ test-next-word:
     e8/call  write/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # next-word(_test-input-stream, slice)
+    # next-word-or-string(_test-input-stream, slice)
     # . . push args
     51/push-ECX
     68/push  _test-input-stream/imm32
     # . . call
-    e8/call  next-word/disp32
+    e8/call  next-word-or-string/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
     # check-ints-equal(_test-input-stream->read, 4, msg)
     # . . push args
-    68/push  "F - test-next-word/updates-stream-read-correctly"/imm32
+    68/push  "F - test-next-word-or-string/updates-stream-read-correctly"/imm32
     68/push  4/imm32
     b8/copy-to-EAX  _test-input-stream/imm32
     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         .                 # push *(EAX+4)
@@ -1715,7 +1812,7 @@ test-next-word:
     # check-ints-equal(slice->start - _test-input-stream->data, 2, msg)
     # . check-ints-equal(slice->start - _test-input-stream, 14, msg)
     # . . push args
-    68/push  "F - test-next-word: start"/imm32
+    68/push  "F - test-next-word-or-string: start"/imm32
     68/push  0xe/imm32
     # . . push slice->start - _test-input-stream
     8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy *ECX to EAX
@@ -1728,7 +1825,7 @@ test-next-word:
     # check-ints-equal(slice->end - _test-input-stream->data, 4, msg)
     # . check-ints-equal(slice->end - _test-input-stream, 16, msg)
     # . . push args
-    68/push  "F - test-next-word: end"/imm32
+    68/push  "F - test-next-word-or-string: end"/imm32
     68/push  0x10/imm32
     # . . push slice->end - _test-input-stream
     8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ECX+4) to EAX
@@ -1743,7 +1840,7 @@ test-next-word:
     5d/pop-to-EBP
     c3/return
 
-test-next-word-returns-whole-comment:
+test-next-word-or-string-returns-whole-comment:
     # . prolog
     55/push-EBP
     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
@@ -1767,17 +1864,17 @@ test-next-word-returns-whole-comment:
     e8/call  write/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # next-word(_test-input-stream, slice)
+    # next-word-or-string(_test-input-stream, slice)
     # . . push args
     51/push-ECX
     68/push  _test-input-stream/imm32
     # . . call
-    e8/call  next-word/disp32
+    e8/call  next-word-or-string/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
     # check-ints-equal(_test-input-stream->read, 5, msg)
     # . . push args
-    68/push  "F - test-next-word-returns-whole-comment/updates-stream-read-correctly"/imm32
+    68/push  "F - test-next-word-or-string-returns-whole-comment/updates-stream-read-correctly"/imm32
     68/push  5/imm32
     b8/copy-to-EAX  _test-input-stream/imm32
     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         .                 # push *(EAX+4)
@@ -1788,7 +1885,7 @@ test-next-word-returns-whole-comment:
     # check-ints-equal(slice->start - _test-input-stream->data, 2, msg)
     # . check-ints-equal(slice->start - _test-input-stream, 14, msg)
     # . . push args
-    68/push  "F - test-next-word-returns-whole-comment: start"/imm32
+    68/push  "F - test-next-word-or-string-returns-whole-comment: start"/imm32
     68/push  0xe/imm32
     # . . push slice->start - _test-input-stream
     8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy *ECX to EAX
@@ -1801,7 +1898,7 @@ test-next-word-returns-whole-comment:
     # check-ints-equal(slice->end - _test-input-stream->data, 5, msg)
     # . check-ints-equal(slice->end - _test-input-stream, 17, msg)
     # . . push args
-    68/push  "F - test-next-word-returns-whole-comment: end"/imm32
+    68/push  "F - test-next-word-or-string-returns-whole-comment: end"/imm32
     68/push  0x11/imm32
     # . . push slice->end - _test-input-stream
     8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ECX+4) to EAX
@@ -1816,7 +1913,7 @@ test-next-word-returns-whole-comment:
     5d/pop-to-EBP
     c3/return
 
-test-next-word-returns-empty-string-on-eof:
+test-next-word-or-string-returns-empty-string-on-eof:
     # . prolog
     55/push-EBP
     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
@@ -1833,17 +1930,17 @@ test-next-word-returns-empty-string-on-eof:
     68/push  0/imm32/start
     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
     # write nothing to _test-input-stream
-    # next-word(_test-input-stream, slice)
+    # next-word-or-string(_test-input-stream, slice)
     # . . push args
     51/push-ECX
     68/push  _test-input-stream/imm32
     # . . call
-    e8/call  next-word/disp32
+    e8/call  next-word-or-string/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  "F - test-next-word-or-string-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
@@ -1858,7 +1955,7 @@ test-next-word-returns-empty-string-on-eof:
     5d/pop-to-EBP
     c3/return
 
-test-next-word-returns-whole-string:
+test-next-word-or-string-returns-whole-string:
     # . prolog
     55/push-EBP
     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
@@ -1882,18 +1979,18 @@ test-next-word-returns-whole-string:
     e8/call  write/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # next-word(_test-input-stream, slice)
+    # next-word-or-string(_test-input-stream, slice)
     # . . push args
     51/push-ECX
     68/push  _test-input-stream/imm32
     # . . call
-    e8/call  next-word/disp32
+    e8/call  next-word-or-string/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
     # check-ints-equal(slice->start - _test-input-stream->data, 1, msg)
     # . check-ints-equal(slice->start - _test-input-stream, 13, msg)
     # . . push args
-    68/push  "F - test-next-word-returns-whole-string: start"/imm32
+    68/push  "F - test-next-word-or-string-returns-whole-string: start"/imm32
     68/push  0xd/imm32
     # . . push slice->start - _test-input-stream
     8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy *ECX to EAX
@@ -1906,7 +2003,7 @@ test-next-word-returns-whole-string:
     # check-ints-equal(slice->end - _test-input-stream->data, 12, msg)
     # . check-ints-equal(slice->end - _test-input-stream, 24, msg)
     # . . push args
-    68/push  "F - test-next-word-returns-whole-string: end"/imm32
+    68/push  "F - test-next-word-or-string-returns-whole-string: end"/imm32
     68/push  0x18/imm32
     # . . push slice->end - _test-input-stream
     8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ECX+4) to EAX
@@ -1921,7 +2018,7 @@ test-next-word-returns-whole-string:
     5d/pop-to-EBP
     c3/return
 
-test-next-word-returns-string-with-escapes:
+test-next-word-or-string-returns-string-with-escapes:
     # . prolog
     55/push-EBP
     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
@@ -1945,18 +2042,18 @@ test-next-word-returns-string-with-escapes:
     e8/call  write/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # next-word(_test-input-stream, slice)
+    # next-word-or-string(_test-input-stream, slice)
     # . . push args
     51/push-ECX
     68/push  _test-input-stream/imm32
     # . . call
-    e8/call  next-word/disp32
+    e8/call  next-word-or-string/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
     # check-ints-equal(slice->start - _test-input-stream->data, 1, msg)
     # . check-ints-equal(slice->start - _test-input-stream, 13, msg)
     # . . push args
-    68/push  "F - test-next-word-returns-string-with-escapes: start"/imm32
+    68/push  "F - test-next-word-or-string-returns-string-with-escapes: start"/imm32
     68/push  0xd/imm32
     # . . push slice->start - _test-input-stream
     8b/copy                         0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy *ECX to EAX
@@ -1969,7 +2066,7 @@ test-next-word-returns-string-with-escapes:
     # check-ints-equal(slice->end - _test-input-stream->data, 9, msg)
     # . check-ints-equal(slice->end - _test-input-stream, 21, msg)
     # . . push args
-    68/push  "F - test-next-word-returns-string-with-escapes: end"/imm32
+    68/push  "F - test-next-word-or-string-returns-string-with-escapes: end"/imm32
     68/push  0x15/imm32
     # . . push slice->end - _test-input-stream
     8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ECX+4) to EAX
@@ -1984,6 +2081,546 @@ test-next-word-returns-string-with-escapes:
     5d/pop-to-EBP
     c3/return
 
+# update line->read to end of string literal surrounded by double quotes
+# line->read must start out at a double-quote
+skip-string:  # line : (address stream)
+    # . 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
+    # ECX = line
+    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         1/r32/ECX   8/disp8         .                 # copy *(EBP+8) to ECX
+    # EAX = skip-string-in-slice(&line->data[line->read], &line->data[line->write])
+    # . . push &line->data[line->write]
+    8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .                         2/r32/EDX   8/disp8         .                 # copy *(ECX+8) to EDX
+    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    1/base/ECX  2/index/EDX   .           2/r32/EDX   0xc/disp8       .                 # copy ECX+EDX+12 to EDX
+    52/push-EDX
+    # . . push &line->data[line->read]
+    8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .                         2/r32/EDX   4/disp8         .                 # copy *(ECX+4) to EDX
+    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    1/base/ECX  2/index/EDX   .           2/r32/EDX   0xc/disp8       .                 # copy ECX+EDX+12 to EDX
+    52/push-EDX
+    # . . call
+    e8/call  skip-string-in-slice/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # line->read = EAX - line->data
+    29/subtract                     3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EAX
+    2d/subtract-from-EAX  0xc/imm32
+    89/copy                         1/mod/*+disp8   1/rm32/ECX    .           .                         0/r32/EAX   4/disp8         .                 # copy EAX to *(ECX+4)
+$skip-string:end:
+    # . restore registers
+    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-skip-string:
+    # . prolog
+    55/push-EBP
+    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+    # setup
+    # . 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
+    # . write(_test-input-stream, "\"abc\" def")
+    # .                   indices:  0123 45
+    # . . push args
+    68/push  "\"abc\" def"/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
+    # precondition: line->read == 0
+    # . . push args
+    68/push  "F - test-skip-string/precondition"/imm32
+    68/push  0/imm32
+    b8/copy-to-EAX  _test-input-stream/imm32
+    ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         .                 # push *(EAX+4)
+    # . . call
+    e8/call  check-ints-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+    # skip-string(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  skip-string/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # check-ints-equal(line->read, 5, msg)
+    # . . push args
+    68/push  "F - test-skip-string"/imm32
+    68/push  5/imm32
+    b8/copy-to-EAX  _test-input-stream/imm32
+    ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         .                 # push *(EAX+4)
+    # . . 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-skip-string-ignores-spaces:
+    # . prolog
+    55/push-EBP
+    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+    # setup
+    # . 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
+    # . write(_test-input-stream, "\"a b\"/yz")
+    # .                   indices:  0123 45
+    # . . push args
+    68/push  "\"a b\"/yz"/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
+    # precondition: line->read == 0
+    # . . push args
+    68/push  "F - test-skip-string-ignores-spaces/precondition"/imm32
+    68/push  0/imm32
+    b8/copy-to-EAX  _test-input-stream/imm32
+    ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         .                 # push *(EAX+4)
+    # . . call
+    e8/call  check-ints-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+    # skip-string(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  skip-string/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # check-ints-equal(line->read, 5, msg)
+    # . . push args
+    68/push  "F - test-skip-string-ignores-spaces"/imm32
+    68/push  5/imm32
+    b8/copy-to-EAX  _test-input-stream/imm32
+    ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         .                 # push *(EAX+4)
+    # . . 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-skip-string-ignores-escapes:
+    # . prolog
+    55/push-EBP
+    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+    # setup
+    # . 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
+    # . write(_test-input-stream, "\"a\\\"b\"/yz")
+    # .                   indices:  01 2 34 56
+    # . . push args
+    68/push  "\"a\\\"b\"/yz"/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
+    # precondition: line->read == 0
+    # . . push args
+    68/push  "F - test-skip-string-ignores-escapes/precondition"/imm32
+    68/push  0/imm32
+    b8/copy-to-EAX  _test-input-stream/imm32
+    ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         .                 # push *(EAX+4)
+    # . . call
+    e8/call  check-ints-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+    # skip-string(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  skip-string/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # check-ints-equal(line->read, 6, msg)
+    # . . push args
+    68/push  "F - test-skip-string-ignores-escapes"/imm32
+    68/push  6/imm32
+    b8/copy-to-EAX  _test-input-stream/imm32
+    ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         .                 # push *(EAX+4)
+    # . . 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-skip-string-works-from-mid-stream:
+    # . prolog
+    55/push-EBP
+    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+    # setup
+    # . 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
+    # . write(_test-input-stream, "0 \"a\\\"b\"/yz")
+    # .                   indices:  01 2 34 56
+    # . . push args
+    68/push  "0 \"a\\\"b\"/yz"/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
+    # precondition: line->read == 2
+    c7          0/subop/copy        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         2/imm32           # copy to *(EAX+4)
+    # skip-string(_test-input-stream)
+    # . . push args
+    68/push  _test-input-stream/imm32
+    # . . call
+    e8/call  skip-string/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # check-ints-equal(line->read, 8, msg)
+    # . . push args
+    68/push  "F - test-skip-string-works-from-mid-stream"/imm32
+    68/push  8/imm32
+    b8/copy-to-EAX  _test-input-stream/imm32
+    ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         .                 # push *(EAX+4)
+    # . . 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
+
+skip-string-in-slice:  # curr : (address byte), end : (address byte) -> new_curr/EAX
+    # . 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
+    53/push-EBX
+    # ECX = curr
+    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         1/r32/ECX   8/disp8         .                 # copy *(EBP+8) to ECX
+    # EDX = end
+    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         2/r32/EDX   0xc/disp8         .               # copy *(EBP+12) to EDX
+    # EAX = 0
+    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
+    # skip initial dquote
+    41/increment-ECX
+$skip-string-in-slice:loop:
+    # if (curr >= end) return curr
+    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
+    73/jump-if-greater-unsigned-or-equal  $skip-string-in-slice:return-curr/disp8
+    # AL = *curr
+    8a/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           0/r32/AL    .               .                 # copy byte at *ECX to AL
+$skip-string-in-slice:dquote:
+    # if (EAX == '"') break
+    3d/compare-EAX-and  0x22/imm32/double-quote
+    74/jump-if-equal  $skip-string-in-slice:break/disp8
+$skip-string-in-slice:check-for-escape:
+    # if (EAX == '\') escape next char
+    3d/compare-EAX-and  0x5c/imm32/backslash
+    75/jump-if-not-equal  $skip-string-in-slice:continue/disp8
+$skip-string-in-slice:escape:
+    41/increment-ECX
+$skip-string-in-slice:continue:
+    # ++curr
+    41/increment-ECX
+    eb/jump  $skip-string-in-slice:loop/disp8
+$skip-string-in-slice:break:
+    # skip final dquote
+    41/increment-ECX
+$skip-string-in-slice:return-curr:
+    # return curr
+    89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy ECX to EAX
+$skip-string-in-slice:end:
+    # . restore registers
+    5b/pop-to-EBX
+    5a/pop-to-EDX
+    59/pop-to-ECX
+    # . epilog
+    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+    5d/pop-to-EBP
+    c3/return
+
+test-skip-string-in-slice:
+    # . prolog
+    55/push-EBP
+    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+    # setup: (EAX..ECX) = "\"abc\" def"
+    b8/copy-to-EAX  "\"abc\" def"/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
+    # EAX = skip-string-in-slice(EAX, ECX)
+    # . . push args
+    51/push-ECX
+    50/push-EAX
+    # . . call
+    e8/call  skip-string-in-slice/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # check-ints-equal(ECX-EAX, 4, msg)  # number of chars remaining after the string literal
+    # . . push args
+    68/push  "F - test-skip-string-in-slice"/imm32
+    68/push  4/imm32
+    # . . push ECX-EAX
+    29/subtract                     3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # subtract EAX from ECX
+    51/push-ECX
+    # . . 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-skip-string-in-slice-ignores-spaces:
+    # . prolog
+    55/push-EBP
+    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+    # setup: (EAX..ECX) = "\"a b\"/yz"
+    b8/copy-to-EAX  "\"a b\"/yz"/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
+    # EAX = skip-string-in-slice(EAX, ECX)
+    # . . push args
+    51/push-ECX
+    50/push-EAX
+    # . . call
+    e8/call  skip-string-in-slice/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # check-ints-equal(ECX-EAX, 3, msg)  # number of chars remaining after the string literal
+    # . . push args
+    68/push  "F - test-skip-string-in-slice-ignores-spaces"/imm32
+    68/push  3/imm32
+    # . . push ECX-EAX
+    29/subtract                     3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # subtract EAX from ECX
+    51/push-ECX
+    # . . 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-skip-string-in-slice-ignores-escapes:
+    # . prolog
+    55/push-EBP
+    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+    # setup: (EAX..ECX) = "\"a\\\"b\"/yz"
+    b8/copy-to-EAX  "\"a\\\"b\"/yz"/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
+    # EAX = skip-string-in-slice(EAX, ECX)
+    # . . push args
+    51/push-ECX
+    50/push-EAX
+    # . . call
+    e8/call  skip-string-in-slice/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # check-ints-equal(ECX-EAX, 3, msg)  # number of chars remaining after the string literal
+    # . . push args
+    68/push  "F - test-skip-string-in-slice-ignores-escapes"/imm32
+    68/push  3/imm32
+    # . . push ECX-EAX
+    29/subtract                     3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # subtract EAX from ECX
+    51/push-ECX
+    # . . 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-skip-string-in-slice-stops-at-end:
+    # . prolog
+    55/push-EBP
+    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+    # setup: (EAX..ECX) = "\"abc"  # unbalanced dquote
+    b8/copy-to-EAX  "\"abc"/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
+    # EAX = skip-string-in-slice(EAX, ECX)
+    # . . push args
+    51/push-ECX
+    50/push-EAX
+    # . . call
+    e8/call  skip-string-in-slice/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # check-ints-equal(ECX-EAX, 0, msg)  # skipped to end of slice
+    # . . push args
+    68/push  "F - test-skip-string-in-slice-stops-at-end"/imm32
+    68/push  0/imm32
+    # . . push ECX-EAX
+    29/subtract                     3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # subtract EAX from ECX
+    51/push-ECX
+    # . . 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
+
+string-length-at-start-of-slice:  # curr : (address byte), end : (address byte) -> length/EAX
+    # . 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
+    53/push-EBX
+    # ECX = curr
+    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         1/r32/ECX   8/disp8         .                 # copy *(EBP+8) to ECX
+    # EDX = end
+    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         2/r32/EDX   0xc/disp8         .               # copy *(EBP+12) to EDX
+    # length/EAX = 0
+    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
+    # EBX = 0
+    31/xor                          3/mod/direct    3/rm32/EBX    .           .             .           3/r32/EBX   .               .                 # clear EBX
+    # skip initial dquote
+    41/increment-ECX
+$string-length-at-start-of-slice:loop:
+    # if (curr >= end) return length
+    39/compare                      3/mod/direct    1/rm32/ECX    .           .             .           2/r32/EDX   .               .                 # compare ECX with EDX
+    73/jump-if-greater-unsigned-or-equal  $string-length-at-start-of-slice:end/disp8
+    # BL = *curr
+    8a/copy-byte                    0/mod/indirect  1/rm32/ECX    .           .             .           3/r32/BL    .               .                 # copy byte at *ECX to BL
+$string-length-at-start-of-slice:dquote:
+    # if (EBX == '"') break
+    81          7/subop/compare     3/mod/direct    3/rm32/EBX    .           .             .           .           .               0x22/imm32/dquote # compare EBX
+    74/jump-if-equal  $string-length-at-start-of-slice:end/disp8
+$string-length-at-start-of-slice:check-for-escape:
+    # if (EBX == '\') escape next char
+    81          7/subop/compare     3/mod/direct    3/rm32/EBX    .           .             .           .           .               0x5c/imm32/backslash # compare EBX
+    75/jump-if-not-equal  $string-length-at-start-of-slice:continue/disp8
+$string-length-at-start-of-slice:escape:
+    # increment curr but not result
+    41/increment-ECX
+$string-length-at-start-of-slice:continue:
+    # ++result
+    40/increment-EAX
+    # ++curr
+    41/increment-ECX
+    eb/jump  $string-length-at-start-of-slice:loop/disp8
+$string-length-at-start-of-slice:end:
+    # . restore registers
+    5b/pop-to-EBX
+    5a/pop-to-EDX
+    59/pop-to-ECX
+    # . epilog
+    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+    5d/pop-to-EBP
+    c3/return
+
+test-string-length-at-start-of-slice:
+    # . prolog
+    55/push-EBP
+    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+    # setup: (EAX..ECX) = "\"abc\" def"
+    b8/copy-to-EAX  "\"abc\" def"/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
+    # EAX = string-length-at-start-of-slice(EAX, ECX)
+    # . . push args
+    51/push-ECX
+    50/push-EAX
+    # . . call
+    e8/call  string-length-at-start-of-slice/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # check-ints-equal(EAX, 3, msg)
+    # . . push args
+    68/push  "F - test-string-length-at-start-of-slice"/imm32
+    68/push  3/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
+
+test-string-length-at-start-of-slice-escaped:
+    # . prolog
+    55/push-EBP
+    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+    # setup: (EAX..ECX) = "\"ab\\c\" def"
+    b8/copy-to-EAX  "\"ab\\c\" def"/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
+    # EAX = string-length-at-start-of-slice(EAX, ECX)
+    # . . push args
+    51/push-ECX
+    50/push-EAX
+    # . . call
+    e8/call  string-length-at-start-of-slice/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # check-ints-equal(EAX, 3, msg)
+    # . . push args
+    68/push  "F - test-string-length-at-start-of-slice-escaped"/imm32
+    68/push  3/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
 
 Segment-size:
@@ -2009,6 +2646,8 @@ Slash:
 _test-slice-abc:
   22/dquote 61/a 62/b 63/c 22/dquote  # "abc"
 _test-slice-abc-end:
+  2f/slash 64/d
+_test-slice-abc-metadata-end:
 
 _test-slice-empty-string-literal:
   22/dquote 22/dquote  # ""
@@ -2031,4 +2670,14 @@ _test-slice-word-end:
   2f/slash 67/g 68/h 69/i  # /ghi
 _test-slice-word-end2:
 
+# "abc/def"/ghi
+_test-slice-literal-string:
+  22/dquote
+  61/a 62/b 63/c  # abc
+  2f/slash 64/d 65/e 66/f  # /def
+  22/dquote
+_test-slice-literal-string-end:
+  2f/slash 67/g 68/h 69/i  # /ghi
+_test-slice-literal-string-with-metadata-end:
+
 # . . vim:nowrap:textwidth=0
diff --git a/subx/apps/factorial b/subx/apps/factorial
index 16b54308..0f913091 100755
--- a/subx/apps/factorial
+++ b/subx/apps/factorial
Binary files differdiff --git a/subx/apps/factorial.subx b/subx/apps/factorial.subx
index 98efc6fa..e4b7a057 100644
--- a/subx/apps/factorial.subx
+++ b/subx/apps/factorial.subx
@@ -50,8 +50,8 @@ Entry:  # run tests if necessary, compute `factorial(5)` if not
     e8/call  run-tests/disp32
     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Num-test-failures/disp32          # copy *Num-test-failures to EAX
     eb/jump  $main:end/disp8  # where EAX will get copied to EBX
-    # - otherwise return factorial(5)
 $run-main:
+    # - otherwise return factorial(5)
     # . . push args
     68/push  5/imm32
     # . . call
diff --git a/subx/apps/handle b/subx/apps/handle
index 154e3725..c7110e42 100755
--- a/subx/apps/handle
+++ b/subx/apps/handle
Binary files differdiff --git a/subx/apps/handle.subx b/subx/apps/handle.subx
index 0ed12067..97a6c622 100644
--- a/subx/apps/handle.subx
+++ b/subx/apps/handle.subx
@@ -15,10 +15,11 @@
 # To run (from the subx directory):
 #   $ ./subx translate *.subx apps/handle.subx -o apps/handle
 #   $ ./subx run apps/handle
-# Expected result is a hard abort:
-#   ........lookup failed
-# (This file is a prototype, so the tests in this file aren't real tests. Don't
-# expect to run anything in the same process after they've completed.)
+# Expected result is a successful lookup followed by a hard abort:
+#   lookup succeeded
+#   lookup failed
+# (This file is a prototype. The 'tests' in it aren't real; failures are
+# expected.)
 
 == code
 #   instruction                     effective address                                                   register    displacement    immediate
@@ -212,25 +213,44 @@ lookup:  # h : (handle T) -> EAX : (address T)
     # EAX = handle
     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   8/disp8         .                 # copy *(EBP+8) to EAX
     # - inline {
+    # push handle->alloc_id
+    ff          6/subop/push        0/mod/indirect  0/rm32/EAX    .           .             .           .           .               .                 # push *EAX
+    # EAX = handle->address (payload)
+    8b/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         .                 # copy *(EAX+4) to EAX
     # push handle->address
-    ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           4/disp8         .                 # push *(EAX+4)
-    # EAX = handle->alloc_id
+    50/push-EAX
+    # EAX = payload->alloc_id
     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           .           .               .                 # copy *EAX to EAX
-    # if (EAX != *ESP) abort
-    39/compare                      0/mod/indirect  4/rm32/sib    4/base/ESP  4/index/none  .           0/r32/EAX   .               .                 # compare *ESP and EAX
-    75/jump-if-not-equal  $lookup:fail/disp8
-    # return ESP+4
+    # if (EAX != handle->alloc_id) abort
+    39/compare                      1/mod/*+disp8   4/rm32/sib    4/base/ESP  4/index/none  .           0/r32/EAX   4/disp8         .                 # compare *(ESP+4) and EAX
+    75/jump-if-not-equal  $lookup:abort/disp8
+    # EAX = pop handle->address
     58/pop-to-EAX
+    # discard handle->alloc_id
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # add 4
     05/add-to-EAX  4/imm32
     # - }
+    # - alternative consuming a second register {
+#?     # ECX = handle->alloc_id
+#?     8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
+#?     # EAX = handle->address (payload)
+#?     8b/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(EAX+4) to EAX
+#?     # if (ECX != *EAX) abort
+#?     39/compare                      0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # compare *EAX and ECX
+#?     75/jump-if-not-equal  $lookup:abort/disp8
+#?     # add 4 to EAX
+#?     05/add-to-EAX  4/imm32
+    # - }
     # . epilog
     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
     5d/pop-to-EBP
     c3/return
-$lookup:fail:
+
+$lookup:abort:
     # . _write(2/stderr, msg)
     # . . push args
-    68/push  "lookup failed"/imm32
+    68/push  "lookup failed\n"/imm32
     68/push  2/imm32/stderr
     # . . call
     e8/call  _write/disp32
@@ -294,6 +314,14 @@ test-lookup-success:
     # clean up
     # . *Next-alloc-id = 1
     c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .     Next-alloc-id/disp32  1/imm32           # copy to *Next-alloc-id
+    # write(2/stderr, "lookup succeeded\n")
+    # . . push args
+    68/push  "lookup succeeded\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
     # . restore registers
     5a/pop-to-EDX
     59/pop-to-ECX
diff --git a/subx/apps/hex b/subx/apps/hex
index b23b8b86..5d663c6d 100755
--- a/subx/apps/hex
+++ b/subx/apps/hex
Binary files differdiff --git a/subx/apps/pack b/subx/apps/pack
index dfc71dc4..ad55fb75 100755
--- a/subx/apps/pack
+++ b/subx/apps/pack
Binary files differ