about summary refs log tree commit diff stats
path: root/subx/apps/dquotes.subx
diff options
context:
space:
mode:
Diffstat (limited to 'subx/apps/dquotes.subx')
-rw-r--r--subx/apps/dquotes.subx573
1 files changed, 520 insertions, 53 deletions
diff --git a/subx/apps/dquotes.subx b/subx/apps/dquotes.subx
index cd376f5d..4810bcad 100644
--- a/subx/apps/dquotes.subx
+++ b/subx/apps/dquotes.subx
@@ -31,7 +31,7 @@ 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
 #?     # . test()
-#?     e8/call test-next-word-returns-string-with-escapes/disp32
+#?     e8/call  test-convert-processes-string-literals/disp32
 #?     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
 #?     eb/jump  $main:end/disp8
 
@@ -100,7 +100,7 @@ convert:  # in : (address buffered-file), out : (address buffered-file) -> <void
     #   write-stream(new-data-segment, "== data\n")
     #   while true
     #     clear-stream(line)
-    #     read-line(in, line)
+    #     read-line-buffered(in, line)
     #     if (line->write == 0) break               # end of file
     #     while true
     #       var word-slice = next-word(line)
@@ -111,7 +111,7 @@ convert:  # in : (address buffered-file), out : (address buffered-file) -> <void
     #       if slice-starts-with?(word-slice, '"')  # string literal <== what we're here for
     #         process-string-literal(word-slice, out, new-data-segment)
     #       else
-    #         write-slice(out, word-slice)
+    #         write-slice-buffered(out, word-slice)
     #       write(out, " ")
     #     write(out, "\n\n")
     #   write-stream-data(out, new-data-segment)
@@ -165,12 +165,12 @@ $convert:line-loop:
     e8/call  clear-stream/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
-    # read-line(in, line)
+    # read-line-buffered(in, line)
     # . . push args
     51/push-ECX
     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
     # . . call
-    e8/call  read-line/disp32
+    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:
@@ -224,12 +224,12 @@ $convert:string-literal:
     # continue
     eb/jump  $convert:next-word/disp8
 $convert:regular-word:
-    # write-slice(out, word-slice)
+    # write-slice-buffered(out, word-slice)
     # . . push args
     52/push-EDX
     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
     # . . call
-    e8/call  write-slice/disp32
+    e8/call  write-slice-buffered/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
     # fall through
@@ -256,6 +256,21 @@ $convert:next-line:
     # loop
     e9/jump  $convert:line-loop/disp32
 $convert:break:
+    # write-stream-data(out, new-data-segment)
+    # . . push args
+    57/push-EDI
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
+    # . . call
+    e8/call  write-stream-data/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # flush(out)
+    # . . push args
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
+    # . . call
+    e8/call  flush/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
 $convert:end:
     # . reclaim locals
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x214/imm32       # add to ESP
@@ -318,6 +333,13 @@ process-string-literal:  # string-literal : (address slice), out : (address buff
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
     # emit-string-literal-data(out-segment, string-literal)
+    # . . push args
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x8/disp8       .                 # push *(EBP+8)
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
+    # . . call
+    e8/call  emit-string-literal-data/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
     # write(out-segment, "\n")
     # . . push args
     68/push  Newline/imm32
@@ -544,7 +566,7 @@ test-convert-is-idempotent-by-default:
 #?     # }}}
     # . check-next-stream-line-equal(_test-output-stream, "", msg)
     # . . push args
-    68/push  "F - test-convert/0"/imm32
+    68/push  "F - test-convert-is-idempotent-by-default/0"/imm32
     68/push  ""/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -553,7 +575,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/1"/imm32
+    68/push  "F - test-convert-is-idempotent-by-default/1"/imm32
     68/push  ""/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -562,7 +584,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 ", msg)
     # . . push args
-    68/push  "F - test-convert/2"/imm32
+    68/push  "F - test-convert-is-idempotent-by-default/2"/imm32
     68/push  "== code "/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -571,7 +593,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/3"/imm32
+    68/push  "F - test-convert-is-idempotent-by-default/3"/imm32
     68/push  ""/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -580,7 +602,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/4"/imm32
+    68/push  "F - test-convert-is-idempotent-by-default/4"/imm32
     68/push  "1 "/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -589,7 +611,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/5"/imm32
+    68/push  "F - test-convert-is-idempotent-by-default/5"/imm32
     68/push  ""/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -598,7 +620,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/6"/imm32
+    68/push  "F - test-convert-is-idempotent-by-default/6"/imm32
     68/push  "2 3 "/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -607,7 +629,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 ", msg)
     # . . push args
-    68/push  "F - test-convert/7"/imm32
+    68/push  "F - test-convert-is-idempotent-by-default/7"/imm32
     68/push  "== data "/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -616,7 +638,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/8"/imm32
+    68/push  "F - test-convert-is-idempotent-by-default/8"/imm32
     68/push  "4 5/imm32 "/imm32
     68/push  _test-output-stream/imm32
     # . . call
@@ -722,32 +744,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
-#?     # }}}
+    # 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 ", msg)
     # . . push args
     68/push  "F - test-convert-processes-string-literals/0"/imm32
@@ -769,16 +798,16 @@ test-convert-processes-string-literals:
     # . 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  "2 3 "/imm32
+    68/push  "2 _string2/y "/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
-    # . 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
@@ -787,7 +816,7 @@ test-convert-processes-string-literals:
     # . check-next-stream-line-equal(_test-output-stream, "_string1: ", msg)
     # . . push args
     68/push  "F - test-convert-processes-string-literals/4"/imm32
-    68/push  "_string1: "/imm32
+    68/push  "_string1:"/imm32
     68/push  _test-output-stream/imm32
     # . . call
     e8/call  check-next-stream-line-equal/disp32
@@ -796,7 +825,7 @@ test-convert-processes-string-literals:
     # . 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  "1/imm32 61/a "/imm32
+    68/push  "0x00000001/imm32 61/a "/imm32
     68/push  _test-output-stream/imm32
     # . . call
     e8/call  check-next-stream-line-equal/disp32
@@ -805,7 +834,7 @@ test-convert-processes-string-literals:
     # . check-next-stream-line-equal(_test-output-stream, "_string2: ", msg)
     # . . push args
     68/push  "F - test-convert-processes-string-literals/6"/imm32
-    68/push  "_string2: "/imm32
+    68/push  "_string2:"/imm32
     68/push  _test-output-stream/imm32
     # . . call
     e8/call  check-next-stream-line-equal/disp32
@@ -814,7 +843,7 @@ test-convert-processes-string-literals:
     # . 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  "2/imm32 62/b 63/c "/imm32
+    68/push  "0x00000002/imm32 62/b 63/c "/imm32
     68/push  _test-output-stream/imm32
     # . . call
     e8/call  check-next-stream-line-equal/disp32
@@ -827,13 +856,420 @@ 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")
+    #   curr = word->start
+    #   ++curr  # skip '"'
+    #   while true
+    #     if (curr >= word->end) break
+    #     c = *curr
+    #     if (c == '"') break
+    #     append-byte-hex(out, c)
+    #     if c is alphanumeric:
+    #       write(out, "/")
+    #       append-byte(out, c)
+    #     write(out, " ")
+    #     ++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 = word
+    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
+$emit-string-literal-data:emit-length:
+    # TODO: handle metadata here
+    # 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
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
+    # . . call
+    e8/call  print-int32/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # . write(out, "/imm32 ")
+    # . . push args
+    68/push  "/imm32 "/imm32
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+$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
+    31/xor                          3/mod/direct    1/rm32/ECX    .           .             .           1/r32/ECX   .               .                 # clear ECX
+$emit-string-literal-data:loop:
+    # 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
+    # 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
+    # append-byte-hex(out, CL)
+    # . . push args
+    51/push-ECX
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
+    # . . call
+    e8/call  append-byte-hex/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # if (is-alphanumeric?(*curr)) print(out, "/#{*curr}")
+    # . EAX = is-alphanumeric?(CL)
+    # . . push args
+    51/push-ECX
+    # . . call
+    e8/call  is-alphanumeric?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
+    # . if (EAX == 0) goto char-done
+    3d/compare-EAX-and  0/imm32
+    74/jump-if-equal  $emit-string-literal-data:char-done/disp8
+    # . write(out, "/")
+    # . . push args
+    68/push  Slash/imm32
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
+    # . . call
+    e8/call  write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # . append-byte(out, *curr)
+    # . . push args
+    51/push-ECX
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
+    # . . call
+    e8/call  append-byte/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+$emit-string-literal-data:char-done:
+    # write(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/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # ++curr
+    42/increment-EDX
+    eb/jump $emit-string-literal-data:loop/disp8
 $emit-string-literal-data:end:
-    # . reclaim locals
     # . 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
+
+is-alphanumeric?:  # c : int -> EAX : boolean
+    # . prolog
+    55/push-EBP
+    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+    # EAX = c
+    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   8/disp8         .                 # copy *(EBP+8) to EAX
+    # if (EAX < '0') return false
+    3d/compare-EAX-with  0x30/imm32/0
+    7c/jump-if-lesser  $is-alphanumeric?:false/disp8
+    # if (EAX <= '9') return true
+    3d/compare-EAX-with  0x39/imm32/9
+    7e/jump-if-lesser-or-equal  $is-alphanumeric?:true/disp8
+    # if (EAX < 'A') return false
+    3d/compare-EAX-with  0x41/imm32/A
+    7c/jump-if-lesser  $is-alphanumeric?:false/disp8
+    # if (EAX <= 'Z') return true
+    3d/compare-EAX-with  0x5a/imm32/Z
+    7e/jump-if-lesser-or-equal  $is-alphanumeric?:true/disp8
+    # if (EAX < 'a') return false
+    3d/compare-EAX-with  0x61/imm32/a
+    7c/jump-if-lesser  $is-alphanumeric?:false/disp8
+    # if (EAX <= 'z') return true
+    3d/compare-EAX-with  0x7a/imm32/z
+    7e/jump-if-lesser-or-equal  $is-alphanumeric?:true/disp8
+    # return false
+$is-alphanumeric?:false:
+    b8/copy-to-EAX  0/imm32/false
+    eb/jump  $is-alphanumeric?:end/disp8
+$is-alphanumeric?:true:
+    b8/copy-to-EAX  1/imm32/true
+$is-alphanumeric?:end:
+    # . epilog
+    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+    5d/pop-to-EBP
+    c3/return
+
+test-emit-string-literal-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
+    # var slice/ECX = '"abc"'
+    68/push  _test-slice-abc-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)
+    # . . push args
+    51/push-ECX
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  emit-string-literal-data/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/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, "3/imm32 61/a 62/b 63/c ", msg)
+    # . . push args
+    68/push  "F - test-emit-string-literal-data"/imm32
+    68/push  "0x00000003/imm32 61/a 62/b 63/c "/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-string-literal-data-empty:
+    # . 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
+    # var slice/ECX = '""'
+    68/push  _test-slice-empty-string-literal-end/imm32
+    68/push  _test-slice-empty-string-literal/imm32
+    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
+    # emit-string-literal-data(_test-output-stream, slice)
+    # . . push args
+    51/push-ECX
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  emit-string-literal-data/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/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, "0/imm32 ", msg)
+    # . . push args
+    68/push  "F - test-emit-string-literal-data-empty"/imm32
+    68/push  "0x00000000/imm32 "/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
+
+# just to keep things simple
+test-emit-string-literal-data-no-metadata-for-non-alphanumerics:
+    # . 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
+    # var slice/ECX = '"a b"'
+    68/push  _test-slice-a-space-b-end/imm32
+    68/push  _test-slice-a-space-b/imm32
+    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
+    # emit-string-literal-data(_test-output-stream, slice)
+    # . . push args
+    51/push-ECX
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  emit-string-literal-data/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/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, "3/imm32 61/a 20 62/b ", msg)  # ideally we'd like to say '20/space' but that requires managing names for codepoints
+    # . . push args
+    68/push  "F - test-emit-string-literal-data-no-metadata-for-non-alphanumerics"/imm32
+    68/push  "0x00000003/imm32 61/a 20 62/b "/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-string-literal-data-handles-escape-sequences:
+    # . 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
+    # var slice/ECX = '"a\"b"'
+    68/push  _test-slice-a-dquote-b-end/imm32
+    68/push  _test-slice-a-dquote-b/imm32
+    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
+    # emit-string-literal-data(_test-output-stream, slice)
+    # . . push args
+    51/push-ECX
+    68/push  _test-output-stream/imm32
+    # . . call
+    e8/call  emit-string-literal-data/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/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, "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  _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
@@ -854,7 +1290,7 @@ emit-metadata:  # out : (address buffered-file), word : (address slice)
     # while true:
     #   if ECX == word->end: return
     #   if *(ECX++) == '/': break
-    # write-slice(out, {ECX, word->end})
+    # 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
@@ -884,7 +1320,7 @@ $skip-datum-loop:
     75/jump-if-not-equal  $skip-datum-loop/disp8
     # end
 
-    # write-slice(out, &{start, end})
+    # write-slice-buffered(out, &{start, end})
     # . push end
     52/push-EDX
     # . push start
@@ -895,7 +1331,7 @@ $skip-datum-loop:
     # . push out
     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
 
-    e8/call  write-slice/disp32
+    e8/call  write-slice-buffered/disp32
     # . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           0x10/imm32      .                 # add 16 to ESP
 
@@ -1123,6 +1559,7 @@ $next-word:comment:
     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  # skip rest of line
+    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
@@ -1556,6 +1993,36 @@ Heap:
   # limit
   0/imm32
 
+# length-prefixed string containing just a single space
+Space:
+    # size
+    1/imm32
+    # data
+    20/space
+
+# length-prefixed string containing just a single slash
+Slash:
+    # size
+    1/imm32
+    # data
+    2f/slash
+
+_test-slice-abc:
+  22/dquote 61/a 62/b 63/c 22/dquote  # "abc"
+_test-slice-abc-end:
+
+_test-slice-empty-string-literal:
+  22/dquote 22/dquote  # ""
+_test-slice-empty-string-literal-end:
+
+_test-slice-a-space-b:
+  22/dquote 61/a 20/space 62/b 22/dquote  # "a b"
+_test-slice-a-space-b-end:
+
+_test-slice-a-dquote-b:
+  22/dquote 61/a 5c/backslash 22/dquote 62/b 22/dquote  # "a\"b"
+_test-slice-a-dquote-b-end:
+
 # abc/def/ghi
 _test-slice-word:
   61/a 62/b 63/c  # abc