about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2016-04-23 17:02:16 -0700
committerKartik K. Agaram <vc@akkartik.com>2016-04-23 17:15:16 -0700
commit7bf9212fd47e505711cc0873d30456e3d793034d (patch)
treee6c84ac4666269b5e366ceff78dd040ac39343bc
parent991d76f32817f5515fb8db25a7d5ca2912cfe4ac (diff)
downloadmu-7bf9212fd47e505711cc0873d30456e3d793034d.tar.gz
2861 - 'maybe-convert' no longer returns address
-rw-r--r--031container.cc11
-rw-r--r--033exclusive_container.cc64
-rw-r--r--042name.cc6
-rw-r--r--084console.mu6
-rwxr-xr-xbuild_and_test_until2
-rw-r--r--edit/002-typing.mu26
-rw-r--r--edit/003-shortcuts.mu34
-rw-r--r--edit/004-programming-environment.mu26
-rw-r--r--edit/005-sandbox.mu6
-rw-r--r--edit/006-sandbox-edit.mu4
-rw-r--r--edit/007-sandbox-delete.mu2
-rw-r--r--edit/008-sandbox-test.mu4
-rw-r--r--edit/009-sandbox-trace.mu4
-rw-r--r--edit/011-editor-undo.mu144
-rw-r--r--sandbox/002-typing.mu26
-rw-r--r--sandbox/003-shortcuts.mu34
-rw-r--r--sandbox/004-programming-environment.mu22
-rw-r--r--sandbox/005-sandbox.mu6
-rw-r--r--sandbox/006-sandbox-edit.mu4
-rw-r--r--sandbox/007-sandbox-delete.mu2
-rw-r--r--sandbox/008-sandbox-test.mu4
-rw-r--r--sandbox/009-sandbox-trace.mu4
-rw-r--r--sandbox/011-editor-undo.mu144
23 files changed, 322 insertions, 263 deletions
diff --git a/031container.cc b/031container.cc
index 14a90501..32b38bc7 100644
--- a/031container.cc
+++ b/031container.cc
@@ -660,6 +660,14 @@ def main [
 ]
 +error: main: too few ingredients in '1:point-number <- merge 3, 4'
 
+:(scenario merge_check_reflexive)
+% Hide_errors = true;
+def main [
+  1:point <- merge 3, 4
+  2:point <- merge 1:point
+]
+$error: 0
+
 //: Since a container can be merged in several ways, we need to be able to
 //: backtrack through different possibilities. Later we'll allow creating
 //: exclusive containers which contain just one of rather than all of their
@@ -721,6 +729,9 @@ void check_merge_call(const vector<reagent>& ingredients, const reagent& product
     type_info& container_info = get(Type, container.type->value);
     switch (container_info.kind) {
       case CONTAINER: {
+        // degenerate case: merge with the same type always succeeds
+        if (state.data.top().container_element_index == 0 && types_coercible(container, inst.ingredients.at(ingredient_index)))
+          return;
         reagent expected_ingredient = element_type(container, state.data.top().container_element_index);
         trace(9999, "transform") << "checking container " << to_string(container) << " || " << to_string(expected_ingredient) << " vs ingredient " << ingredient_index << end();
         // if the current element is the ingredient we expect, move on to the next element/ingredient
diff --git a/033exclusive_container.cc b/033exclusive_container.cc
index 2a006a99..4a7a5e40 100644
--- a/033exclusive_container.cc
+++ b/033exclusive_container.cc
@@ -60,18 +60,24 @@ def main [
   12:number <- copy 1
   13:number <- copy 35
   14:number <- copy 36
-  20:address:point <- maybe-convert 12:number-or-point/unsafe, 1:variant
+  20:point, 22:boolean <- maybe-convert 12:number-or-point/unsafe, 1:variant
 ]
-+mem: storing 13 in location 20
+# point
++mem: storing 35 in location 20
++mem: storing 36 in location 21
+# boolean
++mem: storing 1 in location 22
 
 :(scenario maybe_convert_fail)
 def main [
   12:number <- copy 1
   13:number <- copy 35
   14:number <- copy 36
-  20:address:number <- maybe-convert 12:number-or-point/unsafe, 0:variant
+  20:number, 21:boolean <- maybe-convert 12:number-or-point/unsafe, 0:variant
 ]
-+mem: storing 0 in location 20
+# number: no write
+# boolean
++mem: storing 0 in location 21
 
 :(before "End Primitive Recipe Declarations")
 MAYBE_CONVERT,
@@ -95,6 +101,10 @@ case MAYBE_CONVERT: {
     break;
   }
   if (inst.products.empty()) break;
+  if (SIZE(inst.products) != 2) {
+    raise << maybe(caller.name) << "'maybe-convert' expects exactly 2 products in '" << to_original_string(inst) << "'\n" << end();
+    break;
+  }
   reagent product = inst.products.at(0);
   if (!canonize_type(product)) break;
   reagent& offset = inst.ingredients.at(1);
@@ -104,11 +114,14 @@ case MAYBE_CONVERT: {
     break;
   }
   reagent variant = variant_type(base, offset.value);
-  variant.type = new type_tree("address", get(Type_ordinal, "address"), variant.type);
   if (!types_coercible(product, variant)) {
     raise << maybe(caller.name) << "'maybe-convert " << base.original_string << ", " << inst.ingredients.at(1).original_string << "' should write to " << to_string(variant.type) << " but " << product.name << " has type " << to_string(product.type) << '\n' << end();
     break;
   }
+  if (!is_mu_boolean(inst.products.at(1))) {
+    raise << maybe(get(Recipe, r).name) << "second product yielded by 'maybe-convert' should be a boolean, but tried to write to " << inst.products.at(1).original_string << '\n' << end();
+    break;
+  }
   break;
 }
 :(before "End Primitive Recipe Implementations")
@@ -121,16 +134,26 @@ case MAYBE_CONVERT: {
     break;
   }
   int tag = current_instruction().ingredients.at(1).value;
-  int result;
+  reagent product = current_instruction().products.at(0);
+  canonize(product);
+  reagent did_conversion_happen = current_instruction().products.at(1);
+  canonize(did_conversion_happen);
+  // optimization: directly write results to only update first product when necessary
   if (tag == static_cast<int>(get_or_insert(Memory, base_address))) {
-    result = base_address+1;
+    const reagent variant = variant_type(base, tag);
+    for (int i = 0; i < size_of(variant); ++i) {
+      double val = get_or_insert(Memory, base_address+1+i);
+      trace(9999, "mem") << "storing " << no_scientific(val) << " in location " << product.value+i << end();
+      put(Memory, product.value+i, val);
+    }
+    trace(9999, "mem") << "storing 1 in location " << did_conversion_happen.value << end();
+    put(Memory, did_conversion_happen.value, 1);
   }
   else {
-    result = 0;
+    trace(9999, "mem") << "storing 0 in location " << did_conversion_happen.value << end();
+    put(Memory, did_conversion_happen.value, 0);
   }
-  products.resize(1);
-  products.at(0).push_back(result);
-  break;
+  goto finish_instruction;
 }
 
 :(code)
@@ -151,9 +174,9 @@ def main [
   12:number <- copy 1
   13:number <- copy 35
   14:number <- copy 36
-  20:address:number <- maybe-convert 12:number-or-point/unsafe, 1:variant
+  20:number, 21:boolean <- maybe-convert 12:number-or-point/unsafe, 1:variant
 ]
-+error: main: 'maybe-convert 12:number-or-point/unsafe, 1:variant' should write to (address point) but 20 has type (address number)
++error: main: 'maybe-convert 12:number-or-point/unsafe, 1:variant' should write to point but 20 has type number
 
 //:: Allow exclusive containers to be defined in mu code.
 
@@ -338,6 +361,21 @@ def main [
 ]
 +error: main: too few ingredients in '1:foo <- merge 1/y, 23'
 
+:(scenario merge_check_exclusive_container_containing_container_4)
+exclusive-container foo [
+  x:number
+  y:bar
+]
+container bar [
+  a:number
+  b:number
+]
+def main [
+  1:bar <- merge 23, 24
+  3:foo <- merge 1/y, 1:bar
+]
+$error: 0
+
 //: Since the different variants of an exclusive-container might have
 //: different sizes, relax the size mismatch check for 'merge' instructions.
 :(before "End size_mismatch(x) Cases")
diff --git a/042name.cc b/042name.cc
index d697b40e..a65731e6 100644
--- a/042name.cc
+++ b/042name.cc
@@ -267,10 +267,12 @@ def main [
   12:number <- copy 1
   13:number <- copy 35
   14:number <- copy 36
-  20:address:point <- maybe-convert 12:number-or-point/unsafe, p:variant
+  20:point, 22:boolean <- maybe-convert 12:number-or-point/unsafe, p:variant
 ]
 +name: variant p of type number-or-point has tag 1
-+mem: storing 13 in location 20
++mem: storing 35 in location 20
++mem: storing 36 in location 21
++mem: storing 1 in location 22
 
 :(before "End transform_names(inst) Special-cases")
 // convert variant names of exclusive containers
diff --git a/084console.mu b/084console.mu
index de77c7fe..dab4be7d 100644
--- a/084console.mu
+++ b/084console.mu
@@ -65,9 +65,9 @@ def read-key console:address:shared:console -> result:character, console:address
   x:event, console, found?:boolean, quit?:boolean <- read-event console
   return-if quit?, 0, console/same-as-ingredient:0, found?, quit?
   return-unless found?, 0, console/same-as-ingredient:0, found?, quit?
-  c:address:character <- maybe-convert x, text:variant
-  return-unless c, 0, console/same-as-ingredient:0, 0/found, 0/quit
-  return *c, console/same-as-ingredient:0, 1/found, 0/quit
+  c:character, converted?:boolean <- maybe-convert x, text:variant
+  return-unless converted?, 0, console/same-as-ingredient:0, 0/found, 0/quit
+  return c, console/same-as-ingredient:0, 1/found, 0/quit
 ]
 
 def send-keys-to-channel console:address:shared:console, chan:address:shared:sink:character, screen:address:shared:screen -> console:address:shared:console, chan:address:shared:sink:character, screen:address:shared:screen [
diff --git a/build_and_test_until b/build_and_test_until
index fc0d7689..96eb9e43 100755
--- a/build_and_test_until
+++ b/build_and_test_until
@@ -17,7 +17,7 @@ then
   # All sorts of strange bugs ensue.
   sleep 1
   set -v  # Darwin's clang and valgrind is shit.
-  CFLAGS=${CFLAGS:-"-g -O3"} make #test
+  CFLAGS=${CFLAGS:-"-g -O3"} make test
 else
   set -v
   CXX=${CXX:-clang++} CFLAGS=${CFLAGS:-"-O3 -fsanitize=undefined -Wno-tautological-constant-out-of-range-compare"} make ${2:-valgrind}
diff --git a/edit/002-typing.mu b/edit/002-typing.mu
index 1c27ce98..d7624ccf 100644
--- a/edit/002-typing.mu
+++ b/edit/002-typing.mu
@@ -25,15 +25,15 @@ def editor-event-loop screen:address:shared:screen, console:address:shared:conso
     break-if quit?  # only in tests
     trace 10, [app], [next-event]
     # 'touch' event
-    t:address:touch-event <- maybe-convert e, touch:variant
+    t:touch-event, is-touch?:boolean <- maybe-convert e, touch:variant
     {
-      break-unless t
-      move-cursor-in-editor screen, editor, *t
+      break-unless is-touch?
+      move-cursor-in-editor screen, editor, t
       loop +next-event:label
     }
     # keyboard events
     {
-      break-if t
+      break-if is-touch?
       screen, editor, go-render?:boolean <- handle-keyboard-event screen, editor, e
       {
         break-unless go-render?
@@ -177,24 +177,24 @@ def handle-keyboard-event screen:address:shared:screen, editor:address:shared:ed
   save-column:number <- copy cursor-column
   # character
   {
-    c:address:character <- maybe-convert e, text:variant
-    break-unless c
+    c:character, is-unicode?:boolean <- maybe-convert e, text:variant
+    break-unless is-unicode?
     trace 10, [app], [handle-keyboard-event: special character]
     # exceptions for special characters go here
     <handle-special-character>
     # ignore any other special characters
-    regular-character?:boolean <- greater-or-equal *c, 32/space
+    regular-character?:boolean <- greater-or-equal c, 32/space
     go-render? <- copy 0/false
     return-unless regular-character?
     # otherwise type it in
     <insert-character-begin>
-    editor, screen, go-render?:boolean <- insert-at-cursor editor, *c, screen
+    editor, screen, go-render?:boolean <- insert-at-cursor editor, c, screen
     <insert-character-end>
     return
   }
   # special key to modify the text or move the cursor
-  k:address:number <- maybe-convert e:event, keycode:variant
-  assert k, [event was of unknown type; neither keyboard nor mouse]
+  k:number, is-keycode?:boolean <- maybe-convert e:event, keycode:variant
+  assert is-keycode?, [event was of unknown type; neither keyboard nor mouse]
   # handlers for each special key will go here
   <handle-special-key>
   go-render? <- copy 1/true
@@ -821,7 +821,7 @@ scenario editor-moves-cursor-down-after-inserting-newline [
 
 after <handle-special-character> [
   {
-    newline?:boolean <- equal *c, 10/newline
+    newline?:boolean <- equal c, 10/newline
     break-unless newline?
     <insert-enter-begin>
     editor <- insert-new-line-and-indent editor, screen
@@ -1003,7 +1003,7 @@ ef]
 
 after <handle-special-key> [
   {
-    paste-start?:boolean <- equal *k, 65507/paste-start
+    paste-start?:boolean <- equal k, 65507/paste-start
     break-unless paste-start?
     *editor <- put *editor, indent?:offset, 0/false
     go-render? <- copy 1/true
@@ -1013,7 +1013,7 @@ after <handle-special-key> [
 
 after <handle-special-key> [
   {
-    paste-end?:boolean <- equal *k, 65506/paste-end
+    paste-end?:boolean <- equal k, 65506/paste-end
     break-unless paste-end?
     *editor <- put *editor, indent?:offset, 1/true
     go-render? <- copy 1/true
diff --git a/edit/003-shortcuts.mu b/edit/003-shortcuts.mu
index f29773af..8ed01de7 100644
--- a/edit/003-shortcuts.mu
+++ b/edit/003-shortcuts.mu
@@ -25,7 +25,7 @@ cd]
 
 after <handle-special-character> [
   {
-    tab?:boolean <- equal *c, 9/tab
+    tab?:boolean <- equal c, 9/tab
     break-unless tab?
     <insert-character-begin>
     editor, screen, go-render?:boolean <- insert-at-cursor editor, 32/space, screen
@@ -68,7 +68,7 @@ scenario editor-handles-backspace-key [
 
 after <handle-special-character> [
   {
-    delete-previous-character?:boolean <- equal *c, 8/backspace
+    delete-previous-character?:boolean <- equal c, 8/backspace
     break-unless delete-previous-character?
     <backspace-character-begin>
     editor, screen, go-render?:boolean, backspaced-cell:address:shared:duplex-list:character <- delete-before-cursor editor, screen
@@ -339,7 +339,7 @@ scenario editor-handles-delete-key [
 
 after <handle-special-key> [
   {
-    delete-next-character?:boolean <- equal *k, 65522/delete
+    delete-next-character?:boolean <- equal k, 65522/delete
     break-unless delete-next-character?
     <delete-character-begin>
     editor, screen, go-render?:boolean, deleted-cell:address:shared:duplex-list:character <- delete-at-cursor editor, screen
@@ -415,7 +415,7 @@ scenario editor-moves-cursor-right-with-key [
 
 after <handle-special-key> [
   {
-    move-to-next-character?:boolean <- equal *k, 65514/right-arrow
+    move-to-next-character?:boolean <- equal k, 65514/right-arrow
     break-unless move-to-next-character?
     # if not at end of text
     next-cursor:address:shared:duplex-list:character <- next before-cursor
@@ -699,7 +699,7 @@ scenario editor-moves-cursor-left-with-key [
 
 after <handle-special-key> [
   {
-    move-to-previous-character?:boolean <- equal *k, 65515/left-arrow
+    move-to-previous-character?:boolean <- equal k, 65515/left-arrow
     break-unless move-to-previous-character?
     trace 10, [app], [left arrow]
     # if not at start of text (before-cursor at § sentinel)
@@ -963,7 +963,7 @@ def]
 
 after <handle-special-key> [
   {
-    move-to-previous-line?:boolean <- equal *k, 65517/up-arrow
+    move-to-previous-line?:boolean <- equal k, 65517/up-arrow
     break-unless move-to-previous-line?
     <move-cursor-begin>
     editor, go-render? <- move-to-previous-line editor
@@ -1193,7 +1193,7 @@ def]
 
 after <handle-special-key> [
   {
-    move-to-next-line?:boolean <- equal *k, 65516/down-arrow
+    move-to-next-line?:boolean <- equal k, 65516/down-arrow
     break-unless move-to-next-line?
     <move-cursor-begin>
     editor, go-render? <- move-to-next-line editor, screen-height
@@ -1324,7 +1324,7 @@ scenario editor-moves-to-start-of-line-with-ctrl-a [
 
 after <handle-special-character> [
   {
-    move-to-start-of-line?:boolean <- equal *c, 1/ctrl-a
+    move-to-start-of-line?:boolean <- equal c, 1/ctrl-a
     break-unless move-to-start-of-line?
     <move-cursor-begin>
     move-to-start-of-line editor
@@ -1337,7 +1337,7 @@ after <handle-special-character> [
 
 after <handle-special-key> [
   {
-    move-to-start-of-line?:boolean <- equal *k, 65521/home
+    move-to-start-of-line?:boolean <- equal k, 65521/home
     break-unless move-to-start-of-line?
     <move-cursor-begin>
     move-to-start-of-line editor
@@ -1496,7 +1496,7 @@ scenario editor-moves-to-end-of-line-with-ctrl-e [
 
 after <handle-special-character> [
   {
-    move-to-end-of-line?:boolean <- equal *c, 5/ctrl-e
+    move-to-end-of-line?:boolean <- equal c, 5/ctrl-e
     break-unless move-to-end-of-line?
     <move-cursor-begin>
     move-to-end-of-line editor
@@ -1509,7 +1509,7 @@ after <handle-special-character> [
 
 after <handle-special-key> [
   {
-    move-to-end-of-line?:boolean <- equal *k, 65520/end
+    move-to-end-of-line?:boolean <- equal k, 65520/end
     break-unless move-to-end-of-line?
     <move-cursor-begin>
     move-to-end-of-line editor
@@ -1642,7 +1642,7 @@ scenario editor-deletes-to-start-of-line-with-ctrl-u [
 
 after <handle-special-character> [
   {
-    delete-to-start-of-line?:boolean <- equal *c, 21/ctrl-u
+    delete-to-start-of-line?:boolean <- equal c, 21/ctrl-u
     break-unless delete-to-start-of-line?
     <delete-to-start-of-line-begin>
     deleted-cells:address:shared:duplex-list:character <- delete-to-start-of-line editor
@@ -1776,7 +1776,7 @@ scenario editor-deletes-to-end-of-line-with-ctrl-k [
 
 after <handle-special-character> [
   {
-    delete-to-end-of-line?:boolean <- equal *c, 11/ctrl-k
+    delete-to-end-of-line?:boolean <- equal c, 11/ctrl-k
     break-unless delete-to-end-of-line?
     <delete-to-end-of-line-begin>
     deleted-cells:address:shared:duplex-list:character <- delete-to-end-of-line editor
@@ -2718,7 +2718,7 @@ d]
 
 after <handle-special-character> [
   {
-    page-down?:boolean <- equal *c, 6/ctrl-f
+    page-down?:boolean <- equal c, 6/ctrl-f
     break-unless page-down?
     old-top:address:shared:duplex-list:character <- get *editor, top-of-screen:offset
     <move-cursor-begin>
@@ -2734,7 +2734,7 @@ after <handle-special-character> [
 
 after <handle-special-key> [
   {
-    page-down?:boolean <- equal *k, 65518/page-down
+    page-down?:boolean <- equal k, 65518/page-down
     break-unless page-down?
     old-top:address:shared:duplex-list:character <- get *editor, top-of-screen:offset
     <move-cursor-begin>
@@ -2912,7 +2912,7 @@ d]
 
 after <handle-special-character> [
   {
-    page-up?:boolean <- equal *c, 2/ctrl-b
+    page-up?:boolean <- equal c, 2/ctrl-b
     break-unless page-up?
     old-top:address:shared:duplex-list:character <- get *editor, top-of-screen:offset
     <move-cursor-begin>
@@ -2928,7 +2928,7 @@ after <handle-special-character> [
 
 after <handle-special-key> [
   {
-    page-up?:boolean <- equal *k, 65519/page-up
+    page-up?:boolean <- equal k, 65519/page-up
     break-unless page-up?
     old-top:address:shared:duplex-list:character <- get *editor, top-of-screen:offset
     <move-cursor-begin>
diff --git a/edit/004-programming-environment.mu b/edit/004-programming-environment.mu
index 86ad3f5a..3503f008 100644
--- a/edit/004-programming-environment.mu
+++ b/edit/004-programming-environment.mu
@@ -68,29 +68,29 @@ def event-loop screen:address:shared:screen, console:address:shared:console, env
     <handle-event>
     # check for global events that will trigger regardless of which editor has focus
     {
-      k:address:number <- maybe-convert e:event, keycode:variant
-      break-unless k
+      k:number, is-keycode?:boolean <- maybe-convert e:event, keycode:variant
+      break-unless is-keycode?
       <global-keypress>
     }
     {
-      c:address:character <- maybe-convert e:event, text:variant
-      break-unless c
+      c:character, is-unicode?:boolean <- maybe-convert e:event, text:variant
+      break-unless is-unicode?
       <global-type>
     }
     # 'touch' event - send to both sides, see what picks it up
     {
-      t:address:touch-event <- maybe-convert e:event, touch:variant
-      break-unless t
+      t:touch-event, is-touch?:boolean <- maybe-convert e:event, touch:variant
+      break-unless is-touch?
       # ignore all but 'left-click' events for now
       # todo: test this
-      touch-type:number <- get *t, type:offset
+      touch-type:number <- get t, type:offset
       is-left-click?:boolean <- equal touch-type, 65513/mouse-left
       loop-unless is-left-click?, +next-event:label
       # later exceptions for non-editor touches will go here
       <global-touch>
       # send to both editors
-      _ <- move-cursor-in-editor screen, recipes, *t
-      sandbox-in-focus?:boolean <- move-cursor-in-editor screen, current-sandbox, *t
+      _ <- move-cursor-in-editor screen, recipes, t
+      sandbox-in-focus?:boolean <- move-cursor-in-editor screen, current-sandbox, t
       *env <- put *env, sandbox-in-focus?:offset, sandbox-in-focus?
       screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env
       loop +next-event:label
@@ -98,8 +98,8 @@ def event-loop screen:address:shared:screen, console:address:shared:console, env
     # 'resize' event - redraw editor
     # todo: test this after supporting resize in assume-console
     {
-      r:address:resize-event <- maybe-convert e:event, resize:variant
-      break-unless r
+      r:resize-event, is-resize?:boolean <- maybe-convert e:event, resize:variant
+      break-unless is-resize?
       # if more events, we're still resizing; wait until we stop
       more-events?:boolean <- has-more-events? console
       {
@@ -582,7 +582,7 @@ def render-code screen:address:shared:screen, s:address:shared:array:character,
 
 after <global-type> [
   {
-    redraw-screen?:boolean <- equal *c, 12/ctrl-l
+    redraw-screen?:boolean <- equal c, 12/ctrl-l
     break-unless redraw-screen?
     screen <- render-all screen, env:address:shared:programming-environment-data
     sync-screen screen
@@ -595,7 +595,7 @@ after <global-type> [
 
 after <global-type> [
   {
-    switch-side?:boolean <- equal *c, 14/ctrl-n
+    switch-side?:boolean <- equal c, 14/ctrl-n
     break-unless switch-side?
     sandbox-in-focus?:boolean <- get *env, sandbox-in-focus?:offset
     sandbox-in-focus? <- not sandbox-in-focus?
diff --git a/edit/005-sandbox.mu b/edit/005-sandbox.mu
index 0bbd7f85..89f52fb6 100644
--- a/edit/005-sandbox.mu
+++ b/edit/005-sandbox.mu
@@ -124,7 +124,7 @@ scenario run-and-show-results [
 after <global-keypress> [
   # F4? load all code and run all sandboxes.
   {
-    do-run?:boolean <- equal *k, 65532/F4
+    do-run?:boolean <- equal k, 65532/F4
     break-unless do-run?
     screen <- update-status screen, [running...       ], 245/grey
     error?:boolean, env, screen <- run-sandboxes env, screen
@@ -614,7 +614,7 @@ scenario scrolling-down-past-bottom-of-sandbox-editor [
 after <global-keypress> [
   {
     break-unless sandbox-in-focus?
-    down?:boolean <- equal *k, 65516/down-arrow
+    down?:boolean <- equal k, 65516/down-arrow
     break-unless down?
     sandbox-bottom:number <- get *current-sandbox, bottom:offset
     sandbox-cursor:number <- get *current-sandbox, cursor-row:offset
@@ -656,7 +656,7 @@ after <update-cursor-special-cases> [
 after <global-keypress> [
   {
     break-unless sandbox-in-focus?
-    up?:boolean <- equal *k, 65517/up-arrow
+    up?:boolean <- equal k, 65517/up-arrow
     break-unless up?
     render-from:number <- get *env, render-from:offset
     at-beginning?:boolean <- equal render-from, -1
diff --git a/edit/006-sandbox-edit.mu b/edit/006-sandbox-edit.mu
index c5537851..ebfac6ac 100644
--- a/edit/006-sandbox-edit.mu
+++ b/edit/006-sandbox-edit.mu
@@ -66,13 +66,13 @@ after <global-touch> [
   # below sandbox editor? pop appropriate sandbox contents back into sandbox editor
   {
     sandbox-left-margin:number <- get *current-sandbox, left:offset
-    click-column:number <- get *t, column:offset
+    click-column:number <- get t, column:offset
     on-sandbox-side?:boolean <- greater-or-equal click-column, sandbox-left-margin
     break-unless on-sandbox-side?
     first-sandbox:address:shared:sandbox-data <- get *env, sandbox:offset
     break-unless first-sandbox
     first-sandbox-begins:number <- get *first-sandbox, starting-row-on-screen:offset
-    click-row:number <- get *t, row:offset
+    click-row:number <- get t, row:offset
     below-sandbox-editor?:boolean <- greater-or-equal click-row, first-sandbox-begins
     break-unless below-sandbox-editor?
     empty-sandbox-editor?:boolean <- empty-editor? current-sandbox
diff --git a/edit/007-sandbox-delete.mu b/edit/007-sandbox-delete.mu
index e59d333e..aed22244 100644
--- a/edit/007-sandbox-delete.mu
+++ b/edit/007-sandbox-delete.mu
@@ -67,7 +67,7 @@ scenario deleting-sandboxes [
 after <global-touch> [
   # on a sandbox delete icon? process delete
   {
-    was-delete?:boolean <- delete-sandbox *t, env
+    was-delete?:boolean <- delete-sandbox t, env
     break-unless was-delete?
     hide-screen screen
     screen <- render-sandbox-side screen, env
diff --git a/edit/008-sandbox-test.mu b/edit/008-sandbox-test.mu
index c81099f9..c35cf802 100644
--- a/edit/008-sandbox-test.mu
+++ b/edit/008-sandbox-test.mu
@@ -107,13 +107,13 @@ after <global-touch> [
   # check if it's inside the output of any sandbox
   {
     sandbox-left-margin:number <- get *current-sandbox, left:offset
-    click-column:number <- get *t, column:offset
+    click-column:number <- get t, column:offset
     on-sandbox-side?:boolean <- greater-or-equal click-column, sandbox-left-margin
     break-unless on-sandbox-side?
     first-sandbox:address:shared:sandbox-data <- get *env, sandbox:offset
     break-unless first-sandbox
     first-sandbox-begins:number <- get *first-sandbox, starting-row-on-screen:offset
-    click-row:number <- get *t, row:offset
+    click-row:number <- get t, row:offset
     below-sandbox-editor?:boolean <- greater-or-equal click-row, first-sandbox-begins
     break-unless below-sandbox-editor?
     # identify the sandbox whose output is being clicked on
diff --git a/edit/009-sandbox-trace.mu b/edit/009-sandbox-trace.mu
index f7476ca8..71eb7cfb 100644
--- a/edit/009-sandbox-trace.mu
+++ b/edit/009-sandbox-trace.mu
@@ -142,13 +142,13 @@ after <global-touch> [
   # check if it's inside the code of any sandbox
   {
     sandbox-left-margin:number <- get *current-sandbox, left:offset
-    click-column:number <- get *t, column:offset
+    click-column:number <- get t, column:offset
     on-sandbox-side?:boolean <- greater-or-equal click-column, sandbox-left-margin
     break-unless on-sandbox-side?
     first-sandbox:address:shared:sandbox-data <- get *env, sandbox:offset
     break-unless first-sandbox
     first-sandbox-begins:number <- get *first-sandbox, starting-row-on-screen:offset
-    click-row:number <- get *t, row:offset
+    click-row:number <- get t, row:offset
     below-sandbox-editor?:boolean <- greater-or-equal click-row, first-sandbox-begins
     break-unless below-sandbox-editor?
     # identify the sandbox whose code is being clicked on
diff --git a/edit/011-editor-undo.mu b/edit/011-editor-undo.mu
index 07ecb14f..94135346 100644
--- a/edit/011-editor-undo.mu
+++ b/edit/011-editor-undo.mu
@@ -63,7 +63,7 @@ container editor-data [
 # ctrl-z - undo operation
 after <handle-special-character> [
   {
-    undo?:boolean <- equal *c, 26/ctrl-z
+    undo?:boolean <- equal c, 26/ctrl-z
     break-unless undo?
     undo:address:shared:list:address:shared:operation <- get *editor, undo:offset
     break-unless undo
@@ -81,7 +81,7 @@ after <handle-special-character> [
 # ctrl-y - redo operation
 after <handle-special-character> [
   {
-    redo?:boolean <- equal *c, 25/ctrl-y
+    redo?:boolean <- equal c, 25/ctrl-y
     break-unless redo?
     redo:address:shared:list:address:shared:operation <- get *editor, redo:offset
     break-unless redo
@@ -151,16 +151,17 @@ before <insert-character-end> [
     # if previous operation was an insert, coalesce this operation with it
     break-unless undo
     op:address:shared:operation <- first undo
-    typing:address:insert-operation <- maybe-convert *op, typing:variant
-    break-unless typing
-    previous-coalesce-tag:number <- get *typing, tag:offset
+    typing:insert-operation, is-insert?:boolean <- maybe-convert *op, typing:variant
+    break-unless is-insert?
+    previous-coalesce-tag:number <- get typing, tag:offset
     break-unless previous-coalesce-tag
     before-cursor:address:shared:duplex-list:character <- get *editor, before-cursor:offset
     insert-until:address:shared:duplex-list:character <- next before-cursor
-    *typing <- put *typing, insert-until:offset, insert-until
-    *typing <- put *typing, after-row:offset, cursor-row
-    *typing <- put *typing, after-column:offset, cursor-column
-    *typing <- put *typing, after-top-of-screen:offset, top-after
+    typing <- put typing, insert-until:offset, insert-until
+    typing <- put typing, after-row:offset, cursor-row
+    typing <- put typing, after-column:offset, cursor-column
+    typing <- put typing, after-top-of-screen:offset, top-after
+    *op <- merge 0/insert-operation, typing
     break +done-adding-insert-operation:label
   }
   # if not, create a new operation
@@ -210,19 +211,19 @@ def add-operation editor:address:shared:editor-data, op:address:shared:operation
 
 after <handle-undo> [
   {
-    typing:address:insert-operation <- maybe-convert *op, typing:variant
-    break-unless typing
-    start:address:shared:duplex-list:character <- get *typing, insert-from:offset
-    end:address:shared:duplex-list:character <- get *typing, insert-until:offset
+    typing:insert-operation, is-insert?:boolean <- maybe-convert *op, typing:variant
+    break-unless is-insert?
+    start:address:shared:duplex-list:character <- get typing, insert-from:offset
+    end:address:shared:duplex-list:character <- get typing, insert-until:offset
     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
     before-cursor:address:shared:duplex-list:character <- prev start
     *editor <- put *editor, before-cursor:offset, before-cursor
     remove-between before-cursor, end
-    cursor-row <- get *typing, before-row:offset
+    cursor-row <- get typing, before-row:offset
     *editor <- put *editor, cursor-row:offset, cursor-row
-    cursor-column <- get *typing, before-column:offset
+    cursor-column <- get typing, before-column:offset
     *editor <- put *editor, cursor-column:offset, cursor-column
-    top:address:shared:duplex-list:character <- get *typing, before-top-of-screen:offset
+    top:address:shared:duplex-list:character <- get typing, before-top-of-screen:offset
     *editor <- put *editor, top-of-screen:offset, top
   }
 ]
@@ -410,18 +411,18 @@ scenario editor-redo-typing [
 
 after <handle-redo> [
   {
-    typing:address:insert-operation <- maybe-convert *op, typing:variant
-    break-unless typing
+    typing:insert-operation, is-insert?:boolean <- maybe-convert *op, typing:variant
+    break-unless is-insert?
     before-cursor <- get *editor, before-cursor:offset
-    insert-from:address:shared:duplex-list:character <- get *typing, insert-from:offset  # ignore insert-to because it's already been spliced away
+    insert-from:address:shared:duplex-list:character <- get typing, insert-from:offset  # ignore insert-to because it's already been spliced away
     # assert insert-to matches next(before-cursor)
     insert-range before-cursor, insert-from
     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
-    cursor-row <- get *typing, after-row:offset
+    cursor-row <- get typing, after-row:offset
     *editor <- put *editor, cursor-row:offset, cursor-row
-    cursor-column <- get *typing, after-column:offset
+    cursor-column <- get typing, after-column:offset
     *editor <- put *editor, cursor-column:offset, cursor-column
-    top:address:shared:duplex-list:character <- get *typing, after-top-of-screen:offset
+    top:address:shared:duplex-list:character <- get typing, after-top-of-screen:offset
     *editor <- put *editor, top-of-screen:offset, top
   }
 ]
@@ -730,14 +731,15 @@ before <move-cursor-end> [
     undo:address:shared:list:address:shared:operation <- get *editor, undo:offset
     break-unless undo
     op:address:shared:operation <- first undo
-    move:address:move-operation <- maybe-convert *op, move:variant
-    break-unless move
-    previous-coalesce-tag:number <- get *move, tag:offset
+    move:move-operation, is-move?:boolean <- maybe-convert *op, move:variant
+    break-unless is-move?
+    previous-coalesce-tag:number <- get move, tag:offset
     coalesce?:boolean <- equal undo-coalesce-tag, previous-coalesce-tag
     break-unless coalesce?
-    *move <- put *move, after-row:offset, cursor-row
-    *move <- put *move, after-column:offset, cursor-column
-    *move <- put *move, after-top-of-screen:offset, top-after
+    move <- put move, after-row:offset, cursor-row
+    move <- put move, after-column:offset, cursor-column
+    move <- put move, after-top-of-screen:offset, top-after
+    *op <- merge 1/move-operation, move
     break +done-adding-move-operation:label
   }
   op:address:shared:operation <- new operation:type
@@ -748,14 +750,14 @@ before <move-cursor-end> [
 
 after <handle-undo> [
   {
-    move:address:move-operation <- maybe-convert *op, move:variant
-    break-unless move
+    move:move-operation, is-move?:boolean <- maybe-convert *op, move:variant
+    break-unless is-move?
     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
-    cursor-row <- get *move, before-row:offset
+    cursor-row <- get move, before-row:offset
     *editor <- put *editor, cursor-row:offset, cursor-row
-    cursor-column <- get *move, before-column:offset
+    cursor-column <- get move, before-column:offset
     *editor <- put *editor, cursor-column:offset, cursor-column
-    top:address:shared:duplex-list:character <- get *move, before-top-of-screen:offset
+    top:address:shared:duplex-list:character <- get move, before-top-of-screen:offset
     *editor <- put *editor, top-of-screen:offset, top
   }
 ]
@@ -1378,14 +1380,14 @@ ghi]
 
 after <handle-redo> [
   {
-    move:address:move-operation <- maybe-convert *op, move:variant
-    break-unless move
+    move:move-operation, is-move?:boolean <- maybe-convert *op, move:variant
+    break-unless is-move?
     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
-    cursor-row <- get *move, after-row:offset
+    cursor-row <- get move, after-row:offset
     *editor <- put *editor, cursor-row:offset, cursor-row
-    cursor-column <- get *move, after-column:offset
+    cursor-column <- get move, after-column:offset
     *editor <- put *editor, cursor-column:offset, cursor-column
-    top:address:shared:duplex-list:character <- get *move, after-top-of-screen:offset
+    top:address:shared:duplex-list:character <- get move, after-top-of-screen:offset
     *editor <- put *editor, top-of-screen:offset, top
   }
 ]
@@ -1620,18 +1622,19 @@ before <backspace-character-end> [
       # if previous operation was an insert, coalesce this operation with it
       break-unless *undo
       op:address:shared:operation <- first undo
-      deletion:address:delete-operation <- maybe-convert *op, delete:variant
-      break-unless deletion
-      previous-coalesce-tag:number <- get *deletion, tag:offset
+      deletion:delete-operation, is-delete?:boolean <- maybe-convert *op, delete:variant
+      break-unless is-delete?
+      previous-coalesce-tag:number <- get deletion, tag:offset
       coalesce?:boolean <- equal previous-coalesce-tag, 1/coalesce-backspace
       break-unless coalesce?
-      *deletion <- put *deletion, delete-from:offset, before-cursor
-      backspaced-so-far:address:shared:duplex-list:character <- get *deletion, deleted-text:offset
+      deletion <- put deletion, delete-from:offset, before-cursor
+      backspaced-so-far:address:shared:duplex-list:character <- get deletion, deleted-text:offset
       insert-range backspaced-cell, backspaced-so-far
-      *deletion <- put *deletion, deleted-text:offset, backspaced-cell
-      *deletion <- put *deletion, after-row:offset, cursor-row
-      *deletion <- put *deletion, after-column:offset, cursor-column
-      *deletion <- put *deletion, after-top-of-screen:offset, top-after
+      deletion <- put deletion, deleted-text:offset, backspaced-cell
+      deletion <- put deletion, after-row:offset, cursor-row
+      deletion <- put deletion, after-column:offset, cursor-column
+      deletion <- put deletion, after-top-of-screen:offset, top-after
+      *op <- merge 2/delete-operation, deletion
       break +done-adding-backspace-operation:label
     }
     # if not, create a new operation
@@ -1645,38 +1648,38 @@ before <backspace-character-end> [
 
 after <handle-undo> [
   {
-    deletion:address:delete-operation <- maybe-convert *op, delete:variant
-    break-unless deletion
-    anchor:address:shared:duplex-list:character <- get *deletion, delete-from:offset
+    deletion:delete-operation, is-delete?:boolean <- maybe-convert *op, delete:variant
+    break-unless is-delete?
+    anchor:address:shared:duplex-list:character <- get deletion, delete-from:offset
     break-unless anchor
-    deleted:address:shared:duplex-list:character <- get *deletion, deleted-text:offset
+    deleted:address:shared:duplex-list:character <- get deletion, deleted-text:offset
     old-cursor:address:shared:duplex-list:character <- last deleted
     insert-range anchor, deleted
     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
     before-cursor <- copy old-cursor
-    cursor-row <- get *deletion, before-row:offset
+    cursor-row <- get deletion, before-row:offset
     *editor <- put *editor, cursor-row:offset, cursor-row
-    cursor-column <- get *deletion, before-column:offset
+    cursor-column <- get deletion, before-column:offset
     *editor <- put *editor, cursor-column:offset, cursor-column
-    top:address:shared:duplex-list:character <- get *deletion, before-top-of-screen:offset
+    top:address:shared:duplex-list:character <- get deletion, before-top-of-screen:offset
     *editor <- put *editor, top-of-screen:offset, top
   }
 ]
 
 after <handle-redo> [
   {
-    deletion:address:delete-operation <- maybe-convert *op, delete:variant
-    break-unless deletion
-    start:address:shared:duplex-list:character <- get *deletion, delete-from:offset
-    end:address:shared:duplex-list:character <- get *deletion, delete-until:offset
+    deletion:delete-operation, is-delete?:boolean <- maybe-convert *op, delete:variant
+    break-unless is-delete?
+    start:address:shared:duplex-list:character <- get deletion, delete-from:offset
+    end:address:shared:duplex-list:character <- get deletion, delete-until:offset
     data:address:shared:duplex-list:character <- get *editor, data:offset
     remove-between start, end
     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
-    cursor-row <- get *deletion, after-row:offset
+    cursor-row <- get deletion, after-row:offset
     *editor <- put *editor, cursor-row:offset, cursor-row
-    cursor-column <- get *deletion, after-column:offset
+    cursor-column <- get deletion, after-column:offset
     *editor <- put *editor, cursor-column:offset, cursor-column
-    top:address:shared:duplex-list:character <- get *deletion, before-top-of-screen:offset
+    top:address:shared:duplex-list:character <- get deletion, before-top-of-screen:offset
     *editor <- put *editor, top-of-screen:offset, top
   }
 ]
@@ -1845,19 +1848,20 @@ before <delete-character-end> [
       # if previous operation was an insert, coalesce this operation with it
       break-unless undo
       op:address:shared:operation <- first undo
-      deletion:address:delete-operation <- maybe-convert *op, delete:variant
-      break-unless deletion
-      previous-coalesce-tag:number <- get *deletion, tag:offset
+      deletion:delete-operation, is-delete?:boolean <- maybe-convert *op, delete:variant
+      break-unless is-delete?
+      previous-coalesce-tag:number <- get deletion, tag:offset
       coalesce?:boolean <- equal previous-coalesce-tag, 2/coalesce-delete
       break-unless coalesce?
       delete-until:address:shared:duplex-list:character <- next before-cursor
-      *deletion <- put *deletion, delete-until:offset, delete-until
-      deleted-so-far:address:shared:duplex-list:character <- get *deletion, deleted-text:offset
+      deletion <- put deletion, delete-until:offset, delete-until
+      deleted-so-far:address:shared:duplex-list:character <- get deletion, deleted-text:offset
       deleted-so-far <- append deleted-so-far, deleted-cell
-      *deletion <- put *deletion, deleted-text:offset, deleted-so-far
-      *deletion <- put *deletion, after-row:offset, cursor-row
-      *deletion <- put *deletion, after-column:offset, cursor-column
-      *deletion <- put *deletion, after-top-of-screen:offset, top-after
+      deletion <- put deletion, deleted-text:offset, deleted-so-far
+      deletion <- put deletion, after-row:offset, cursor-row
+      deletion <- put deletion, after-column:offset, cursor-column
+      deletion <- put deletion, after-top-of-screen:offset, top-after
+      *op <- merge 2/delete-operation, deletion
       break +done-adding-delete-operation:label
     }
     # if not, create a new operation
diff --git a/sandbox/002-typing.mu b/sandbox/002-typing.mu
index 1c27ce98..d7624ccf 100644
--- a/sandbox/002-typing.mu
+++ b/sandbox/002-typing.mu
@@ -25,15 +25,15 @@ def editor-event-loop screen:address:shared:screen, console:address:shared:conso
     break-if quit?  # only in tests
     trace 10, [app], [next-event]
     # 'touch' event
-    t:address:touch-event <- maybe-convert e, touch:variant
+    t:touch-event, is-touch?:boolean <- maybe-convert e, touch:variant
     {
-      break-unless t
-      move-cursor-in-editor screen, editor, *t
+      break-unless is-touch?
+      move-cursor-in-editor screen, editor, t
       loop +next-event:label
     }
     # keyboard events
     {
-      break-if t
+      break-if is-touch?
       screen, editor, go-render?:boolean <- handle-keyboard-event screen, editor, e
       {
         break-unless go-render?
@@ -177,24 +177,24 @@ def handle-keyboard-event screen:address:shared:screen, editor:address:shared:ed
   save-column:number <- copy cursor-column
   # character
   {
-    c:address:character <- maybe-convert e, text:variant
-    break-unless c
+    c:character, is-unicode?:boolean <- maybe-convert e, text:variant
+    break-unless is-unicode?
     trace 10, [app], [handle-keyboard-event: special character]
     # exceptions for special characters go here
     <handle-special-character>
     # ignore any other special characters
-    regular-character?:boolean <- greater-or-equal *c, 32/space
+    regular-character?:boolean <- greater-or-equal c, 32/space
     go-render? <- copy 0/false
     return-unless regular-character?
     # otherwise type it in
     <insert-character-begin>
-    editor, screen, go-render?:boolean <- insert-at-cursor editor, *c, screen
+    editor, screen, go-render?:boolean <- insert-at-cursor editor, c, screen
     <insert-character-end>
     return
   }
   # special key to modify the text or move the cursor
-  k:address:number <- maybe-convert e:event, keycode:variant
-  assert k, [event was of unknown type; neither keyboard nor mouse]
+  k:number, is-keycode?:boolean <- maybe-convert e:event, keycode:variant
+  assert is-keycode?, [event was of unknown type; neither keyboard nor mouse]
   # handlers for each special key will go here
   <handle-special-key>
   go-render? <- copy 1/true
@@ -821,7 +821,7 @@ scenario editor-moves-cursor-down-after-inserting-newline [
 
 after <handle-special-character> [
   {
-    newline?:boolean <- equal *c, 10/newline
+    newline?:boolean <- equal c, 10/newline
     break-unless newline?
     <insert-enter-begin>
     editor <- insert-new-line-and-indent editor, screen
@@ -1003,7 +1003,7 @@ ef]
 
 after <handle-special-key> [
   {
-    paste-start?:boolean <- equal *k, 65507/paste-start
+    paste-start?:boolean <- equal k, 65507/paste-start
     break-unless paste-start?
     *editor <- put *editor, indent?:offset, 0/false
     go-render? <- copy 1/true
@@ -1013,7 +1013,7 @@ after <handle-special-key> [
 
 after <handle-special-key> [
   {
-    paste-end?:boolean <- equal *k, 65506/paste-end
+    paste-end?:boolean <- equal k, 65506/paste-end
     break-unless paste-end?
     *editor <- put *editor, indent?:offset, 1/true
     go-render? <- copy 1/true
diff --git a/sandbox/003-shortcuts.mu b/sandbox/003-shortcuts.mu
index f29773af..8ed01de7 100644
--- a/sandbox/003-shortcuts.mu
+++ b/sandbox/003-shortcuts.mu
@@ -25,7 +25,7 @@ cd]
 
 after <handle-special-character> [
   {
-    tab?:boolean <- equal *c, 9/tab
+    tab?:boolean <- equal c, 9/tab
     break-unless tab?
     <insert-character-begin>
     editor, screen, go-render?:boolean <- insert-at-cursor editor, 32/space, screen
@@ -68,7 +68,7 @@ scenario editor-handles-backspace-key [
 
 after <handle-special-character> [
   {
-    delete-previous-character?:boolean <- equal *c, 8/backspace
+    delete-previous-character?:boolean <- equal c, 8/backspace
     break-unless delete-previous-character?
     <backspace-character-begin>
     editor, screen, go-render?:boolean, backspaced-cell:address:shared:duplex-list:character <- delete-before-cursor editor, screen
@@ -339,7 +339,7 @@ scenario editor-handles-delete-key [
 
 after <handle-special-key> [
   {
-    delete-next-character?:boolean <- equal *k, 65522/delete
+    delete-next-character?:boolean <- equal k, 65522/delete
     break-unless delete-next-character?
     <delete-character-begin>
     editor, screen, go-render?:boolean, deleted-cell:address:shared:duplex-list:character <- delete-at-cursor editor, screen
@@ -415,7 +415,7 @@ scenario editor-moves-cursor-right-with-key [
 
 after <handle-special-key> [
   {
-    move-to-next-character?:boolean <- equal *k, 65514/right-arrow
+    move-to-next-character?:boolean <- equal k, 65514/right-arrow
     break-unless move-to-next-character?
     # if not at end of text
     next-cursor:address:shared:duplex-list:character <- next before-cursor
@@ -699,7 +699,7 @@ scenario editor-moves-cursor-left-with-key [
 
 after <handle-special-key> [
   {
-    move-to-previous-character?:boolean <- equal *k, 65515/left-arrow
+    move-to-previous-character?:boolean <- equal k, 65515/left-arrow
     break-unless move-to-previous-character?
     trace 10, [app], [left arrow]
     # if not at start of text (before-cursor at § sentinel)
@@ -963,7 +963,7 @@ def]
 
 after <handle-special-key> [
   {
-    move-to-previous-line?:boolean <- equal *k, 65517/up-arrow
+    move-to-previous-line?:boolean <- equal k, 65517/up-arrow
     break-unless move-to-previous-line?
     <move-cursor-begin>
     editor, go-render? <- move-to-previous-line editor
@@ -1193,7 +1193,7 @@ def]
 
 after <handle-special-key> [
   {
-    move-to-next-line?:boolean <- equal *k, 65516/down-arrow
+    move-to-next-line?:boolean <- equal k, 65516/down-arrow
     break-unless move-to-next-line?
     <move-cursor-begin>
     editor, go-render? <- move-to-next-line editor, screen-height
@@ -1324,7 +1324,7 @@ scenario editor-moves-to-start-of-line-with-ctrl-a [
 
 after <handle-special-character> [
   {
-    move-to-start-of-line?:boolean <- equal *c, 1/ctrl-a
+    move-to-start-of-line?:boolean <- equal c, 1/ctrl-a
     break-unless move-to-start-of-line?
     <move-cursor-begin>
     move-to-start-of-line editor
@@ -1337,7 +1337,7 @@ after <handle-special-character> [
 
 after <handle-special-key> [
   {
-    move-to-start-of-line?:boolean <- equal *k, 65521/home
+    move-to-start-of-line?:boolean <- equal k, 65521/home
     break-unless move-to-start-of-line?
     <move-cursor-begin>
     move-to-start-of-line editor
@@ -1496,7 +1496,7 @@ scenario editor-moves-to-end-of-line-with-ctrl-e [
 
 after <handle-special-character> [
   {
-    move-to-end-of-line?:boolean <- equal *c, 5/ctrl-e
+    move-to-end-of-line?:boolean <- equal c, 5/ctrl-e
     break-unless move-to-end-of-line?
     <move-cursor-begin>
     move-to-end-of-line editor
@@ -1509,7 +1509,7 @@ after <handle-special-character> [
 
 after <handle-special-key> [
   {
-    move-to-end-of-line?:boolean <- equal *k, 65520/end
+    move-to-end-of-line?:boolean <- equal k, 65520/end
     break-unless move-to-end-of-line?
     <move-cursor-begin>
     move-to-end-of-line editor
@@ -1642,7 +1642,7 @@ scenario editor-deletes-to-start-of-line-with-ctrl-u [
 
 after <handle-special-character> [
   {
-    delete-to-start-of-line?:boolean <- equal *c, 21/ctrl-u
+    delete-to-start-of-line?:boolean <- equal c, 21/ctrl-u
     break-unless delete-to-start-of-line?
     <delete-to-start-of-line-begin>
     deleted-cells:address:shared:duplex-list:character <- delete-to-start-of-line editor
@@ -1776,7 +1776,7 @@ scenario editor-deletes-to-end-of-line-with-ctrl-k [
 
 after <handle-special-character> [
   {
-    delete-to-end-of-line?:boolean <- equal *c, 11/ctrl-k
+    delete-to-end-of-line?:boolean <- equal c, 11/ctrl-k
     break-unless delete-to-end-of-line?
     <delete-to-end-of-line-begin>
     deleted-cells:address:shared:duplex-list:character <- delete-to-end-of-line editor
@@ -2718,7 +2718,7 @@ d]
 
 after <handle-special-character> [
   {
-    page-down?:boolean <- equal *c, 6/ctrl-f
+    page-down?:boolean <- equal c, 6/ctrl-f
     break-unless page-down?
     old-top:address:shared:duplex-list:character <- get *editor, top-of-screen:offset
     <move-cursor-begin>
@@ -2734,7 +2734,7 @@ after <handle-special-character> [
 
 after <handle-special-key> [
   {
-    page-down?:boolean <- equal *k, 65518/page-down
+    page-down?:boolean <- equal k, 65518/page-down
     break-unless page-down?
     old-top:address:shared:duplex-list:character <- get *editor, top-of-screen:offset
     <move-cursor-begin>
@@ -2912,7 +2912,7 @@ d]
 
 after <handle-special-character> [
   {
-    page-up?:boolean <- equal *c, 2/ctrl-b
+    page-up?:boolean <- equal c, 2/ctrl-b
     break-unless page-up?
     old-top:address:shared:duplex-list:character <- get *editor, top-of-screen:offset
     <move-cursor-begin>
@@ -2928,7 +2928,7 @@ after <handle-special-character> [
 
 after <handle-special-key> [
   {
-    page-up?:boolean <- equal *k, 65519/page-up
+    page-up?:boolean <- equal k, 65519/page-up
     break-unless page-up?
     old-top:address:shared:duplex-list:character <- get *editor, top-of-screen:offset
     <move-cursor-begin>
diff --git a/sandbox/004-programming-environment.mu b/sandbox/004-programming-environment.mu
index a329c8c1..22fe6745 100644
--- a/sandbox/004-programming-environment.mu
+++ b/sandbox/004-programming-environment.mu
@@ -56,35 +56,35 @@ def event-loop screen:address:shared:screen, console:address:shared:console, env
     <handle-event>
     # check for global events that will trigger regardless of which editor has focus
     {
-      k:address:number <- maybe-convert e:event, keycode:variant
-      break-unless k
+      k:number, is-keycode?:boolean <- maybe-convert e:event, keycode:variant
+      break-unless is-keycode?
       <global-keypress>
     }
     {
-      c:address:character <- maybe-convert e:event, text:variant
-      break-unless c
+      c:character, is-unicode?:boolean <- maybe-convert e:event, text:variant
+      break-unless is-unicode?
       <global-type>
     }
     # 'touch' event
     {
-      t:address:touch-event <- maybe-convert e:event, touch:variant
-      break-unless t
+      t:touch-event, is-touch?:boolean <- maybe-convert e:event, touch:variant
+      break-unless is-touch?
       # ignore all but 'left-click' events for now
       # todo: test this
-      touch-type:number <- get *t, type:offset
+      touch-type:number <- get t, type:offset
       is-left-click?:boolean <- equal touch-type, 65513/mouse-left
       loop-unless is-left-click?, +next-event:label
       # later exceptions for non-editor touches will go here
       <global-touch>
-      move-cursor-in-editor screen, current-sandbox, *t
+      move-cursor-in-editor screen, current-sandbox, t
       screen <- update-cursor screen, current-sandbox, env
       loop +next-event:label
     }
     # 'resize' event - redraw editor
     # todo: test this after supporting resize in assume-console
     {
-      r:address:resize-event <- maybe-convert e:event, resize:variant
-      break-unless r
+      r:resize-event, is-resize?:boolean <- maybe-convert e:event, resize:variant
+      break-unless is-resize?
       # if more events, we're still resizing; wait until we stop
       more-events?:boolean <- has-more-events? console
       {
@@ -327,7 +327,7 @@ def render-code screen:address:shared:screen, s:address:shared:array:character,
 
 after <global-type> [
   {
-    redraw-screen?:boolean <- equal *c, 12/ctrl-l
+    redraw-screen?:boolean <- equal c, 12/ctrl-l
     break-unless redraw-screen?
     screen <- render-all screen, env:address:shared:programming-environment-data
     sync-screen screen
diff --git a/sandbox/005-sandbox.mu b/sandbox/005-sandbox.mu
index 0b6bd79c..7297c768 100644
--- a/sandbox/005-sandbox.mu
+++ b/sandbox/005-sandbox.mu
@@ -109,7 +109,7 @@ scenario run-and-show-results [
 after <global-keypress> [
   # F4? load all code and run all sandboxes.
   {
-    do-run?:boolean <- equal *k, 65532/F4
+    do-run?:boolean <- equal k, 65532/F4
     break-unless do-run?
     screen <- update-status screen, [running...       ], 245/grey
     test-recipes:address:shared:array:character, _/optional <- next-ingredient
@@ -606,7 +606,7 @@ scenario scrolling-down-past-bottom-of-sandbox-editor [
 # down on sandbox side updates render-from when sandbox editor has cursor at bottom
 after <global-keypress> [
   {
-    down?:boolean <- equal *k, 65516/down-arrow
+    down?:boolean <- equal k, 65516/down-arrow
     break-unless down?
     sandbox-bottom:number <- get *current-sandbox, bottom:offset
     sandbox-cursor:number <- get *current-sandbox, cursor-row:offset
@@ -646,7 +646,7 @@ after <update-cursor-special-cases> [
 # 'up' on sandbox side is like 'down': updates first-sandbox-to-render when necessary
 after <global-keypress> [
   {
-    up?:boolean <- equal *k, 65517/up-arrow
+    up?:boolean <- equal k, 65517/up-arrow
     break-unless up?
     render-from:number <- get *env, render-from:offset
     at-beginning?:boolean <- equal render-from, -1
diff --git a/sandbox/006-sandbox-edit.mu b/sandbox/006-sandbox-edit.mu
index c863efe1..91c632ed 100644
--- a/sandbox/006-sandbox-edit.mu
+++ b/sandbox/006-sandbox-edit.mu
@@ -67,13 +67,13 @@ after <global-touch> [
   # below editor? pop appropriate sandbox contents back into sandbox editor provided it's empty
   {
     sandbox-left-margin:number <- get *current-sandbox, left:offset
-    click-column:number <- get *t, column:offset
+    click-column:number <- get t, column:offset
     on-sandbox-side?:boolean <- greater-or-equal click-column, sandbox-left-margin
     break-unless on-sandbox-side?
     first-sandbox:address:shared:sandbox-data <- get *env, sandbox:offset
     break-unless first-sandbox
     first-sandbox-begins:number <- get *first-sandbox, starting-row-on-screen:offset
-    click-row:number <- get *t, row:offset
+    click-row:number <- get t, row:offset
     below-sandbox-editor?:boolean <- greater-or-equal click-row, first-sandbox-begins
     break-unless below-sandbox-editor?
     empty-sandbox-editor?:boolean <- empty-editor? current-sandbox
diff --git a/sandbox/007-sandbox-delete.mu b/sandbox/007-sandbox-delete.mu
index f99012b6..2fed31cd 100644
--- a/sandbox/007-sandbox-delete.mu
+++ b/sandbox/007-sandbox-delete.mu
@@ -66,7 +66,7 @@ scenario deleting-sandboxes [
 after <global-touch> [
   # on a sandbox delete icon? process delete
   {
-    was-delete?:boolean <- delete-sandbox *t, env
+    was-delete?:boolean <- delete-sandbox t, env
     break-unless was-delete?
     hide-screen screen
     screen <- render-sandbox-side screen, env
diff --git a/sandbox/008-sandbox-test.mu b/sandbox/008-sandbox-test.mu
index acd4c04d..04a83ac6 100644
--- a/sandbox/008-sandbox-test.mu
+++ b/sandbox/008-sandbox-test.mu
@@ -106,13 +106,13 @@ after <global-touch> [
   # check if it's inside the output of any sandbox
   {
     sandbox-left-margin:number <- get *current-sandbox, left:offset
-    click-column:number <- get *t, column:offset
+    click-column:number <- get t, column:offset
     on-sandbox-side?:boolean <- greater-or-equal click-column, sandbox-left-margin
     break-unless on-sandbox-side?
     first-sandbox:address:shared:sandbox-data <- get *env, sandbox:offset
     break-unless first-sandbox
     first-sandbox-begins:number <- get *first-sandbox, starting-row-on-screen:offset
-    click-row:number <- get *t, row:offset
+    click-row:number <- get t, row:offset
     below-sandbox-editor?:boolean <- greater-or-equal click-row, first-sandbox-begins
     break-unless below-sandbox-editor?
     # identify the sandbox whose output is being clicked on
diff --git a/sandbox/009-sandbox-trace.mu b/sandbox/009-sandbox-trace.mu
index a85a1920..c5f96726 100644
--- a/sandbox/009-sandbox-trace.mu
+++ b/sandbox/009-sandbox-trace.mu
@@ -134,13 +134,13 @@ after <global-touch> [
   # check if it's inside the code of any sandbox
   {
     sandbox-left-margin:number <- get *current-sandbox, left:offset
-    click-column:number <- get *t, column:offset
+    click-column:number <- get t, column:offset
     on-sandbox-side?:boolean <- greater-or-equal click-column, sandbox-left-margin
     break-unless on-sandbox-side?
     first-sandbox:address:shared:sandbox-data <- get *env, sandbox:offset
     break-unless first-sandbox
     first-sandbox-begins:number <- get *first-sandbox, starting-row-on-screen:offset
-    click-row:number <- get *t, row:offset
+    click-row:number <- get t, row:offset
     below-sandbox-editor?:boolean <- greater-or-equal click-row, first-sandbox-begins
     break-unless below-sandbox-editor?
     # identify the sandbox whose code is being clicked on
diff --git a/sandbox/011-editor-undo.mu b/sandbox/011-editor-undo.mu
index 07ecb14f..94135346 100644
--- a/sandbox/011-editor-undo.mu
+++ b/sandbox/011-editor-undo.mu
@@ -63,7 +63,7 @@ container editor-data [
 # ctrl-z - undo operation
 after <handle-special-character> [
   {
-    undo?:boolean <- equal *c, 26/ctrl-z
+    undo?:boolean <- equal c, 26/ctrl-z
     break-unless undo?
     undo:address:shared:list:address:shared:operation <- get *editor, undo:offset
     break-unless undo
@@ -81,7 +81,7 @@ after <handle-special-character> [
 # ctrl-y - redo operation
 after <handle-special-character> [
   {
-    redo?:boolean <- equal *c, 25/ctrl-y
+    redo?:boolean <- equal c, 25/ctrl-y
     break-unless redo?
     redo:address:shared:list:address:shared:operation <- get *editor, redo:offset
     break-unless redo
@@ -151,16 +151,17 @@ before <insert-character-end> [
     # if previous operation was an insert, coalesce this operation with it
     break-unless undo
     op:address:shared:operation <- first undo
-    typing:address:insert-operation <- maybe-convert *op, typing:variant
-    break-unless typing
-    previous-coalesce-tag:number <- get *typing, tag:offset
+    typing:insert-operation, is-insert?:boolean <- maybe-convert *op, typing:variant
+    break-unless is-insert?
+    previous-coalesce-tag:number <- get typing, tag:offset
     break-unless previous-coalesce-tag
     before-cursor:address:shared:duplex-list:character <- get *editor, before-cursor:offset
     insert-until:address:shared:duplex-list:character <- next before-cursor
-    *typing <- put *typing, insert-until:offset, insert-until
-    *typing <- put *typing, after-row:offset, cursor-row
-    *typing <- put *typing, after-column:offset, cursor-column
-    *typing <- put *typing, after-top-of-screen:offset, top-after
+    typing <- put typing, insert-until:offset, insert-until
+    typing <- put typing, after-row:offset, cursor-row
+    typing <- put typing, after-column:offset, cursor-column
+    typing <- put typing, after-top-of-screen:offset, top-after
+    *op <- merge 0/insert-operation, typing
     break +done-adding-insert-operation:label
   }
   # if not, create a new operation
@@ -210,19 +211,19 @@ def add-operation editor:address:shared:editor-data, op:address:shared:operation
 
 after <handle-undo> [
   {
-    typing:address:insert-operation <- maybe-convert *op, typing:variant
-    break-unless typing
-    start:address:shared:duplex-list:character <- get *typing, insert-from:offset
-    end:address:shared:duplex-list:character <- get *typing, insert-until:offset
+    typing:insert-operation, is-insert?:boolean <- maybe-convert *op, typing:variant
+    break-unless is-insert?
+    start:address:shared:duplex-list:character <- get typing, insert-from:offset
+    end:address:shared:duplex-list:character <- get typing, insert-until:offset
     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
     before-cursor:address:shared:duplex-list:character <- prev start
     *editor <- put *editor, before-cursor:offset, before-cursor
     remove-between before-cursor, end
-    cursor-row <- get *typing, before-row:offset
+    cursor-row <- get typing, before-row:offset
     *editor <- put *editor, cursor-row:offset, cursor-row
-    cursor-column <- get *typing, before-column:offset
+    cursor-column <- get typing, before-column:offset
     *editor <- put *editor, cursor-column:offset, cursor-column
-    top:address:shared:duplex-list:character <- get *typing, before-top-of-screen:offset
+    top:address:shared:duplex-list:character <- get typing, before-top-of-screen:offset
     *editor <- put *editor, top-of-screen:offset, top
   }
 ]
@@ -410,18 +411,18 @@ scenario editor-redo-typing [
 
 after <handle-redo> [
   {
-    typing:address:insert-operation <- maybe-convert *op, typing:variant
-    break-unless typing
+    typing:insert-operation, is-insert?:boolean <- maybe-convert *op, typing:variant
+    break-unless is-insert?
     before-cursor <- get *editor, before-cursor:offset
-    insert-from:address:shared:duplex-list:character <- get *typing, insert-from:offset  # ignore insert-to because it's already been spliced away
+    insert-from:address:shared:duplex-list:character <- get typing, insert-from:offset  # ignore insert-to because it's already been spliced away
     # assert insert-to matches next(before-cursor)
     insert-range before-cursor, insert-from
     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
-    cursor-row <- get *typing, after-row:offset
+    cursor-row <- get typing, after-row:offset
     *editor <- put *editor, cursor-row:offset, cursor-row
-    cursor-column <- get *typing, after-column:offset
+    cursor-column <- get typing, after-column:offset
     *editor <- put *editor, cursor-column:offset, cursor-column
-    top:address:shared:duplex-list:character <- get *typing, after-top-of-screen:offset
+    top:address:shared:duplex-list:character <- get typing, after-top-of-screen:offset
     *editor <- put *editor, top-of-screen:offset, top
   }
 ]
@@ -730,14 +731,15 @@ before <move-cursor-end> [
     undo:address:shared:list:address:shared:operation <- get *editor, undo:offset
     break-unless undo
     op:address:shared:operation <- first undo
-    move:address:move-operation <- maybe-convert *op, move:variant
-    break-unless move
-    previous-coalesce-tag:number <- get *move, tag:offset
+    move:move-operation, is-move?:boolean <- maybe-convert *op, move:variant
+    break-unless is-move?
+    previous-coalesce-tag:number <- get move, tag:offset
     coalesce?:boolean <- equal undo-coalesce-tag, previous-coalesce-tag
     break-unless coalesce?
-    *move <- put *move, after-row:offset, cursor-row
-    *move <- put *move, after-column:offset, cursor-column
-    *move <- put *move, after-top-of-screen:offset, top-after
+    move <- put move, after-row:offset, cursor-row
+    move <- put move, after-column:offset, cursor-column
+    move <- put move, after-top-of-screen:offset, top-after
+    *op <- merge 1/move-operation, move
     break +done-adding-move-operation:label
   }
   op:address:shared:operation <- new operation:type
@@ -748,14 +750,14 @@ before <move-cursor-end> [
 
 after <handle-undo> [
   {
-    move:address:move-operation <- maybe-convert *op, move:variant
-    break-unless move
+    move:move-operation, is-move?:boolean <- maybe-convert *op, move:variant
+    break-unless is-move?
     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
-    cursor-row <- get *move, before-row:offset
+    cursor-row <- get move, before-row:offset
     *editor <- put *editor, cursor-row:offset, cursor-row
-    cursor-column <- get *move, before-column:offset
+    cursor-column <- get move, before-column:offset
     *editor <- put *editor, cursor-column:offset, cursor-column
-    top:address:shared:duplex-list:character <- get *move, before-top-of-screen:offset
+    top:address:shared:duplex-list:character <- get move, before-top-of-screen:offset
     *editor <- put *editor, top-of-screen:offset, top
   }
 ]
@@ -1378,14 +1380,14 @@ ghi]
 
 after <handle-redo> [
   {
-    move:address:move-operation <- maybe-convert *op, move:variant
-    break-unless move
+    move:move-operation, is-move?:boolean <- maybe-convert *op, move:variant
+    break-unless is-move?
     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
-    cursor-row <- get *move, after-row:offset
+    cursor-row <- get move, after-row:offset
     *editor <- put *editor, cursor-row:offset, cursor-row
-    cursor-column <- get *move, after-column:offset
+    cursor-column <- get move, after-column:offset
     *editor <- put *editor, cursor-column:offset, cursor-column
-    top:address:shared:duplex-list:character <- get *move, after-top-of-screen:offset
+    top:address:shared:duplex-list:character <- get move, after-top-of-screen:offset
     *editor <- put *editor, top-of-screen:offset, top
   }
 ]
@@ -1620,18 +1622,19 @@ before <backspace-character-end> [
       # if previous operation was an insert, coalesce this operation with it
       break-unless *undo
       op:address:shared:operation <- first undo
-      deletion:address:delete-operation <- maybe-convert *op, delete:variant
-      break-unless deletion
-      previous-coalesce-tag:number <- get *deletion, tag:offset
+      deletion:delete-operation, is-delete?:boolean <- maybe-convert *op, delete:variant
+      break-unless is-delete?
+      previous-coalesce-tag:number <- get deletion, tag:offset
       coalesce?:boolean <- equal previous-coalesce-tag, 1/coalesce-backspace
       break-unless coalesce?
-      *deletion <- put *deletion, delete-from:offset, before-cursor
-      backspaced-so-far:address:shared:duplex-list:character <- get *deletion, deleted-text:offset
+      deletion <- put deletion, delete-from:offset, before-cursor
+      backspaced-so-far:address:shared:duplex-list:character <- get deletion, deleted-text:offset
       insert-range backspaced-cell, backspaced-so-far
-      *deletion <- put *deletion, deleted-text:offset, backspaced-cell
-      *deletion <- put *deletion, after-row:offset, cursor-row
-      *deletion <- put *deletion, after-column:offset, cursor-column
-      *deletion <- put *deletion, after-top-of-screen:offset, top-after
+      deletion <- put deletion, deleted-text:offset, backspaced-cell
+      deletion <- put deletion, after-row:offset, cursor-row
+      deletion <- put deletion, after-column:offset, cursor-column
+      deletion <- put deletion, after-top-of-screen:offset, top-after
+      *op <- merge 2/delete-operation, deletion
       break +done-adding-backspace-operation:label
     }
     # if not, create a new operation
@@ -1645,38 +1648,38 @@ before <backspace-character-end> [
 
 after <handle-undo> [
   {
-    deletion:address:delete-operation <- maybe-convert *op, delete:variant
-    break-unless deletion
-    anchor:address:shared:duplex-list:character <- get *deletion, delete-from:offset
+    deletion:delete-operation, is-delete?:boolean <- maybe-convert *op, delete:variant
+    break-unless is-delete?
+    anchor:address:shared:duplex-list:character <- get deletion, delete-from:offset
     break-unless anchor
-    deleted:address:shared:duplex-list:character <- get *deletion, deleted-text:offset
+    deleted:address:shared:duplex-list:character <- get deletion, deleted-text:offset
     old-cursor:address:shared:duplex-list:character <- last deleted
     insert-range anchor, deleted
     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
     before-cursor <- copy old-cursor
-    cursor-row <- get *deletion, before-row:offset
+    cursor-row <- get deletion, before-row:offset
     *editor <- put *editor, cursor-row:offset, cursor-row
-    cursor-column <- get *deletion, before-column:offset
+    cursor-column <- get deletion, before-column:offset
     *editor <- put *editor, cursor-column:offset, cursor-column
-    top:address:shared:duplex-list:character <- get *deletion, before-top-of-screen:offset
+    top:address:shared:duplex-list:character <- get deletion, before-top-of-screen:offset
     *editor <- put *editor, top-of-screen:offset, top
   }
 ]
 
 after <handle-redo> [
   {
-    deletion:address:delete-operation <- maybe-convert *op, delete:variant
-    break-unless deletion
-    start:address:shared:duplex-list:character <- get *deletion, delete-from:offset
-    end:address:shared:duplex-list:character <- get *deletion, delete-until:offset
+    deletion:delete-operation, is-delete?:boolean <- maybe-convert *op, delete:variant
+    break-unless is-delete?
+    start:address:shared:duplex-list:character <- get deletion, delete-from:offset
+    end:address:shared:duplex-list:character <- get deletion, delete-until:offset
     data:address:shared:duplex-list:character <- get *editor, data:offset
     remove-between start, end
     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
-    cursor-row <- get *deletion, after-row:offset
+    cursor-row <- get deletion, after-row:offset
     *editor <- put *editor, cursor-row:offset, cursor-row
-    cursor-column <- get *deletion, after-column:offset
+    cursor-column <- get deletion, after-column:offset
     *editor <- put *editor, cursor-column:offset, cursor-column
-    top:address:shared:duplex-list:character <- get *deletion, before-top-of-screen:offset
+    top:address:shared:duplex-list:character <- get deletion, before-top-of-screen:offset
     *editor <- put *editor, top-of-screen:offset, top
   }
 ]
@@ -1845,19 +1848,20 @@ before <delete-character-end> [
       # if previous operation was an insert, coalesce this operation with it
       break-unless undo
       op:address:shared:operation <- first undo
-      deletion:address:delete-operation <- maybe-convert *op, delete:variant
-      break-unless deletion
-      previous-coalesce-tag:number <- get *deletion, tag:offset
+      deletion:delete-operation, is-delete?:boolean <- maybe-convert *op, delete:variant
+      break-unless is-delete?
+      previous-coalesce-tag:number <- get deletion, tag:offset
       coalesce?:boolean <- equal previous-coalesce-tag, 2/coalesce-delete
       break-unless coalesce?
       delete-until:address:shared:duplex-list:character <- next before-cursor
-      *deletion <- put *deletion, delete-until:offset, delete-until
-      deleted-so-far:address:shared:duplex-list:character <- get *deletion, deleted-text:offset
+      deletion <- put deletion, delete-until:offset, delete-until
+      deleted-so-far:address:shared:duplex-list:character <- get deletion, deleted-text:offset
       deleted-so-far <- append deleted-so-far, deleted-cell
-      *deletion <- put *deletion, deleted-text:offset, deleted-so-far
-      *deletion <- put *deletion, after-row:offset, cursor-row
-      *deletion <- put *deletion, after-column:offset, cursor-column
-      *deletion <- put *deletion, after-top-of-screen:offset, top-after
+      deletion <- put deletion, deleted-text:offset, deleted-so-far
+      deletion <- put deletion, after-row:offset, cursor-row
+      deletion <- put deletion, after-column:offset, cursor-column
+      deletion <- put deletion, after-top-of-screen:offset, top-after
+      *op <- merge 2/delete-operation, deletion
       break +done-adding-delete-operation:label
     }
     # if not, create a new operation