about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rwxr-xr-xsubx/apps/assortbin24109 -> 24100 bytes
-rw-r--r--subx/apps/assort.subx279
-rwxr-xr-xsubx/apps/packbin38948 -> 39782 bytes
-rw-r--r--subx/apps/subx-common.subx274
-rwxr-xr-xsubx/apps/surveybin19668 -> 21901 bytes
-rw-r--r--subx/apps/survey.subx110
6 files changed, 278 insertions, 385 deletions
diff --git a/subx/apps/assort b/subx/apps/assort
index c6a8840c..e9228e59 100755
--- a/subx/apps/assort
+++ b/subx/apps/assort
Binary files differdiff --git a/subx/apps/assort.subx b/subx/apps/assort.subx
index 4a23fdbd..14956d82 100644
--- a/subx/apps/assort.subx
+++ b/subx/apps/assort.subx
@@ -453,7 +453,7 @@ read-segments:  # in : (address buffered-file), table : (address stream {string,
     #       continue
     #     if slice-equal?(word-slice, "==")
     #       var segment-name = next-word(line)
-    #       curr-segment = get-or-insert-segment(table, segment-name, Segment-size)
+    #       curr-segment = get-or-insert-stream(table, segment-name, Segment-size)
     #       if curr-segment->write == 0
     #         rewind-stream(line)
     #         write-stream(curr-segment, line)
@@ -611,7 +611,7 @@ $read-segments:check-for-segment-header:
 #?     # }}}
     # if slice-equal?(word-slice, "==")
     #   segment-name = next-word(line)
-    #   curr-segment = get-or-insert(table, segment-name)
+    #   curr-segment = get-or-insert-stream(table, segment-name)
     #   if (curr-segment->write > 0) continue
     # . EAX = slice-equal?(word-slice, "==")
     # . . push args
@@ -674,13 +674,13 @@ $read-segments:check-for-segment-header:
 #?     # . . discard args
 #?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 #?     # }}}
-    # . EAX = get-or-insert-segment(table, segment-name, Segment-size)
+    # . EAX = get-or-insert-stream(table, segment-name, Segment-size)
     # . . push args
     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Segment-size/disp32               # push *Segment-size
     52/push-EDX
     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
     # . . call
-    e8/call  get-or-insert-segment/disp32
+    e8/call  get-or-insert-stream/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
     # . curr-segment = EAX
@@ -786,269 +786,6 @@ $write-segments:end:
 
 ## helpers
 
-# TODO: pass in an allocation descriptor
-get-or-insert-segment:  # table : (address stream {string, (address stream byte)}), s : (address slice), n : int -> EAX : (address stream)
-    # pseudocode:
-    #   curr = table->data
-    #   max = &table->data[table->write]
-    #   while curr < max
-    #     if slice-equal?(s, *curr)
-    #       return *(curr+4)
-    #     curr += 8
-    #   if table->write < table->length
-    #     *max = slice-to-string(Heap, s)
-    #     result = new-stream(Heap, n, 1)
-    #     *(max+4) = result
-    #     table->write += 8
-    #     return result
-    #   return 0
-    #
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    51/push-ECX
-    52/push-EDX
-    56/push-ESI
-    # ESI = table
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
-    # curr/ECX = table->data
-    8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   0xc/disp8       .                 # copy ESI+12 to ECX
-    # max/EDX = table->data + table->write
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
-    8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ECX  2/index/EDX   .           2/r32/EDX   .               .                 # copy ECX+EDX to EDX
-$get-or-insert-segment:search-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  $get-or-insert-segment:not-found/disp8
-    # if (slice-equal?(s, *curr)) return *(curr+4)
-    # . EAX = slice-equal?(s, *curr)
-    # . . push args
-    ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  slice-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX != 0) return EAX = *(curr+4)
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $get-or-insert-segment:mismatch/disp8
-    8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ECX+4) to EAX
-    eb/jump  $get-or-insert-segment:end/disp8
-$get-or-insert-segment:mismatch:
-    # curr += 8
-    81          0/subop/add         3/mod/direct    1/rm32/ECX    .           .             .           .           .               8/imm32           # add to ECX
-    # loop
-    eb/jump  $get-or-insert-segment:search-loop/disp8
-$get-or-insert-segment:not-found:
-    # result/EAX = 0
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    # if (table->write >= table->length) abort
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # copy *ESI to ECX
-    3b/compare                      1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   8/disp8         .                 # compare ECX with *(ESI+8)
-    7d/jump-if-greater-or-equal  $get-or-insert-segment:abort/disp8
-    # *max = slice-to-string(Heap, s)
-    # . EAX = slice-to-string(Heap, s)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    68/push  Heap/imm32
-    # . . call
-    e8/call  slice-to-string/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . *max = EAX
-    89/copy                         0/mod/indirect  2/rm32/EDX    .           .             .           0/r32/EAX   .               .                 # copy EAX to *EDX
-    # result/EAX = new-stream(Heap, n, 1)
-    # . . push args
-    68/push  1/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
-    68/push  Heap/imm32
-    # . . call
-    e8/call  new-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # *(max+4) = result
-    89/copy                         1/mod/*+disp8   2/rm32/EDX    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(EDX+4)
-    # table->write += 8
-    81          0/subop/add         0/mod/indirect  6/rm32/ESI    .           .             .           .           .               8/imm32           # add to *ESI
-$get-or-insert-segment:end:
-    # . restore registers
-    5e/pop-to-ESI
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-$get-or-insert-segment:abort:
-    # . _write(2/stderr, error)
-    # . . push args
-    68/push  "get-or-insert-segment: too many segments"/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . syscall(exit, 1)
-    bb/copy-to-EBX  1/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-    # never gets here
-
-test-get-or-insert-segment:
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # var table/ECX : (address stream byte) = stream(2 * 8)
-    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # subtract from ESP
-    68/push  0x10/imm32/length
-    68/push  0/imm32/read
-    68/push  0/imm32/write
-    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
-    # EDX : (address slice) = "code"
-    68/push  _test-code-segment-end/imm32/end
-    68/push  _test-code-segment/imm32/start
-    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
-$test-get-or-insert-segment:first-call:
-    # - start with an empty table, insert one segment, verify that it was inserted
-    # segment/EAX = get-or-insert-segment(table, "code" slice, 10)
-    # . . push args
-    68/push  0xa/imm32/segment-length
-    52/push-EDX
-    51/push-ECX
-    # . . call
-    e8/call  get-or-insert-segment/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # save segment
-    50/push-EAX
-    # if (segment != 0) goto next check
-    3d/compare-EAX-and  0/imm32
-    75/jump-if-not-equal  $test-get-or-insert-segment:check1/disp8
-    # fail test
-    # . _write(2/stderr, msg)
-    # . . push args
-    68/push  "F - test-get-or-insert-segment/0\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
-    # . increment Num-test-failures
-    ff          0/subop/increment   0/mod/indirect  5/rm32/.disp32            .             .           .           Num-test-failures/disp32          # increment *Num-test-failures
-    e9/jump  $test-get-or-insert-segment:end/disp32
-$test-get-or-insert-segment:check1:
-    # check-ints-equal(segment->length, 10, msg)
-    # . . push args
-    68/push  "F - test-get-or-insert-segment/1"/imm32
-    68/push  0xa/imm32/segment-length
-    ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           8/disp8         .                 # push *(EAX+8)
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-$test-get-or-insert-segment:check2:
-    # check-ints-equal(table->write, row-size = 8, msg)
-    # . . push args
-    68/push  "F - test-get-or-insert-segment/2"/imm32
-    68/push  8/imm32/row-size
-    ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # 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
-    # EAX = string-equal?(*table->data, "code")
-    # . . push args
-    68/push  "code"/imm32
-    ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           0xc/disp8       .                 # push *(ECX+12)
-    # . . call
-    e8/call  string-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # check-ints-equal(EAX, 1, msg)
-    # . . push args
-    68/push  "F - test-get-or-insert-segment/3"/imm32
-    68/push  1/imm32
-    50/push-EAX
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-$test-get-or-insert-segment:check3:
-    # stream/EAX = *(table->data+4)
-    8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   0x10/disp8      .                 # copy *(ECX+16) to EAX
-    # check-ints-equal(stream->length, 10, msg)
-    # . . push args
-    68/push  "F - test-get-or-insert-segment/4"/imm32
-    68/push  0xa/imm32/segment-size
-    ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           8/disp8         .                 # push *(EAX+8)
-    # . . call
-    e8/call  check-ints-equal/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-$test-get-or-insert-segment:second-call:
-    # - insert the same segment name again, verify that it was reused
-    # segment2/EAX = get-or-insert-segment(table, "code" slice, 8)
-    # . . push args
-    68/push  8/imm32/segment-length
-    52/push-EDX
-    51/push-ECX
-    # . . call
-    e8/call  get-or-insert-segment/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # restore old segment1
-    5a/pop-to-EDX
-    # check-ints-equal(segment2/EAX, segment1/EDX, msg)
-    # . . push args
-    68/push  "F - test-get-or-insert-segment/5"/imm32
-    52/push-EDX
-    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
-    # no change to table size
-    # . check-ints-equal(table->write, row-size = 8, msg)
-    # . . push args
-    68/push  "F - test-get-or-insert-segment/6"/imm32
-    68/push  8/imm32/row-size
-    ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # 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
-$test-get-or-insert-segment:third-call:
-    # - insert a new segment name, verify that it was inserted
-    # EDX : (address slice) = "data"
-    c7          0/subop/copy        0/mod/indirect  2/rm32/EDX    .           .             .           .           .               _test-data-segment/imm32  # copy to *EDX
-    c7          0/subop/copy        1/mod/*+disp8   2/rm32/EDX    .           .             .           .           4/disp8         _test-data-segment-end/imm32  # copy to *(EDX+4)
-    # segment2/EAX = get-or-insert-segment(table, "data" slice, 8)
-    # . . push args
-    68/push  8/imm32/segment-length
-    52/push-EDX
-    51/push-ECX
-    # . . call
-    e8/call  get-or-insert-segment/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # table gets a new row
-    # . check-ints-equal(table->write, 2 rows = 16, msg)
-    # . . push args
-    68/push  "F - test-get-or-insert-segment/7"/imm32
-    68/push  0x10/imm32/two-rows
-    ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # 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
-$test-get-or-insert-segment:end:
-    # . 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)
@@ -1297,14 +1034,6 @@ test-next-word-returns-empty-string-on-eof:
 
 == data
 
-_test-code-segment:
-  63/c 6f/o 64/d 65/e
-_test-code-segment-end:
-
-_test-data-segment:
-  64/d 61/a 74/t 61/a
-_test-data-segment-end:
-
 Segment-size:
   0x1000/imm32/4KB
 
diff --git a/subx/apps/pack b/subx/apps/pack
index 26497e17..f7363a10 100755
--- a/subx/apps/pack
+++ b/subx/apps/pack
Binary files differdiff --git a/subx/apps/subx-common.subx b/subx/apps/subx-common.subx
index cba1e9cc..86cf10fe 100644
--- a/subx/apps/subx-common.subx
+++ b/subx/apps/subx-common.subx
@@ -5,6 +5,272 @@
 # . op          subop               mod             rm32          base        index         scale       r32
 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
 
+# scan a 'table' of (string, stream) rows for a row matching 's'
+# if it doesn't exist, add a new row with a stream of size 'n'
+# if there isn't room, abort
+# TODO: pass in an allocation descriptor
+get-or-insert-stream:  # table : (address stream {string, (address stream byte)}), s : (address slice), n : int -> EAX : (address stream)
+    # pseudocode:
+    #   curr = table->data
+    #   max = &table->data[table->write]
+    #   while curr < max
+    #     if slice-equal?(s, *curr)
+    #       return *(curr+4)
+    #     curr += 8
+    #   if table->write < table->length
+    #     *max = slice-to-string(Heap, s)
+    #     result = new-stream(Heap, n, 1)
+    #     *(max+4) = result
+    #     table->write += 8
+    #     return result
+    #   return 0
+    #
+    # . prolog
+    55/push-EBP
+    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+    # . save registers
+    51/push-ECX
+    52/push-EDX
+    56/push-ESI
+    # ESI = table
+    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
+    # curr/ECX = table->data
+    8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   0xc/disp8       .                 # copy ESI+12 to ECX
+    # max/EDX = table->data + table->write
+    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
+    8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ECX  2/index/EDX   .           2/r32/EDX   .               .                 # copy ECX+EDX to EDX
+$get-or-insert-stream:search-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  $get-or-insert-stream:not-found/disp8
+    # if (slice-equal?(s, *curr)) return *(curr+4)
+    # . EAX = slice-equal?(s, *curr)
+    # . . push args
+    ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
+    # . . call
+    e8/call  slice-equal?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # . if (EAX != 0) return EAX = *(curr+4)
+    3d/compare-EAX-and  0/imm32
+    74/jump-if-equal  $get-or-insert-stream:mismatch/disp8
+    8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ECX+4) to EAX
+    eb/jump  $get-or-insert-stream:end/disp8
+$get-or-insert-stream:mismatch:
+    # curr += 8
+    81          0/subop/add         3/mod/direct    1/rm32/ECX    .           .             .           .           .               8/imm32           # add to ECX
+    # loop
+    eb/jump  $get-or-insert-stream:search-loop/disp8
+$get-or-insert-stream:not-found:
+    # result/EAX = 0
+    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
+    # if (table->write >= table->length) abort
+    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # copy *ESI to ECX
+    3b/compare                      1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   8/disp8         .                 # compare ECX with *(ESI+8)
+    7d/jump-if-greater-or-equal  $get-or-insert-stream:abort/disp8
+    # *max = slice-to-string(Heap, s)
+    # . EAX = slice-to-string(Heap, s)
+    # . . push args
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
+    68/push  Heap/imm32
+    # . . call
+    e8/call  slice-to-string/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # . *max = EAX
+    89/copy                         0/mod/indirect  2/rm32/EDX    .           .             .           0/r32/EAX   .               .                 # copy EAX to *EDX
+    # result/EAX = new-stream(Heap, n, 1)
+    # . . push args
+    68/push  1/imm32
+    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
+    68/push  Heap/imm32
+    # . . call
+    e8/call  new-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+    # *(max+4) = result
+    89/copy                         1/mod/*+disp8   2/rm32/EDX    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(EDX+4)
+    # table->write += 8
+    81          0/subop/add         0/mod/indirect  6/rm32/ESI    .           .             .           .           .               8/imm32           # add to *ESI
+$get-or-insert-stream:end:
+    # . restore registers
+    5e/pop-to-ESI
+    5a/pop-to-EDX
+    59/pop-to-ECX
+    # . epilog
+    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+    5d/pop-to-EBP
+    c3/return
+
+$get-or-insert-stream:abort:
+    # . _write(2/stderr, error)
+    # . . push args
+    68/push  "get-or-insert-stream: too many segments"/imm32
+    68/push  2/imm32/stderr
+    # . . call
+    e8/call  _write/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # . syscall(exit, 1)
+    bb/copy-to-EBX  1/imm32
+    b8/copy-to-EAX  1/imm32/exit
+    cd/syscall  0x80/imm8
+    # never gets here
+
+test-get-or-insert-stream:
+    # . prolog
+    55/push-EBP
+    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
+    # var table/ECX : (address stream byte) = stream(2 * 8)
+    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # subtract from ESP
+    68/push  0x10/imm32/length
+    68/push  0/imm32/read
+    68/push  0/imm32/write
+    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
+    # EDX : (address slice) = "code"
+    68/push  _test-code-segment-end/imm32/end
+    68/push  _test-code-segment/imm32/start
+    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
+$test-get-or-insert-stream:first-call:
+    # - start with an empty table, insert one segment, verify that it was inserted
+    # segment/EAX = get-or-insert-stream(table, "code" slice, 10)
+    # . . push args
+    68/push  0xa/imm32/segment-length
+    52/push-EDX
+    51/push-ECX
+    # . . call
+    e8/call  get-or-insert-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+    # save segment
+    50/push-EAX
+    # if (segment != 0) goto next check
+    3d/compare-EAX-and  0/imm32
+    75/jump-if-not-equal  $test-get-or-insert-stream:check1/disp8
+    # fail test
+    # . _write(2/stderr, msg)
+    # . . push args
+    68/push  "F - test-get-or-insert-stream/0\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
+    # . increment Num-test-failures
+    ff          0/subop/increment   0/mod/indirect  5/rm32/.disp32            .             .           .           Num-test-failures/disp32          # increment *Num-test-failures
+    e9/jump  $test-get-or-insert-stream:end/disp32
+$test-get-or-insert-stream:check1:
+    # check-ints-equal(segment->length, 10, msg)
+    # . . push args
+    68/push  "F - test-get-or-insert-stream/1"/imm32
+    68/push  0xa/imm32/segment-length
+    ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           8/disp8         .                 # push *(EAX+8)
+    # . . call
+    e8/call  check-ints-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+$test-get-or-insert-stream:check2:
+    # check-ints-equal(table->write, row-size = 8, msg)
+    # . . push args
+    68/push  "F - test-get-or-insert-stream/2"/imm32
+    68/push  8/imm32/row-size
+    ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # 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
+    # EAX = string-equal?(*table->data, "code")
+    # . . push args
+    68/push  "code"/imm32
+    ff          6/subop/push        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           0xc/disp8       .                 # push *(ECX+12)
+    # . . call
+    e8/call  string-equal?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
+    # check-ints-equal(EAX, 1, msg)
+    # . . push args
+    68/push  "F - test-get-or-insert-stream/3"/imm32
+    68/push  1/imm32
+    50/push-EAX
+    # . . call
+    e8/call  check-ints-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+$test-get-or-insert-stream:check3:
+    # stream/EAX = *(table->data+4)
+    8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   0x10/disp8      .                 # copy *(ECX+16) to EAX
+    # check-ints-equal(stream->length, 10, msg)
+    # . . push args
+    68/push  "F - test-get-or-insert-stream/4"/imm32
+    68/push  0xa/imm32/segment-size
+    ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           8/disp8         .                 # push *(EAX+8)
+    # . . call
+    e8/call  check-ints-equal/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+$test-get-or-insert-stream:second-call:
+    # - insert the same segment name again, verify that it was reused
+    # segment2/EAX = get-or-insert-stream(table, "code" slice, 8)
+    # . . push args
+    68/push  8/imm32/segment-length
+    52/push-EDX
+    51/push-ECX
+    # . . call
+    e8/call  get-or-insert-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+    # restore old segment1
+    5a/pop-to-EDX
+    # check-ints-equal(segment2/EAX, segment1/EDX, msg)
+    # . . push args
+    68/push  "F - test-get-or-insert-stream/5"/imm32
+    52/push-EDX
+    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
+    # no change to table size
+    # . check-ints-equal(table->write, row-size = 8, msg)
+    # . . push args
+    68/push  "F - test-get-or-insert-stream/6"/imm32
+    68/push  8/imm32/row-size
+    ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # 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
+$test-get-or-insert-stream:third-call:
+    # - insert a new segment name, verify that it was inserted
+    # EDX : (address slice) = "data"
+    c7          0/subop/copy        0/mod/indirect  2/rm32/EDX    .           .             .           .           .               _test-data-segment/imm32  # copy to *EDX
+    c7          0/subop/copy        1/mod/*+disp8   2/rm32/EDX    .           .             .           .           4/disp8         _test-data-segment-end/imm32  # copy to *(EDX+4)
+    # segment2/EAX = get-or-insert-stream(table, "data" slice, 8)
+    # . . push args
+    68/push  8/imm32/segment-length
+    52/push-EDX
+    51/push-ECX
+    # . . call
+    e8/call  get-or-insert-stream/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
+    # table gets a new row
+    # . check-ints-equal(table->write, 2 rows = 16, msg)
+    # . . push args
+    68/push  "F - test-get-or-insert-stream/7"/imm32
+    68/push  0x10/imm32/two-rows
+    ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # 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
+$test-get-or-insert-stream:end:
+    # . epilog
+    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
+    5d/pop-to-EBP
+    c3/return
+
 # write an entire stream's contents to a buffered-file
 # ways to do this:
 #   - construct a 'maximal slice' and pass it to write-slice-buffered
@@ -195,4 +461,12 @@ _test-output-buffered-file:
     # data
     00 00 00 00 00 00  # 6 bytes
 
+_test-code-segment:
+  63/c 6f/o 64/d 65/e
+_test-code-segment-end:
+
+_test-data-segment:
+  64/d 61/a 74/t 61/a
+_test-data-segment-end:
+
 # . . vim:nowrap:textwidth=0
diff --git a/subx/apps/survey b/subx/apps/survey
index 1d4c9535..fb72c78f 100755
--- a/subx/apps/survey
+++ b/subx/apps/survey
Binary files differdiff --git a/subx/apps/survey.subx b/subx/apps/survey.subx
index 43e52150..e0f04e22 100644
--- a/subx/apps/survey.subx
+++ b/subx/apps/survey.subx
@@ -115,116 +115,6 @@ $convert:end:
 
 ## helpers
 
-# TODO: pass in an allocation descriptor
-get-or-insert-segment:  # table : (address stream row), s : (address slice), n : int -> EAX : (address stream)
-    # pseudocode:
-    #   curr = table->data
-    #   max = &table->data[table->write]
-    #   while curr < max
-    #     if slice-equal?(s, *curr)
-    #       return *(curr+4)
-    #     curr += 8
-    #   if table->write < table->length
-    #     *max = slice-to-string(Heap, s)
-    #     result = new-stream(Heap, n, 1)
-    #     *(max+4) = result
-    #     table->write += 8
-    #     return result
-    #   return 0
-    #
-    # . prolog
-    55/push-EBP
-    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
-    # . save registers
-    51/push-ECX
-    52/push-EDX
-    56/push-ESI
-    # ESI = table
-    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
-    # curr/ECX = table->data
-    8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   0xc/disp8       .                 # copy ESI+12 to ECX
-    # max/EDX = table->data + table->write
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           2/r32/EDX   .               .                 # copy *ESI to EDX
-    8d/copy-address                 0/mod/indirect  4/rm32/sib    1/base/ECX  2/index/EDX   .           2/r32/EDX   .               .                 # copy ECX+EDX to EDX
-$get-or-insert-segment:search-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  $get-or-insert-segment:not-found/disp8
-    # if (slice-equal?(s, *curr)) return *(curr+4)
-    # . EAX = slice-equal?(s, *curr)
-    # . . push args
-    ff          6/subop/push        0/mod/indirect  1/rm32/ECX    .           .             .           .           .               .                 # push *ECX
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    # . . call
-    e8/call  slice-equal?/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . if (EAX != 0) return EAX = *(curr+4)
-    3d/compare-EAX-and  0/imm32
-    74/jump-if-equal  $get-or-insert-segment:mismatch/disp8
-    8b/copy                         1/mod/*+disp8   1/rm32/ECX    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ECX+4) to EAX
-    eb/jump  $get-or-insert-segment:end/disp8
-$get-or-insert-segment:mismatch:
-    # curr += 8
-    81          0/subop/add         3/mod/direct    1/rm32/ECX    .           .             .           .           .               8/imm32           # add to ECX
-    # loop
-    eb/jump  $get-or-insert-segment:search-loop/disp8
-$get-or-insert-segment:not-found:
-    # result/EAX = 0
-    31/xor                          3/mod/direct    0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # clear EAX
-    # if (table->write >= table->length) abort
-    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # copy *ESI to ECX
-    3b/compare                      1/mod/*+disp8   6/rm32/ESI    .           .             .           1/r32/ECX   8/disp8         .                 # compare ECX with *(ESI+8)
-    7d/jump-if-greater-or-equal  $get-or-insert-segment:abort/disp8
-    # *max = slice-to-string(Heap, s)
-    # . EAX = slice-to-string(Heap, s)
-    # . . push args
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
-    68/push  Heap/imm32
-    # . . call
-    e8/call  slice-to-string/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . *max = EAX
-    89/copy                         0/mod/indirect  2/rm32/EDX    .           .             .           0/r32/EAX   .               .                 # copy EAX to *EDX
-    # result/EAX = new-stream(Heap, n, 1)
-    # . . push args
-    68/push  1/imm32
-    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
-    68/push  Heap/imm32
-    # . . call
-    e8/call  new-stream/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
-    # *(max+4) = result
-    89/copy                         1/mod/*+disp8   2/rm32/EDX    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(EDX+4)
-    # table->write += 8
-    81          0/subop/add         0/mod/indirect  6/rm32/ESI    .           .             .           .           .               8/imm32           # add to *ESI
-$get-or-insert-segment:end:
-    # . restore registers
-    5e/pop-to-ESI
-    5a/pop-to-EDX
-    59/pop-to-ECX
-    # . epilog
-    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
-    5d/pop-to-EBP
-    c3/return
-
-$get-or-insert-segment:abort:
-    # . _write(2/stderr, error)
-    # . . push args
-    68/push  "get-or-insert-segment: too many segments"/imm32
-    68/push  2/imm32/stderr
-    # . . call
-    e8/call  _write/disp32
-    # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
-    # . syscall(exit, 1)
-    bb/copy-to-EBX  1/imm32
-    b8/copy-to-EAX  1/imm32/exit
-    cd/syscall  0x80/imm8
-    # never gets here
-
 == data
 
 Segment-size: