about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--018type_abbreviations.cc14
-rw-r--r--021check_instruction.cc8
-rw-r--r--022constant.cc51
-rw-r--r--036lookup.cc8
-rw-r--r--042name.cc2
-rw-r--r--046check_type_by_name.cc2
-rw-r--r--054static_dispatch.cc27
-rw-r--r--056shape_shifting_recipe.cc12
-rw-r--r--057immutable.cc6
-rw-r--r--061text.mu12
-rw-r--r--064list.mu28
-rw-r--r--065duplex_list.mu62
-rw-r--r--066stream.mu2
-rw-r--r--069hash.cc2
-rw-r--r--072recipe.cc2
-rw-r--r--081print.mu2
-rw-r--r--088file.mu10
-rw-r--r--092socket.mu4
-rw-r--r--chessboard.mu26
-rw-r--r--continuation2.mu6
-rw-r--r--continuation4.mu4
-rw-r--r--continuation5.mu4
-rw-r--r--edit/001-editor.mu10
-rw-r--r--edit/002-typing.mu10
-rw-r--r--edit/003-shortcuts.mu4
-rw-r--r--edit/004-programming-environment.mu8
-rw-r--r--edit/005-sandbox.mu24
-rw-r--r--edit/006-sandbox-copy.mu2
-rw-r--r--edit/009-sandbox-test.mu6
-rw-r--r--edit/010-sandbox-trace.mu2
-rw-r--r--edit/011-errors.mu8
-rw-r--r--edit/012-editor-undo.mu2
-rw-r--r--filesystem.mu4
-rw-r--r--http-client.mu6
-rw-r--r--lambda-to-mu.mu20
-rw-r--r--mu.vim1
-rw-r--r--sandbox/001-editor.mu10
-rw-r--r--sandbox/002-typing.mu10
-rw-r--r--sandbox/003-shortcuts.mu4
-rw-r--r--sandbox/004-programming-environment.mu8
-rw-r--r--sandbox/005-sandbox.mu24
-rw-r--r--sandbox/006-sandbox-copy.mu2
-rw-r--r--sandbox/009-sandbox-test.mu6
-rw-r--r--sandbox/010-sandbox-trace.mu2
-rw-r--r--sandbox/011-errors.mu4
-rw-r--r--sandbox/012-editor-undo.mu2
-rw-r--r--screen.mu38
-rw-r--r--vimrc.vim3
48 files changed, 268 insertions, 246 deletions
diff --git a/018type_abbreviations.cc b/018type_abbreviations.cc
index 65905702..dad8bb9b 100644
--- a/018type_abbreviations.cc
+++ b/018type_abbreviations.cc
@@ -90,13 +90,6 @@ type foo = bar
 type foo = baz
 +error: 'type' conflict: 'foo' defined as both 'bar' and 'baz'
 
-:(scenario type_abbreviation_for_compound)
-type foo = address:number
-def main [
-  a:foo <- copy 0
-]
-+transform: product type after expanding abbreviations: ("address" "number")
-
 //: cleaning up type abbreviations between tests and before exiting
 
 :(before "End save_snapshots")
@@ -129,13 +122,6 @@ put(Type_abbreviations, "num", new_type_tree("number"));
 put(Type_abbreviations, "bool", new_type_tree("boolean"));
 put(Type_abbreviations, "char", new_type_tree("character"));
 
-:(scenario use_type_abbreviations_when_declaring_type_abbreviations)
-type foo = &:num
-def main [
-  a:foo <- copy 0
-]
-+transform: product type after expanding abbreviations: ("address" "number")
-
 //:: Expand type aliases before running.
 //: We'll do this in a transform so that we don't need to define abbreviations
 //: before we use them.
diff --git a/021check_instruction.cc b/021check_instruction.cc
index 48f90078..ee44161a 100644
--- a/021check_instruction.cc
+++ b/021check_instruction.cc
@@ -102,15 +102,19 @@ bool types_match(const reagent& to, const reagent& from) {
   if (is_literal(from)) {
     if (is_mu_array(to)) return false;
     // End Matching Types For Literal(to)
-    // allow writing 0 to any address
-    if (is_mu_address(to)) return from.name == "0";
     if (!to.type) return false;
+    if (is_mu_address(to)) return types_match_literal_to_address(from);
     // End Literal types_match Special-cases
     return size_of(to) == 1;  // literals are always scalars
   }
   return types_strictly_match(to, from);
 }
 
+bool types_match_literal_to_address(const reagent& from) {
+  // End Literal->Address types_match(from) Special-cases
+  return false;
+}
+
 //: copy arguments for later layers
 bool types_strictly_match(reagent/*copy*/ to, reagent/*copy*/ from) {
   // End Preprocess types_strictly_match(reagent to, reagent from)
diff --git a/022constant.cc b/022constant.cc
index 800e1b2b..6fc9a019 100644
--- a/022constant.cc
+++ b/022constant.cc
@@ -3,6 +3,8 @@
 :(before "End Mu Types Initialization")
 put(Type_ordinal, "literal-boolean", 0);
 
+//: 'true'
+
 :(scenario true)
 def main [
   1:boolean <- copy true
@@ -21,6 +23,8 @@ if (name == "true") {
 :(before "End Literal types_match Special-cases")
 if (is_mu_boolean(to)) return from.name == "false" || from.name == "true";
 
+//: 'false'
+
 :(scenario false)
 def main [
   1:boolean <- copy false
@@ -36,3 +40,50 @@ if (name == "false") {
   type = new type_tree("literal-boolean");
   set_value(0);
 }
+
+//: 'null'
+
+:(scenario null)
+def main [
+  1:address:number <- copy null
+]
++mem: storing 0 in location 1
+
+:(scenario null_has_wildcard_type)
+def main [
+  1:address:boolean <- copy null
+]
++mem: storing 0 in location 1
+
+:(before "End Mu Types Initialization")
+put(Type_ordinal, "literal-address", 0);
+
+:(before "End Parsing reagent")
+if (name == "null") {
+  if (type != NULL) {
+    raise << "'null' is a literal and can't take a type\n" << end();
+    return;
+  }
+  type = new type_tree("literal-address");
+  set_value(0);
+}
+
+:(before "End Literal->Address types_match(from) Special-cases")
+// allow writing null to any address
+if (from.name == "null") return true;
+
+//: scenarios for type abbreviations that we couldn't write until now
+
+:(scenario type_abbreviation_for_compound)
+type foo = address:number
+def main [
+  1:foo <- copy null
+]
++transform: product type after expanding abbreviations: ("address" "number")
+
+:(scenario use_type_abbreviations_when_declaring_type_abbreviations)
+type foo = &:num
+def main [
+  1:foo <- copy null
+]
++transform: product type after expanding abbreviations: ("address" "number")
diff --git a/036lookup.cc b/036lookup.cc
index a2647f5d..c95f1c8d 100644
--- a/036lookup.cc
+++ b/036lookup.cc
@@ -31,7 +31,7 @@ canonize(x);
 :(scenario store_to_0_fails)
 % Hide_errors = true;
 def main [
-  1:address:num <- copy 0
+  1:address:num <- copy null
   1:address:num/lookup <- copy 34
 ]
 -mem: storing 34 in location 0
@@ -41,7 +41,7 @@ def main [
 :(scenario lookup_0_fails)
 % Hide_errors = true;
 def main [
-  1:address:num <- copy 0
+  1:address:num <- copy null
   2:num <- copy 1:address:num/lookup
 ]
 +error: main: tried to lookup 0 in '2:num <- copy 1:address:num/lookup'
@@ -49,14 +49,14 @@ def main [
 :(scenario lookup_0_dumps_callstack)
 % Hide_errors = true;
 def main [
-  foo 0
+  foo null
 ]
 def foo [
   1:address:num <- next-input
   2:num <- copy 1:address:num/lookup
 ]
 +error: foo: tried to lookup 0 in '2:num <- copy 1:address:num/lookup'
-+error:   called from main: foo 0
++error:   called from main: foo null
 
 :(code)
 void canonize(reagent& x) {
diff --git a/042name.cc b/042name.cc
index f183962c..3cfc8587 100644
--- a/042name.cc
+++ b/042name.cc
@@ -245,7 +245,7 @@ else {
 
 :(scenario transform_names_transforms_container_elements)
 def main [
-  p:&:point <- copy 0
+  p:&:point <- copy null
   a:num <- get *p:&:point, y:offset
   b:num <- get *p:&:point, x:offset
 ]
diff --git a/046check_type_by_name.cc b/046check_type_by_name.cc
index 786714b4..c68a9bae 100644
--- a/046check_type_by_name.cc
+++ b/046check_type_by_name.cc
@@ -153,7 +153,7 @@ def foo [  # dummy
 ]
 def main [
   local-scope
-  0:space/names:foo <- copy 0  # specify surrounding space
+  0:space/names:foo <- copy null  # specify surrounding space
   x:bool <- copy true
   x:num/space:1 <- copy 34
   x/space:1 <- copy 35
diff --git a/054static_dispatch.cc b/054static_dispatch.cc
index 5f0fb03c..17ffc755 100644
--- a/054static_dispatch.cc
+++ b/054static_dispatch.cc
@@ -439,33 +439,6 @@ container foo [
 ]
 $error: 0
 
-:(scenario static_dispatch_prefers_literals_to_be_numbers_rather_than_addresses)
-def main [
-  1:num <- foo 0
-]
-def foo x:&:num -> y:num [
-  return 34
-]
-def foo x:num -> y:num [
-  return 35
-]
-+mem: storing 35 in location 1
-
-:(scenario static_dispatch_prefers_literals_to_be_numbers_rather_than_addresses_2)
-def main [
-  1:num <- foo 0 0
-]
-# Both variants need to bind 0 to address in first ingredient.
-# We still want to prefer the variant with a number rather than address for
-# _subsequent_ ingredients.
-def foo x:&:num y:&:num -> z:num [  # put the bad match before the good one
-  return 34
-]
-def foo x:&:num y:num -> z:num [
-  return 35
-]
-+mem: storing 35 in location 1
-
 :(scenario static_dispatch_on_non_literal_character_ignores_variant_with_numbers)
 % Hide_errors = true;
 def main [
diff --git a/056shape_shifting_recipe.cc b/056shape_shifting_recipe.cc
index f1145803..0c97fcdd 100644
--- a/056shape_shifting_recipe.cc
+++ b/056shape_shifting_recipe.cc
@@ -178,7 +178,7 @@ bool concrete_type_names_strictly_match(const type_tree* to, const type_tree* fr
   if (to->atom && is_type_ingredient_name(to->name)) return true;  // type ingredient matches anything
   if (!to->atom && to->right == NULL && to->left != NULL && to->left->atom && is_type_ingredient_name(to->left->name)) return true;
   if (from->atom && is_mu_address(to))
-    return from->name == "literal" && rhs_reagent.name == "0";
+    return from->name == "literal-address" && rhs_reagent.name == "null";
   if (!from->atom && !to->atom)
     return concrete_type_names_strictly_match(to->left, from->left, rhs_reagent)
         && concrete_type_names_strictly_match(to->right, from->right, rhs_reagent);
@@ -330,7 +330,7 @@ void compute_type_ingredient_mappings(const recipe& exemplar, const instruction&
     const reagent& exemplar_reagent = exemplar.ingredients.at(i);
     reagent/*copy*/ ingredient = inst.ingredients.at(i);
     canonize_type(ingredient);
-    if (is_mu_address(exemplar_reagent) && ingredient.name == "0") continue;  // assume it matches
+    if (is_mu_address(exemplar_reagent) && ingredient.name == "null") continue;  // assume it matches
     accumulate_type_ingredients(exemplar_reagent, ingredient, mappings, exemplar, inst, caller_recipe, error);
   }
   limit = min(SIZE(inst.products), SIZE(exemplar.products));
@@ -662,7 +662,7 @@ def main [
 def bar x:_t -> result:&:_t [
   local-scope
   load-ingredients
-  result <- copy 0
+  result <- copy null
 ]
 $error: 0
 
@@ -782,7 +782,7 @@ def foo x:_elem -> y:_elem [
 def main [
   local-scope
   # permit '0' to map to address to shape-shifting type-ingredient
-  1:&:char/raw <- foo 0
+  1:&:char/raw <- foo null
 ]
 def foo x:&:_elem -> y:&:_elem [
   local-scope
@@ -953,7 +953,7 @@ def foo x:&:_elem -> y:num [
 # version with headers padded with lots of unrelated concrete types
 def main [
   1:num <- copy 23
-  2:&:@:num <- copy 0
+  2:&:@:num <- copy null
   3:num <- foo 2:&:@:num, 1:num
 ]
 # variant with concrete type
@@ -1023,7 +1023,7 @@ def foo x:&:_elem -> y:num [
 
 :(scenario specialize_literal_as_address)
 def main [
-  1:num <- foo 0
+  1:num <- foo null
 ]
 # variant with concrete address type
 def foo x:&:num -> y:num [
diff --git a/057immutable.cc b/057immutable.cc
index d3c633b2..658f301b 100644
--- a/057immutable.cc
+++ b/057immutable.cc
@@ -110,7 +110,7 @@ def foo x:&:num [
   local-scope
   load-ingredients
   # modify the address, not the payload
-  x <- copy 0
+  x <- copy null
 ]
 $error: 0
 
@@ -207,7 +207,7 @@ def main [
 def foo a:&:foo [
   local-scope
   load-ingredients
-  b:foo <- merge 0
+  b:foo <- merge null
   # modify b, completely unrelated to immutable ingredient a
   x:&:@:num <- get b, x:offset
   *x <- put-index *x, 0, 34
@@ -596,7 +596,7 @@ container test-list [
 def foo x:&:test-list/contained-in:result -> result:&:test-list [
   local-scope
   load-ingredients
-  result <- copy 0
+  result <- copy null
 ]
 $error: 0
 
diff --git a/061text.mu b/061text.mu
index 8ef19a9d..4d46319b 100644
--- a/061text.mu
+++ b/061text.mu
@@ -86,10 +86,10 @@ scenario text-equal-with-empty [
 scenario text-equal-with-null [
   local-scope
   x:text <- new [abcd]
-  y:text <- copy 0
+  y:text <- copy null
   run [
-    10:bool/raw <- equal x, 0
-    11:bool/raw <- equal 0, x
+    10:bool/raw <- equal x, null
+    11:bool/raw <- equal null, x
     12:bool/raw <- equal x, y
     13:bool/raw <- equal y, x
     14:bool/raw <- equal y, y
@@ -339,7 +339,7 @@ def buffer-to-array in:&:buffer:_elem -> result:&:@:_elem [
   local-scope
   load-inputs
   # propagate null buffer
-  return-unless in, 0
+  return-unless in, null
   len:num <- get *in, length:offset
   s:&:@:_elem <- get *in, data:offset
   # we can't just return s because it is usually the wrong length
@@ -406,7 +406,7 @@ scenario text-append-1 [
 
 scenario text-append-null [
   local-scope
-  x:text <- copy 0
+  x:text <- copy null
   y:text <- new [ world!]
   run [
     z:text <- append x, y
@@ -420,7 +420,7 @@ scenario text-append-null [
 scenario text-append-null-2 [
   local-scope
   x:text <- new [hello,]
-  y:text <- copy 0
+  y:text <- copy null
   run [
     z:text <- append x, y
     10:@:char/raw <- copy *z
diff --git a/064list.mu b/064list.mu
index eca3ded1..d669ec2c 100644
--- a/064list.mu
+++ b/064list.mu
@@ -30,7 +30,7 @@ def rest in:&:list:_elem -> result:&:list:_elem/contained-in:in [
 scenario list-handling [
   run [
     local-scope
-    x:&:list:num <- push 3, 0
+    x:&:list:num <- push 3, null
     x <- push 4, x
     x <- push 5, x
     10:num/raw <- first x
@@ -73,7 +73,7 @@ def insert x:_elem, in:&:list:_elem -> in:&:list:_elem [
 
 scenario inserting-into-list [
   local-scope
-  list:&:list:num <- push 3, 0
+  list:&:list:num <- push 3, null
   list <- push 4, list
   list <- push 5, list
   run [
@@ -99,7 +99,7 @@ scenario inserting-into-list [
 
 scenario inserting-at-end-of-list [
   local-scope
-  list:&:list:num <- push 3, 0
+  list:&:list:num <- push 3, null
   list <- push 4, list
   list <- push 5, list
   run [
@@ -126,7 +126,7 @@ scenario inserting-at-end-of-list [
 
 scenario inserting-after-start-of-list [
   local-scope
-  list:&:list:num <- push 3, 0
+  list:&:list:num <- push 3, null
   list <- push 4, list
   list <- push 5, list
   run [
@@ -160,7 +160,7 @@ def remove x:&:list:_elem/contained-in:in, in:&:list:_elem -> in:&:list:_elem [
   return-unless x
   next-node:&:list:_elem <- rest x
   # clear next pointer of 'x'
-  *x <- put *x, next:offset, 0
+  *x <- put *x, next:offset, null
   # if 'x' is at the head of 'in', return the new head
   at-head?:bool <- equal x, in
   return-if at-head?, next-node
@@ -180,13 +180,13 @@ def remove x:&:list:_elem/contained-in:in, in:&:list:_elem -> in:&:list:_elem [
 
 scenario removing-from-list [
   local-scope
-  list:&:list:num <- push 3, 0
+  list:&:list:num <- push 3, null
   list <- push 4, list
   list <- push 5, list
   run [
     list2:&:list:num <- rest list  # second element
     list <- remove list2, list
-    10:bool/raw <- equal list2, 0
+    10:bool/raw <- equal list2, null
     # check structure like before
     list2 <- copy list
     11:num/raw <- first list2
@@ -204,7 +204,7 @@ scenario removing-from-list [
 
 scenario removing-from-start-of-list [
   local-scope
-  list:&:list:num <- push 3, 0
+  list:&:list:num <- push 3, null
   list <- push 4, list
   list <- push 5, list
   run [
@@ -225,7 +225,7 @@ scenario removing-from-start-of-list [
 
 scenario removing-from-end-of-list [
   local-scope
-  list:&:list:num <- push 3, 0
+  list:&:list:num <- push 3, null
   list <- push 4, list
   list <- push 5, list
   run [
@@ -233,7 +233,7 @@ scenario removing-from-end-of-list [
     list2:&:list:num <- rest list
     list2 <- rest list2
     list <- remove list2, list
-    10:bool/raw <- equal list2, 0
+    10:bool/raw <- equal list2, null
     # check structure like before
     list2 <- copy list
     11:num/raw <- first list2
@@ -251,7 +251,7 @@ scenario removing-from-end-of-list [
 
 scenario removing-from-singleton-list [
   local-scope
-  list:&:list:num <- push 3, 0
+  list:&:list:num <- push 3, null
   run [
     list <- remove list, list
     1:num/raw <- deaddress list
@@ -275,7 +275,7 @@ def reverse list:&:list:_elem temp:&:list:_elem/contained-in:result -> result:&:
 
 scenario reverse-list [
   local-scope
-  list:&:list:num <- push 1, 0
+  list:&:list:num <- push 1, null
   list <- push 2, list
   list <- push 3, list
   run [
@@ -291,7 +291,7 @@ scenario reverse-list [
 
 scenario stash-list [
   local-scope
-  list:&:list:num <- push 1, 0
+  list:&:list:num <- push 1, null
   list <- push 2, list
   list <- push 3, list
   run [
@@ -356,7 +356,7 @@ def to-buffer in:&:list:_elem, buf:&:buffer:char -> buf:&:buffer:char [
 
 scenario stash-empty-list [
   local-scope
-  x:&:list:num <- copy 0
+  x:&:list:num <- copy null
   run [
     stash x
   ]
diff --git a/065duplex_list.mu b/065duplex_list.mu
index 129da96c..7d369186 100644
--- a/065duplex_list.mu
+++ b/065duplex_list.mu
@@ -10,7 +10,7 @@ def push x:_elem, in:&:duplex-list:_elem/contained-in:result -> result:&:duplex-
   local-scope
   load-inputs
   result:&:duplex-list:_elem <- new {(duplex-list _elem): type}
-  *result <- merge x, in, 0
+  *result <- merge x, in, null
   return-unless in
   put *in, prev:offset, result
 ]
@@ -18,21 +18,27 @@ def push x:_elem, in:&:duplex-list:_elem/contained-in:result -> result:&:duplex-
 def first in:&:duplex-list:_elem -> result:_elem [
   local-scope
   load-inputs
-  return-unless in, 0
+  {
+    break-if in
+    zero:&:_elem <- new _elem:type
+    zero-result:_elem <- copy *zero
+    abandon zero
+    return zero-result
+  }
   result <- get *in, value:offset
 ]
 
 def next in:&:duplex-list:_elem -> result:&:duplex-list:_elem/contained-in:in [
   local-scope
   load-inputs
-  return-unless in, 0
+  return-unless in, null
   result <- get *in, next:offset
 ]
 
 def prev in:&:duplex-list:_elem -> result:&:duplex-list:_elem/contained-in:in [
   local-scope
   load-inputs
-  return-unless in, 0
+  return-unless in, null
   result <- get *in, prev:offset
   return result
 ]
@@ -43,7 +49,7 @@ scenario duplex-list-handling [
     # reserve locations 0-9 to check for missing null check
     10:num/raw <- copy 34
     11:num/raw <- copy 35
-    list:&:duplex-list:num <- push 3, 0
+    list:&:duplex-list:num <- push 3, null
     list <- push 4, list
     list <- push 5, list
     list2:&:duplex-list:num <- copy list
@@ -108,7 +114,7 @@ def insert x:_elem, in:&:duplex-list:_elem -> in:&:duplex-list:_elem [
 
 scenario inserting-into-duplex-list [
   local-scope
-  list:&:duplex-list:num <- push 3, 0
+  list:&:duplex-list:num <- push 3, null
   list <- push 4, list
   list <- push 5, list
   run [
@@ -145,7 +151,7 @@ scenario inserting-into-duplex-list [
 
 scenario inserting-at-end-of-duplex-list [
   local-scope
-  list:&:duplex-list:num <- push 3, 0
+  list:&:duplex-list:num <- push 3, null
   list <- push 4, list
   list <- push 5, list
   run [
@@ -183,7 +189,7 @@ scenario inserting-at-end-of-duplex-list [
 
 scenario inserting-after-start-of-duplex-list [
   local-scope
-  list:&:duplex-list:num <- push 3, 0
+  list:&:duplex-list:num <- push 3, null
   list <- push 4, list
   list <- push 5, list
   run [
@@ -229,8 +235,8 @@ def remove x:&:duplex-list:_elem/contained-in:in, in:&:duplex-list:_elem -> in:&
   next-node:&:duplex-list:_elem <- get *x, next:offset
   prev-node:&:duplex-list:_elem <- get *x, prev:offset
   # null x's pointers
-  *x <- put *x, next:offset, 0
-  *x <- put *x, prev:offset, 0
+  *x <- put *x, next:offset, null
+  *x <- put *x, prev:offset, null
   # if next-node is not null, set its prev pointer
   {
     break-unless next-node
@@ -249,13 +255,13 @@ def remove x:&:duplex-list:_elem/contained-in:in, in:&:duplex-list:_elem -> in:&
 
 scenario removing-from-duplex-list [
   local-scope
-  list:&:duplex-list:num <- push 3, 0
+  list:&:duplex-list:num <- push 3, null
   list <- push 4, list
   list <- push 5, list
   run [
     list2:&:duplex-list:num <- next list  # second element
     list <- remove list2, list
-    10:bool/raw <- equal list2, 0
+    10:bool/raw <- equal list2, null
     # check structure like before
     list2 <- copy list
     11:num/raw <- first list2
@@ -278,7 +284,7 @@ scenario removing-from-duplex-list [
 
 scenario removing-from-start-of-duplex-list [
   local-scope
-  list:&:duplex-list:num <- push 3, 0
+  list:&:duplex-list:num <- push 3, null
   list <- push 4, list
   list <- push 5, list
   run [
@@ -304,7 +310,7 @@ scenario removing-from-start-of-duplex-list [
 
 scenario removing-from-end-of-duplex-list [
   local-scope
-  list:&:duplex-list:num <- push 3, 0
+  list:&:duplex-list:num <- push 3, null
   list <- push 4, list
   list <- push 5, list
   run [
@@ -312,7 +318,7 @@ scenario removing-from-end-of-duplex-list [
     list2:&:duplex-list:num <- next list
     list2 <- next list2
     list <- remove list2, list
-    10:bool/raw <- equal list2, 0
+    10:bool/raw <- equal list2, null
     # check structure like before
     list2 <- copy list
     11:num/raw <- first list2
@@ -335,7 +341,7 @@ scenario removing-from-end-of-duplex-list [
 
 scenario removing-from-singleton-duplex-list [
   local-scope
-  list:&:duplex-list:num <- push 3, 0
+  list:&:duplex-list:num <- push 3, null
   run [
     list <- remove list, list
     1:num/raw <- deaddress list
@@ -364,7 +370,7 @@ def remove x:&:duplex-list:_elem/contained-in:in, n:num, in:&:duplex-list:_elem
 
 scenario removing-multiple-from-duplex-list [
   local-scope
-  list:&:duplex-list:num <- push 3, 0
+  list:&:duplex-list:num <- push 3, null
   list <- push 4, list
   list <- push 5, list
   run [
@@ -391,21 +397,21 @@ def remove-between start:&:duplex-list:_elem, end:&:duplex-list:_elem/contained-
   assert next, [malformed duplex list]
   # start->next->prev = 0
   # start->next = end
-  *next <- put *next, prev:offset, 0
+  *next <- put *next, prev:offset, null
   *start <- put *start, next:offset, end
   return-unless end
   # end->prev->next = 0
   # end->prev = start
   prev:&:duplex-list:_elem <- get *end, prev:offset
   assert prev, [malformed duplex list - 2]
-  *prev <- put *prev, next:offset, 0
+  *prev <- put *prev, next:offset, null
   *end <- put *end, prev:offset, start
 ]
 
 scenario remove-range [
   # construct a duplex list with six elements [13, 14, 15, 16, 17, 18]
   local-scope
-  list:&:duplex-list:num <- push 18, 0
+  list:&:duplex-list:num <- push 18, null
   list <- push 17, list
   list <- push 16, list
   list <- push 15, list
@@ -416,7 +422,7 @@ scenario remove-range [
     # first pointer: to the third element
     list2:&:duplex-list:num <- next list
     list2 <- next list2
-    list2 <- remove-between list2, 0
+    list2 <- remove-between list2, null
     # now check the list
     10:num/raw <- get *list, value:offset
     list <- next list
@@ -436,7 +442,7 @@ scenario remove-range [
 scenario remove-range-to-final [
   local-scope
   # construct a duplex list with six elements [13, 14, 15, 16, 17, 18]
-  list:&:duplex-list:num <- push 18, 0
+  list:&:duplex-list:num <- push 18, null
   list <- push 17, list
   list <- push 16, list
   list <- push 15, list
@@ -471,7 +477,7 @@ scenario remove-range-to-final [
 scenario remove-range-empty [
   local-scope
   # construct a duplex list with three elements [13, 14, 15]
-  list:&:duplex-list:num <- push 15, 0
+  list:&:duplex-list:num <- push 15, null
   list <- push 14, list
   list <- push 13, list
   run [
@@ -498,7 +504,7 @@ scenario remove-range-empty [
 scenario remove-range-to-end [
   local-scope
   # construct a duplex list with six elements [13, 14, 15, 16, 17, 18]
-  list:&:duplex-list:num <- push 18, 0
+  list:&:duplex-list:num <- push 18, null
   list <- push 17, list
   list <- push 16, list
   list <- push 15, list
@@ -507,7 +513,7 @@ scenario remove-range-to-end [
   run [
     # remove the third element and beyond
     list2:&:duplex-list:num <- next list
-    remove-between list2, 0
+    remove-between list2, null
     # now check the list
     10:num/raw <- get *list, value:offset
     list <- next list
@@ -604,7 +610,7 @@ def match x:&:duplex-list:_elem, y:&:@:_elem -> result:bool [
 
 scenario duplex-list-match [
   local-scope
-  list:&:duplex-list:char <- push 97/a, 0
+  list:&:duplex-list:char <- push 97/a, null
   list <- push 98/b, list
   list <- push 99/c, list
   list <- push 100/d, list
@@ -649,7 +655,7 @@ def dump-from x:&:duplex-list:_elem [
 
 scenario stash-duplex-list [
   local-scope
-  list:&:duplex-list:num <- push 1, 0
+  list:&:duplex-list:num <- push 1, null
   list <- push 2, list
   list <- push 3, list
   run [
@@ -714,7 +720,7 @@ def to-buffer in:&:duplex-list:_elem, buf:&:buffer:char -> buf:&:buffer:char [
 
 scenario stash-empty-duplex-list [
   local-scope
-  x:&:duplex-list:num <- copy 0
+  x:&:duplex-list:num <- copy null
   run [
     stash x
   ]
diff --git a/066stream.mu b/066stream.mu
index 86ce26ed..b3202f65 100644
--- a/066stream.mu
+++ b/066stream.mu
@@ -7,7 +7,7 @@ container stream:_elem [
 def new-stream s:&:@:_elem -> result:&:stream:_elem [
   local-scope
   load-inputs
-  return-unless s, 0/null
+  return-unless s, null
   result <- new {(stream _elem): type}
   *result <- put *result, index:offset, 0
   *result <- put *result, data:offset, s
diff --git a/069hash.cc b/069hash.cc
index 4400c1e8..8a698d38 100644
--- a/069hash.cc
+++ b/069hash.cc
@@ -183,7 +183,7 @@ def main [
 
 :(scenario hash_of_zero_address)
 def main [
-  1:&:num <- copy 0
+  1:&:num <- copy null
   2:num <- hash 1:&:num
 ]
 +mem: storing 0 in location 2
diff --git a/072recipe.cc b/072recipe.cc
index dd71830d..439b2000 100644
--- a/072recipe.cc
+++ b/072recipe.cc
@@ -389,7 +389,7 @@ if (is_mu_recipe(to)) {
 :(scenario call_variable_compound_ingredient)
 def main [
   {1: (recipe (address number) -> number)} <- copy f
-  2:&:num <- copy 0
+  2:&:num <- copy null
   3:num <- call {1: (recipe (address number) -> number)}, 2:&:num
 ]
 def f x:&:num -> y:num [
diff --git a/081print.mu b/081print.mu
index df58f831..98ea0f21 100644
--- a/081print.mu
+++ b/081print.mu
@@ -909,6 +909,6 @@ def print screen:&:screen, n:&:_elem -> screen:&:screen [
     break-if bg-color-found?
     bg-color <- copy 0/black
   }
-  n2:num <- copy n
+  n2:num <- deaddress n
   screen <- print screen, n2, color, bg-color
 ]
diff --git a/088file.mu b/088file.mu
index d9c3391d..da3e35d3 100644
--- a/088file.mu
+++ b/088file.mu
@@ -30,7 +30,7 @@ def start-reading resources:&:resources, filename:text -> contents:&:source:char
   }
   # real file system
   file:num <- $open-file-for-reading filename
-  return-unless file, 0/contents, true/error
+  return-unless file, null/no-contents, true/error
   contents:&:source:char, sink:&:sink:char <- new-channel 30
   start-running receive-from-file file, sink
 ]
@@ -39,7 +39,7 @@ def slurp resources:&:resources, filename:text -> contents:text, error?:bool [
   local-scope
   load-inputs
   source:&:source:char, error?:bool <- start-reading resources, filename
-  return-if error?, 0/contents
+  return-if error?, null/no-contents
   buf:&:buffer:char <- new-buffer 30/capacity
   {
     c:char, done?:bool, source <- read source
@@ -70,7 +70,7 @@ def start-reading-from-fake-resource resources:&:resources, resource:text -> con
     start-running receive-from-text curr-contents, sink
     return
   }
-  return 0/not-found, true/error-found
+  return null/no-such-resource, true/error-found
 ]
 
 def receive-from-file file:num, sink:&:sink:char -> sink:&:sink:char [
@@ -115,7 +115,7 @@ def start-writing resources:&:resources, filename:text -> sink:&:sink:char, rout
   }
   # real file system
   file:num <- $open-file-for-writing filename
-  return-unless file, 0/sink, 0/routine-id, true/error
+  return-unless file, null/sink, 0/routine-id, true/error
   {
     break-if file
     msg:text <- append [no such file: ] filename
@@ -175,7 +175,7 @@ def transmit-to-fake-resource resources:&:resources, filename:text, source:&:sou
   contents:text <- buffer-to-array buf
   new-resource:resource <- merge filename, contents
   # write to resources
-  curr-filename:text <- copy 0
+  curr-filename:text <- copy null
   data:&:@:resource <- get *resources, data:offset
   # replace file contents if it already exists
   i:num <- copy 0
diff --git a/092socket.mu b/092socket.mu
index be4e5bb2..b0dca4b7 100644
--- a/092socket.mu
+++ b/092socket.mu
@@ -7,14 +7,14 @@ scenario example-server-test [
   # that way repeatedly running the test will give ports time to timeout and
   # close before reusing them
   make-random-nondeterministic
-  port:num <- random-in-range 0/real-random-numbers, 8000, 8100
+  port:num <- random-in-range null/real-random-numbers, 8000, 8100
   run [
     socket:num <- $open-server-socket port
     assert socket, [ 
 F - example-server-test: $open-server-socket failed]
     handler-routine:number <- start-running serve-one-request socket, example-handler
   ]
-  source:&:source:char <- start-reading-from-network 0/real-resources, [localhost/], port
+  source:&:source:char <- start-reading-from-network null/real-resources, [localhost/], port
   response:text <- drain source
   10:@:char/raw <- copy *response
   memory-should-contain [
diff --git a/chessboard.mu b/chessboard.mu
index 74cda69a..09c85188 100644
--- a/chessboard.mu
+++ b/chessboard.mu
@@ -4,16 +4,16 @@
 def main [
   local-scope
   open-console  # take control of screen, keyboard and mouse
-  clear-screen 0/screen  # non-scrolling app
+  clear-screen null/screen  # non-scrolling app
 
   # The chessboard function takes keyboard and screen objects as inputs.
   #
   # In Mu it is good form (though not required) to explicitly state what
   # hardware a function needs.
   #
-  # Here the console and screen are both 0, which usually indicates real
+  # Here the console and screen are both null, which usually indicates real
   # hardware rather than a fake for testing as you'll see below.
-  chessboard 0/screen, 0/console
+  chessboard null/screen, null/console
 
   close-console  # clean up screen, keyboard and mouse
 ]
@@ -249,27 +249,27 @@ def read-move stdin:&:source:char, screen:&:screen -> result:&:move, quit?:bool,
   local-scope
   load-inputs
   from-file:num, quit?:bool, error?:bool <- read-file stdin, screen
-  return-if quit?, 0/dummy
-  return-if error?, 0/dummy
+  return-if quit?, null/dummy
+  return-if error?, null/dummy
   # construct the move object
   result:&:move <- new move:type
   *result <- put *result, from-file:offset, from-file
   from-rank:num, quit?, error? <- read-rank stdin, screen
-  return-if quit?, 0/dummy
-  return-if error?, 0/dummy
+  return-if quit?, null/dummy
+  return-if error?, null/dummy
   *result <- put *result, from-rank:offset, from-rank
   error? <- expect-from-channel stdin, 45/dash, screen
-  return-if error?, 0/dummy, false/quit
+  return-if error?, null/dummy, false/quit
   to-file:num, quit?, error? <- read-file stdin, screen
-  return-if quit?, 0/dummy
-  return-if error?, 0/dummy
+  return-if quit?, null/dummy
+  return-if error?, null/dummy
   *result <- put *result, to-file:offset, to-file
   to-rank:num, quit?, error? <- read-rank stdin, screen
-  return-if quit?, 0/dummy
-  return-if error?, 0/dummy
+  return-if quit?, null/dummy
+  return-if error?, null/dummy
   *result <- put *result, to-rank:offset, to-rank
   error? <- expect-from-channel stdin, 10/newline, screen
-  return-if error?, 0/dummy, false/quit
+  return-if error?, null/dummy, false/quit
 ]
 
 # valid values for file: 0-7
diff --git a/continuation2.mu b/continuation2.mu
index b71e9a11..45a65e9f 100644
--- a/continuation2.mu
+++ b/continuation2.mu
@@ -13,7 +13,7 @@
 
 def main [
   local-scope
-  l:&:list:num <- copy 0
+  l:&:list:num <- copy null
   l <- push 3, l
   l <- push 2, l
   l <- push 1, l
@@ -30,8 +30,8 @@ def create-yielder l:&:list:num -> n:num, done?:bool [
   local-scope
   load-inputs
   return-continuation-until-mark 100/mark
-  done? <- equal l, 0/nil
-  return-if done?, false
+  done? <- equal l, null
+  return-if done?, 0/dummy
   n <- first l
   l <- rest l
 ]
diff --git a/continuation4.mu b/continuation4.mu
index d4eb0641..1a523fe9 100644
--- a/continuation4.mu
+++ b/continuation4.mu
@@ -15,7 +15,7 @@
 
 def main [
   local-scope
-  l:&:list:num <- copy 0
+  l:&:list:num <- copy null
   l <- push 3, l
   l <- push 2, l
   l <- push 1, l
@@ -32,7 +32,7 @@ def create-yielder l:&:list:num -> n:num, done?:bool [
   local-scope
   load-inputs
   {
-    done? <- equal l, 0
+    done? <- equal l, null
     break-if done?
     n <- first l
     l <- rest l
diff --git a/continuation5.mu b/continuation5.mu
index 4ae05614..295cb9c9 100644
--- a/continuation5.mu
+++ b/continuation5.mu
@@ -16,7 +16,7 @@
 
 def main [
   local-scope
-  l:&:list:num <- copy 0
+  l:&:list:num <- copy null
   l <- push 3, l
   l <- push 2, l
   l <- push 1, l
@@ -36,7 +36,7 @@ def create-yielder l:&:list:num -> n:num, done?:bool [
   load-inputs
   a:num <- copy 0
   {
-    done? <- equal l, 0
+    done? <- equal l, null
     break-if done?
     n <- first l
     l <- rest l
diff --git a/edit/001-editor.mu b/edit/001-editor.mu
index 8855395a..963bb1cf 100644
--- a/edit/001-editor.mu
+++ b/edit/001-editor.mu
@@ -6,10 +6,10 @@ def main text:text [
   local-scope
   load-inputs
   open-console
-  clear-screen 0/screen  # non-scrolling app
+  clear-screen null/screen  # non-scrolling app
   e:&:editor <- new-editor text, 0/left, 5/right
-  render 0/screen, e
-  wait-for-event 0/console
+  render null/screen, e
+  wait-for-event null/console
   close-console
 ]
 
@@ -61,7 +61,7 @@ def new-editor s:text, left:num, right:num -> result:&:editor [
   *result <- put *result, cursor-row:offset, 1/top
   *result <- put *result, cursor-column:offset, left
   # initialize empty contents
-  init:&:duplex-list:char <- push 167/§, 0/tail
+  init:&:duplex-list:char <- push 167/§, null
   *result <- put *result, data:offset, init
   *result <- put *result, top-of-screen:offset, init
   *result <- put *result, before-cursor:offset, init
@@ -80,7 +80,7 @@ scenario editor-initializes-without-data [
   local-scope
   assume-screen 5/width, 3/height
   run [
-    e:&:editor <- new-editor 0/data, 2/left, 5/right
+    e:&:editor <- new-editor null/data, 2/left, 5/right
     2:editor/raw <- copy *e
   ]
   memory-should-contain [
diff --git a/edit/002-typing.mu b/edit/002-typing.mu
index a67fcf3c..ef3f25d2 100644
--- a/edit/002-typing.mu
+++ b/edit/002-typing.mu
@@ -6,10 +6,10 @@ def! main text:text [
   local-scope
   load-inputs
   open-console
-  clear-screen 0/screen  # non-scrolling app
+  clear-screen null/screen  # non-scrolling app
   editor:&:editor <- new-editor text, 5/left, 45/right
-  editor-render 0/screen, editor
-  editor-event-loop 0/screen, 0/console, editor
+  editor-render null/screen, editor
+  editor-event-loop null/screen, null/console, editor
   close-console
 ]
 
@@ -223,7 +223,7 @@ def insert-at-cursor editor:&:editor, c:char, screen:&:screen -> go-render?:bool
   next:&:duplex-list:char <- next before-cursor
   {
     # at end of all text? no need to scroll? just print the character and leave
-    at-end?:bool <- equal next, 0/null
+    at-end?:bool <- equal next, null
     break-unless at-end?
     bottom:num <- subtract screen-height, 1
     at-bottom?:bool <- equal save-row, bottom
@@ -701,7 +701,7 @@ after <insert-character-special-case> [
     just-before-wrap?:bool <- greater-or-equal cursor-column, before-wrap-column
     next:&:duplex-list:char <- next before-cursor
     # at end of line? next == 0 || next.value == 10/newline
-    at-end-of-line?:bool <- equal next, 0
+    at-end-of-line?:bool <- equal next, null
     {
       break-if at-end-of-line?
       next-character:char <- get *next, value:offset
diff --git a/edit/003-shortcuts.mu b/edit/003-shortcuts.mu
index b8f49731..872dfcea 100644
--- a/edit/003-shortcuts.mu
+++ b/edit/003-shortcuts.mu
@@ -113,7 +113,7 @@ def delete-before-cursor editor:&:editor, screen:&:screen -> go-render?:bool, ba
   data:&:duplex-list:char <- get *editor, data:offset
   # if at start of text (before-cursor at § sentinel), return
   prev:&:duplex-list:char <- prev before-cursor
-  return-unless prev, false/no-more-render, 0/nothing-deleted
+  return-unless prev, false/no-more-render, null/nothing-deleted
   trace 10, [app], [delete-before-cursor]
   original-row:num <- get *editor, cursor-row:offset
   scroll?:bool <- move-cursor-coordinates-left editor
@@ -2616,7 +2616,7 @@ def delete-to-end-of-line editor:&:editor -> result:&:duplex-list:char, editor:&
   start:&:duplex-list:char <- get *editor, before-cursor:offset
   end:&:duplex-list:char <- next start
   {
-    at-end-of-text?:bool <- equal end, 0/null
+    at-end-of-text?:bool <- equal end, null
     break-if at-end-of-text?
     curr:char <- get *end, value:offset
     at-end-of-line?:bool <- equal curr, 10/newline
diff --git a/edit/004-programming-environment.mu b/edit/004-programming-environment.mu
index 1065dd64..dec8a2d5 100644
--- a/edit/004-programming-environment.mu
+++ b/edit/004-programming-environment.mu
@@ -6,10 +6,10 @@
 def! main [
   local-scope
   open-console
-  clear-screen 0/screen  # non-scrolling app
-  env:&:environment <- new-programming-environment 0/filesystem, 0/screen
-  render-all 0/screen, env, render
-  event-loop 0/screen, 0/console, env, 0/filesystem
+  clear-screen null/screen  # non-scrolling app
+  env:&:environment <- new-programming-environment null/filesystem, null/screen
+  render-all null/screen, env, render
+  event-loop null/screen, null/console, env, null/filesystem
 ]
 
 container environment [
diff --git a/edit/005-sandbox.mu b/edit/005-sandbox.mu
index ae8561a9..96ec804d 100644
--- a/edit/005-sandbox.mu
+++ b/edit/005-sandbox.mu
@@ -10,11 +10,11 @@
 def! main [
   local-scope
   open-console
-  clear-screen 0/screen  # non-scrolling app
-  env:&:environment <- new-programming-environment 0/filesystem, 0/screen
-  env <- restore-sandboxes env, 0/filesystem
-  render-all 0/screen, env, render
-  event-loop 0/screen, 0/console, env, 0/filesystem
+  clear-screen null/screen  # non-scrolling app
+  env:&:environment <- new-programming-environment null/filesystem, null/screen
+  env <- restore-sandboxes env, null/filesystem
+  render-all null/screen, env, render
+  event-loop null/screen, null/console, env, null/filesystem
 ]
 
 container environment [
@@ -170,7 +170,7 @@ def run-sandboxes env:&:environment, resources:&:resources, screen:&:screen -> e
     # needs to be before running them, in case we die when running
     save-sandboxes env, resources
     # clear sandbox editor
-    init:&:duplex-list:char <- push 167/§, 0/tail
+    init:&:duplex-list:char <- push 167/§, null
     *current-sandbox <- put *current-sandbox, data:offset, init
     *current-sandbox <- put *current-sandbox, top-of-screen:offset, init
   }
@@ -475,8 +475,8 @@ def restore-sandboxes env:&:environment, resources:&:resources -> env:&:environm
   load-inputs
   # read all scenarios, pushing them to end of a list of scenarios
   idx:num <- copy 0
-  curr:&:sandbox <- copy 0
-  prev:&:sandbox <- copy 0
+  curr:&:sandbox <- copy null
+  prev:&:sandbox <- copy null
   {
     filename:text <- append [lesson/], idx
     contents:text <- slurp resources, filename
@@ -686,7 +686,7 @@ def editor-contents editor:&:editor -> result:text [
   # skip § sentinel
   assert curr, [editor without data is illegal; must have at least a sentinel]
   curr <- next curr
-  return-unless curr, 0
+  return-unless curr, null
   {
     break-unless curr
     c:char <- get *curr, value:offset
@@ -939,15 +939,15 @@ after <global-keypress> [
 ]
 
 # sandbox belonging to 'env' whose next-sandbox is 'in'
-# return 0 if there's no such sandbox, either because 'in' doesn't exist in 'env', or because it's the first sandbox
+# return null if there's no such sandbox, either because 'in' doesn't exist in 'env', or because it's the first sandbox
 def previous-sandbox env:&:environment, in:&:sandbox -> out:&:sandbox [
   local-scope
   load-inputs
   curr:&:sandbox <- get *env, sandbox:offset
-  return-unless curr, 0/nil
+  return-unless curr, null
   next:&:sandbox <- get *curr, next-sandbox:offset
   {
-    return-unless next, 0/nil
+    return-unless next, null
     found?:bool <- equal next, in
     break-if found?
     curr <- copy next
diff --git a/edit/006-sandbox-copy.mu b/edit/006-sandbox-copy.mu
index 04d22ab5..6af72f77 100644
--- a/edit/006-sandbox-copy.mu
+++ b/edit/006-sandbox-copy.mu
@@ -184,7 +184,7 @@ def find-sandbox env:&:environment, click-row:num -> result:&:sandbox [
     curr-sandbox <- get *curr-sandbox, next-sandbox:offset
     loop
   }
-  return 0/not-found
+  return null/not-found
 ]
 
 def click-on-sandbox-area? click-row:num, click-column:num, env:&:environment -> result:bool [
diff --git a/edit/009-sandbox-test.mu b/edit/009-sandbox-test.mu
index b871107e..52c1e909 100644
--- a/edit/009-sandbox-test.mu
+++ b/edit/009-sandbox-test.mu
@@ -171,9 +171,9 @@ def find-click-in-sandbox-output env:&:environment, click-row:num -> sandbox:&:s
   }
   # return sandbox if click is in its output region
   response-starting-row:num <- get *sandbox, response-starting-row-on-screen:offset
-  return-unless response-starting-row, 0/no-click-in-sandbox-output, 0/sandbox-index
+  return-unless response-starting-row, null/no-click-in-sandbox-output, 0/sandbox-index
   click-in-response?:bool <- greater-or-equal click-row, response-starting-row
-  return-unless click-in-response?, 0/no-click-in-sandbox-output, 0/sandbox-index
+  return-unless click-in-response?, null/no-click-in-sandbox-output, 0/sandbox-index
   return sandbox, sandbox-index
 ]
 
@@ -184,7 +184,7 @@ def toggle-expected-response sandbox:&:sandbox -> sandbox:&:sandbox [
   {
     # if expected-response is set, reset
     break-unless expected-response
-    *sandbox <- put *sandbox, expected-response:offset, 0
+    *sandbox <- put *sandbox, expected-response:offset, null
   }
   {
     # if not, set expected response to the current response
diff --git a/edit/010-sandbox-trace.mu b/edit/010-sandbox-trace.mu
index cc8b2805..23b88833 100644
--- a/edit/010-sandbox-trace.mu
+++ b/edit/010-sandbox-trace.mu
@@ -235,7 +235,7 @@ def find-click-in-sandbox-code env:&:environment, click-row:num -> sandbox:&:san
   click-on-sandbox-code?:bool <- and click-above-response?, click-below-menu?
   {
     break-if click-on-sandbox-code?
-    return 0/no-click-in-sandbox-output
+    return null/no-click-in-sandbox-output
   }
   return sandbox
 ]
diff --git a/edit/011-errors.mu b/edit/011-errors.mu
index 373193d6..47258815 100644
--- a/edit/011-errors.mu
+++ b/edit/011-errors.mu
@@ -310,7 +310,7 @@ scenario run-updates-errors-for-shape-shifting-recipes [
       |recipe foo x:_elem -> z:_elem [|
       |  local-scope|
       |  load-ingredients|
-      |  y:&:num <- copy 0|
+      |  y:&:num <- copy null|
       |  z <- add x, y|
       |]|
     ]
@@ -326,7 +326,7 @@ scenario run-updates-errors-for-shape-shifting-recipes [
     .recipe foo x:_elem -> z:_elem [                   ┊                                                 .
     .  local-scope                                     ┊─────────────────────────────────────────────────.
     .  load-ingredients                                ┊0   edit       copy       to recipe    delete    .
-    .  y:&:num <- copy 0                               ┊foo 2                                            .
+    .  y:&:num <- copy null                            ┊foo 2                                            .
     .  z <- add x, y                                   ┊foo_2: 'add' requires number ingredients, but go↩.
     .]                                                 ┊t 'y'                                            .
     .                                                  ┊─────────────────────────────────────────────────.
@@ -346,7 +346,7 @@ scenario run-updates-errors-for-shape-shifting-recipes [
     .recipe foo x:_elem -> z:_elem [                   ┊                                                 .
     .  local-scope                                     ┊─────────────────────────────────────────────────.
     .  load-ingredients                                ┊0   edit       copy       to recipe    delete    .
-    .  y:&:num <- copy 0                               ┊foo 2                                            .
+    .  y:&:num <- copy null                            ┊foo 2                                            .
     .  z <- add x, y                                   ┊foo_3: 'add' requires number ingredients, but go↩.
     .]                                                 ┊t 'y'                                            .
     .                                                  ┊─────────────────────────────────────────────────.
@@ -367,7 +367,7 @@ scenario run-avoids-spurious-errors-on-reloading-shape-shifting-recipes [
     ]
   ]
   # call code that uses other variants of it, but not it itself
-  test-sandbox:text <- new [x:&:list:num <- copy 0
+  test-sandbox:text <- new [x:&:list:num <- copy null
 to-text x]
   env:&:environment <- new-programming-environment resources, screen, test-sandbox
   render-all screen, env, render
diff --git a/edit/012-editor-undo.mu b/edit/012-editor-undo.mu
index 42d325bd..871f6c74 100644
--- a/edit/012-editor-undo.mu
+++ b/edit/012-editor-undo.mu
@@ -206,7 +206,7 @@ def add-operation editor:&:editor, op:&:operation -> editor:&:editor [
   undo <- push op undo
   *editor <- put *editor, undo:offset, undo
   redo:&:list:&:operation <- get *editor, redo:offset
-  redo <- copy 0
+  redo <- copy null
   *editor <- put *editor, redo:offset, redo
 ]
 
diff --git a/filesystem.mu b/filesystem.mu
index acfd39b2..6ea8e08c 100644
--- a/filesystem.mu
+++ b/filesystem.mu
@@ -5,8 +5,8 @@
 
 def main [
   local-scope
-  source-file:&:source:char <- start-reading 0/real-filesystem, [/tmp/mu-x]
-  sink-file:&:sink:char, write-routine:num <- start-writing 0/real-filesystem, [/tmp/mu-y]
+  source-file:&:source:char <- start-reading null/real-filesystem, [/tmp/mu-x]
+  sink-file:&:sink:char, write-routine:num <- start-writing null/real-filesystem, [/tmp/mu-y]
   {
     c:char, done?:bool, source-file <- read source-file
     break-if done?
diff --git a/http-client.mu b/http-client.mu
index 9219a76f..8f04c2bc 100644
--- a/http-client.mu
+++ b/http-client.mu
@@ -3,7 +3,7 @@
 def main [
   local-scope
   $print [aaa] 10/newline
-  google:&:source:char <- start-reading-from-network 0/real-resources, [google.com/]
+  google:&:source:char <- start-reading-from-network null/real-resources, [google.com/]
   $print [bbb] 10/newline
   n:num <- copy 0
   buf:&:buffer:char <- new-buffer 30
@@ -21,9 +21,9 @@ def main [
   }
   result:text <- buffer-to-array buf
   open-console
-  clear-screen 0/screen  # non-scrolling app
+  clear-screen null/screen  # non-scrolling app
   len:num <- length *result
-  print 0/real-screen, result
+  print null/real-screen, result
   wait-for-some-interaction
   close-console
 ]
diff --git a/lambda-to-mu.mu b/lambda-to-mu.mu
index 1d23cd46..a171b4ca 100644
--- a/lambda-to-mu.mu
+++ b/lambda-to-mu.mu
@@ -22,7 +22,7 @@ result <- add a t1]
 def lambda-to-mu in:text -> out:text [
   local-scope
   load-inputs
-  out <- copy 0
+  out <- copy null
   cells:&:cell <- parse in
   out <- to-mu cells
 ]
@@ -84,7 +84,7 @@ scenario pair-is-not-atom [
   # construct (a . nil)
   s:text <- new [a]
   x:&:cell <- new-atom s
-  y:&:cell <- new-pair x, 0/nil
+  y:&:cell <- new-pair x, null
   10:bool/raw <- is-atom? y
   11:bool/raw <- is-pair? y
   memory-should-contain [
@@ -114,7 +114,7 @@ def first x:&:cell -> result:&:cell [
   local-scope
   load-inputs
   pair:pair, pair?:bool <- maybe-convert *x, pair:variant
-  return-unless pair?, 0/nil
+  return-unless pair?, null
   result <- get pair, first:offset
 ]
 
@@ -122,7 +122,7 @@ def rest x:&:cell -> result:&:cell [
   local-scope
   load-inputs
   pair:pair, pair?:bool <- maybe-convert *x, pair:variant
-  return-unless pair?, 0/nil
+  return-unless pair?, null
   result <- get pair, rest:offset
 ]
 
@@ -161,7 +161,7 @@ scenario cell-operations-on-pair [
   # construct (a . nil)
   s:text <- new [a]
   x:&:cell <- new-atom s
-  y:&:cell <- new-pair x, 0/nil
+  y:&:cell <- new-pair x, null
   x2:&:cell <- first y
   10:bool/raw <- equal x, x2
   11:&:cell/raw <- rest y
@@ -187,7 +187,7 @@ def parse in:&:stream:char -> out:&:cell, in:&:stream:char [
   # skip whitespace
   in <- skip-whitespace in
   c:char, eof?:bool <- peek in
-  return-if eof?, 0/nil
+  return-if eof?, null
   pair?:bool <- equal c, 40/open-paren
   {
     break-if pair?
@@ -223,7 +223,7 @@ def parse in:&:stream:char -> out:&:cell, in:&:stream:char [
       close-paren?:bool <- equal c, 41/close-paren
       break-if close-paren?
       first:&:cell, in <- parse in
-      *out <- merge 1/pair, first, 0/nil
+      *out <- merge 1/pair, first, null
     }
     # read in any remaining elements
     curr:&:cell <- copy out
@@ -245,7 +245,7 @@ def parse in:&:stream:char -> out:&:cell, in:&:stream:char [
       is-dot?:bool <- atom-match? next, [.]
       {
         break-if is-dot?
-        next-curr:&:cell <- new-pair next, 0/nil
+        next-curr:&:cell <- new-pair next, null
         curr <- set-rest curr, next-curr
         curr <- rest curr
       }
@@ -276,7 +276,7 @@ def skip-whitespace in:&:stream:char -> in:&:stream:char [
   load-inputs
   {
     done?:bool <- end-of-stream? in
-    return-if done?, 0/null
+    return-if done?, null
     c:char <- peek in
     space?:bool <- space? c
     break-unless space?
@@ -586,5 +586,5 @@ def to-mu in:&:cell, buf:&:buffer:char -> buf:&:buffer:char, result-name:text [
   # null cell? no change.
   # pair with all atoms? gensym a new variable
   # pair containing other pairs? recurse
-  result-name <- copy 0
+  result-name <- copy null
 ]
diff --git a/mu.vim b/mu.vim
index 6dfe6318..35d46651 100644
--- a/mu.vim
+++ b/mu.vim
@@ -55,6 +55,7 @@ syntax match muLiteral %[^ ]\+:type/[^ ,]*\|[^ ]\+:type\>%
 syntax match muLiteral %[^ ]\+:offset/[^ ,]*\|[^ ]\+:offset\>%
 syntax match muLiteral %[^ ]\+:variant/[^ ,]*\|[^ ]\+:variant\>%
 syntax match muLiteral % true\(\/[^ ]*\)\?\| false\(\/[^ ]*\)\?%  " literals will never be the first word in an instruction
+syntax match muLiteral % null\(\/[^ ]*\)\?%
 highlight link muLiteral Constant
 
 " sources of action at a distance
diff --git a/sandbox/001-editor.mu b/sandbox/001-editor.mu
index 8855395a..963bb1cf 100644
--- a/sandbox/001-editor.mu
+++ b/sandbox/001-editor.mu
@@ -6,10 +6,10 @@ def main text:text [
   local-scope
   load-inputs
   open-console
-  clear-screen 0/screen  # non-scrolling app
+  clear-screen null/screen  # non-scrolling app
   e:&:editor <- new-editor text, 0/left, 5/right
-  render 0/screen, e
-  wait-for-event 0/console
+  render null/screen, e
+  wait-for-event null/console
   close-console
 ]
 
@@ -61,7 +61,7 @@ def new-editor s:text, left:num, right:num -> result:&:editor [
   *result <- put *result, cursor-row:offset, 1/top
   *result <- put *result, cursor-column:offset, left
   # initialize empty contents
-  init:&:duplex-list:char <- push 167/§, 0/tail
+  init:&:duplex-list:char <- push 167/§, null
   *result <- put *result, data:offset, init
   *result <- put *result, top-of-screen:offset, init
   *result <- put *result, before-cursor:offset, init
@@ -80,7 +80,7 @@ scenario editor-initializes-without-data [
   local-scope
   assume-screen 5/width, 3/height
   run [
-    e:&:editor <- new-editor 0/data, 2/left, 5/right
+    e:&:editor <- new-editor null/data, 2/left, 5/right
     2:editor/raw <- copy *e
   ]
   memory-should-contain [
diff --git a/sandbox/002-typing.mu b/sandbox/002-typing.mu
index a67fcf3c..ef3f25d2 100644
--- a/sandbox/002-typing.mu
+++ b/sandbox/002-typing.mu
@@ -6,10 +6,10 @@ def! main text:text [
   local-scope
   load-inputs
   open-console
-  clear-screen 0/screen  # non-scrolling app
+  clear-screen null/screen  # non-scrolling app
   editor:&:editor <- new-editor text, 5/left, 45/right
-  editor-render 0/screen, editor
-  editor-event-loop 0/screen, 0/console, editor
+  editor-render null/screen, editor
+  editor-event-loop null/screen, null/console, editor
   close-console
 ]
 
@@ -223,7 +223,7 @@ def insert-at-cursor editor:&:editor, c:char, screen:&:screen -> go-render?:bool
   next:&:duplex-list:char <- next before-cursor
   {
     # at end of all text? no need to scroll? just print the character and leave
-    at-end?:bool <- equal next, 0/null
+    at-end?:bool <- equal next, null
     break-unless at-end?
     bottom:num <- subtract screen-height, 1
     at-bottom?:bool <- equal save-row, bottom
@@ -701,7 +701,7 @@ after <insert-character-special-case> [
     just-before-wrap?:bool <- greater-or-equal cursor-column, before-wrap-column
     next:&:duplex-list:char <- next before-cursor
     # at end of line? next == 0 || next.value == 10/newline
-    at-end-of-line?:bool <- equal next, 0
+    at-end-of-line?:bool <- equal next, null
     {
       break-if at-end-of-line?
       next-character:char <- get *next, value:offset
diff --git a/sandbox/003-shortcuts.mu b/sandbox/003-shortcuts.mu
index db19443d..c9e66d5b 100644
--- a/sandbox/003-shortcuts.mu
+++ b/sandbox/003-shortcuts.mu
@@ -113,7 +113,7 @@ def delete-before-cursor editor:&:editor, screen:&:screen -> go-render?:bool, ba
   data:&:duplex-list:char <- get *editor, data:offset
   # if at start of text (before-cursor at § sentinel), return
   prev:&:duplex-list:char <- prev before-cursor
-  return-unless prev, false/no-more-render, 0/nothing-deleted
+  return-unless prev, false/no-more-render, null/nothing-deleted
   trace 10, [app], [delete-before-cursor]
   original-row:num <- get *editor, cursor-row:offset
   move-cursor-coordinates-left editor
@@ -2353,7 +2353,7 @@ def delete-to-end-of-line editor:&:editor -> result:&:duplex-list:char, editor:&
   start:&:duplex-list:char <- get *editor, before-cursor:offset
   end:&:duplex-list:char <- next start
   {
-    at-end-of-text?:bool <- equal end, 0/null
+    at-end-of-text?:bool <- equal end, null
     break-if at-end-of-text?
     curr:char <- get *end, value:offset
     at-end-of-line?:bool <- equal curr, 10/newline
diff --git a/sandbox/004-programming-environment.mu b/sandbox/004-programming-environment.mu
index 1208d0e8..1454144b 100644
--- a/sandbox/004-programming-environment.mu
+++ b/sandbox/004-programming-environment.mu
@@ -3,10 +3,10 @@
 def! main [
   local-scope
   open-console
-  clear-screen 0/screen  # non-scrolling app
-  env:&:environment <- new-programming-environment 0/filesystem, 0/screen
-  render-all 0/screen, env, render
-  event-loop 0/screen, 0/console, env, 0/filesystem
+  clear-screen null/screen  # non-scrolling app
+  env:&:environment <- new-programming-environment null/filesystem, null/screen
+  render-all null/screen, env, render
+  event-loop null/screen, null/console, env, null/filesystem
 ]
 
 container environment [
diff --git a/sandbox/005-sandbox.mu b/sandbox/005-sandbox.mu
index ae4372a1..632a5df1 100644
--- a/sandbox/005-sandbox.mu
+++ b/sandbox/005-sandbox.mu
@@ -10,11 +10,11 @@
 def! main [
   local-scope
   open-console
-  clear-screen 0/screen  # non-scrolling app
-  env:&:environment <- new-programming-environment 0/filesystem, 0/screen
-  env <- restore-sandboxes env, 0/filesystem
-  render-all 0/screen, env, render
-  event-loop 0/screen, 0/console, env, 0/filesystem
+  clear-screen null/screen  # non-scrolling app
+  env:&:environment <- new-programming-environment null/filesystem, null/screen
+  env <- restore-sandboxes env, null/filesystem
+  render-all null/screen, env, render
+  event-loop null/screen, null/console, env, null/filesystem
 ]
 
 container environment [
@@ -160,7 +160,7 @@ def run-sandboxes env:&:environment, resources:&:resources, screen:&:screen -> e
     # needs to be before running them, in case we die when running
     save-sandboxes env, resources
     # clear sandbox editor
-    init:&:duplex-list:char <- push 167/§, 0/tail
+    init:&:duplex-list:char <- push 167/§, null
     *current-sandbox <- put *current-sandbox, data:offset, init
     *current-sandbox <- put *current-sandbox, top-of-screen:offset, init
   }
@@ -458,8 +458,8 @@ def restore-sandboxes env:&:environment, resources:&:resources -> env:&:environm
   load-inputs
   # read all scenarios, pushing them to end of a list of scenarios
   idx:num <- copy 0
-  curr:&:sandbox <- copy 0
-  prev:&:sandbox <- copy 0
+  curr:&:sandbox <- copy null
+  prev:&:sandbox <- copy null
   {
     filename:text <- append [lesson/], idx
     contents:text <- slurp resources, filename
@@ -668,7 +668,7 @@ def editor-contents editor:&:editor -> result:text [
   # skip § sentinel
   assert curr, [editor without data is illegal; must have at least a sentinel]
   curr <- next curr
-  return-unless curr, 0
+  return-unless curr, null
   {
     break-unless curr
     c:char <- get *curr, value:offset
@@ -817,15 +817,15 @@ after <global-keypress> [
 ]
 
 # sandbox belonging to 'env' whose next-sandbox is 'in'
-# return 0 if there's no such sandbox, either because 'in' doesn't exist in 'env', or because it's the first sandbox
+# return null if there's no such sandbox, either because 'in' doesn't exist in 'env', or because it's the first sandbox
 def previous-sandbox env:&:environment, in:&:sandbox -> out:&:sandbox [
   local-scope
   load-inputs
   curr:&:sandbox <- get *env, sandbox:offset
-  return-unless curr, 0/nil
+  return-unless curr, null
   next:&:sandbox <- get *curr, next-sandbox:offset
   {
-    return-unless next, 0/nil
+    return-unless next, null
     found?:bool <- equal next, in
     break-if found?
     curr <- copy next
diff --git a/sandbox/006-sandbox-copy.mu b/sandbox/006-sandbox-copy.mu
index 4b7222f3..0eae6cf7 100644
--- a/sandbox/006-sandbox-copy.mu
+++ b/sandbox/006-sandbox-copy.mu
@@ -194,7 +194,7 @@ def find-sandbox env:&:environment, click-row:num -> result:&:sandbox [
     curr-sandbox <- get *curr-sandbox, next-sandbox:offset
     loop
   }
-  return 0/not-found
+  return null/not-found
 ]
 
 def click-on-sandbox-area? click-row:num, env:&:environment -> result:bool [
diff --git a/sandbox/009-sandbox-test.mu b/sandbox/009-sandbox-test.mu
index 3fd4dafc..c22916a7 100644
--- a/sandbox/009-sandbox-test.mu
+++ b/sandbox/009-sandbox-test.mu
@@ -173,9 +173,9 @@ def find-click-in-sandbox-output env:&:environment, click-row:num -> sandbox:&:s
   }
   # return sandbox if click is in its output region
   response-starting-row:num <- get *sandbox, response-starting-row-on-screen:offset
-  return-unless response-starting-row, 0/no-click-in-sandbox-output, 0/sandbox-index
+  return-unless response-starting-row, null/no-click-in-sandbox-output, 0/sandbox-index
   click-in-response?:bool <- greater-or-equal click-row, response-starting-row
-  return-unless click-in-response?, 0/no-click-in-sandbox-output, 0/sandbox-index
+  return-unless click-in-response?, null/no-click-in-sandbox-output, 0/sandbox-index
   return sandbox, sandbox-index
 ]
 
@@ -186,7 +186,7 @@ def toggle-expected-response sandbox:&:sandbox -> sandbox:&:sandbox [
   {
     # if expected-response is set, reset
     break-unless expected-response
-    *sandbox <- put *sandbox, expected-response:offset, 0
+    *sandbox <- put *sandbox, expected-response:offset, null
   }
   {
     # if not, set expected response to the current response
diff --git a/sandbox/010-sandbox-trace.mu b/sandbox/010-sandbox-trace.mu
index 6d775322..d544dd8c 100644
--- a/sandbox/010-sandbox-trace.mu
+++ b/sandbox/010-sandbox-trace.mu
@@ -225,7 +225,7 @@ def find-click-in-sandbox-code env:&:environment, click-row:num -> sandbox:&:san
   click-on-sandbox-code?:bool <- and click-above-response?, click-below-menu?
   {
     break-if click-on-sandbox-code?
-    return 0/no-click-in-sandbox-output
+    return null/no-click-in-sandbox-output
   }
   return sandbox
 ]
diff --git a/sandbox/011-errors.mu b/sandbox/011-errors.mu
index 8678989f..2f59d1fe 100644
--- a/sandbox/011-errors.mu
+++ b/sandbox/011-errors.mu
@@ -253,7 +253,7 @@ scenario run-updates-errors-for-shape-shifting-recipes [
       |recipe foo x:_elem -> z:_elem [|
       |  local-scope|
       |  load-ingredients|
-      |  y:&:num <- copy 0|
+      |  y:&:num <- copy null|
       |  z <- add x, y|
       |]|
     ]
@@ -308,7 +308,7 @@ scenario run-avoids-spurious-errors-on-reloading-shape-shifting-recipes [
     ]
   ]
   # call code that uses other variants of it, but not it itself
-  test-sandbox:text <- new [x:&:list:num <- copy 0
+  test-sandbox:text <- new [x:&:list:num <- copy null
 to-text x]
   env:&:environment <- new-programming-environment resources, screen, test-sandbox
   render-all screen, env, render
diff --git a/sandbox/012-editor-undo.mu b/sandbox/012-editor-undo.mu
index 23448ea2..69afd207 100644
--- a/sandbox/012-editor-undo.mu
+++ b/sandbox/012-editor-undo.mu
@@ -204,7 +204,7 @@ def add-operation editor:&:editor, op:&:operation -> editor:&:editor [
   undo <- push op undo
   *editor <- put *editor, undo:offset, undo
   redo:&:list:&:operation <- get *editor, redo:offset
-  redo <- copy 0
+  redo <- copy null
   *editor <- put *editor, redo:offset, redo
 ]
 
diff --git a/screen.mu b/screen.mu
index 1c7a31a1..58ecaa60 100644
--- a/screen.mu
+++ b/screen.mu
@@ -4,26 +4,26 @@
 # screens.
 def main [
   open-console
-  clear-screen 0/screen  # non-scrolling app
+  clear-screen null/screen  # non-scrolling app
   10:char <- copy 97/a
-  print 0/screen, 10:char/a, 1/red, 2/green
-  1:num/raw, 2:num/raw <- cursor-position 0/screen
-  wait-for-event 0/console
-  clear-screen 0/screen
-  move-cursor 0/screen, 0/row, 4/column
+  print null/screen, 10:char/a, 1/red, 2/green
+  1:num/raw, 2:num/raw <- cursor-position null/screen
+  wait-for-event null/console
+  clear-screen null/screen
+  move-cursor null/screen, 0/row, 4/column
   10:char <- copy 98/b
-  print 0/screen, 10:char
-  wait-for-event 0/console
-  move-cursor 0/screen, 0/row, 0/column
-  clear-line 0/screen
-  wait-for-event 0/console
-  cursor-down 0/screen
-  wait-for-event 0/console
-  cursor-right 0/screen
-  wait-for-event 0/console
-  cursor-left 0/screen
-  wait-for-event 0/console
-  cursor-up 0/screen
-  wait-for-event 0/console
+  print null/screen, 10:char
+  wait-for-event null/console
+  move-cursor null/screen, 0/row, 0/column
+  clear-line null/screen
+  wait-for-event null/console
+  cursor-down null/screen
+  wait-for-event null/console
+  cursor-right null/screen
+  wait-for-event null/console
+  cursor-left null/screen
+  wait-for-event null/console
+  cursor-up null/screen
+  wait-for-event null/console
   close-console
 ]
diff --git a/vimrc.vim b/vimrc.vim
index 218da388..9618a44b 100644
--- a/vimrc.vim
+++ b/vimrc.vim
@@ -31,7 +31,8 @@ function! HighlightTangledFile()
   syntax match muLiteral %[^ ]\+:type/[^ ,]*\|[^ ]\+:type\>%
   syntax match muLiteral %[^ ]\+:offset/[^ ,]*\|[^ ]\+:offset\>%
   syntax match muLiteral %[^ ]\+:variant/[^ ,]*\|[^ ]\+:variant\>%
-  syntax keyword muLiteral true false null
+  syntax match muLiteral % true\(\/[^ ]*\)\?\| false\(\/[^ ]*\)\?%  " literals will never be the first word in an instruction
+  syntax match muLiteral % null\(\/[^ ]*\)\?%
   highlight link muLiteral Constant
   syntax match muAssign " <- \|\<raw\>" | highlight link muAssign SpecialChar
   " common keywords