about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2016-10-23 23:15:12 -0700
committerKartik K. Agaram <vc@akkartik.com>2016-10-23 23:15:12 -0700
commit2e4f0bbd12231a51ae08df4fe0f8847111fde405 (patch)
tree581166406a4749ac2dcd87c8555cc95dbb1bbd52
parent2225b945ad5ba9310081b371bf32a9f575646e1b (diff)
downloadmu-2e4f0bbd12231a51ae08df4fe0f8847111fde405.tar.gz
3578
-rw-r--r--html/042name.cc.html2
-rw-r--r--html/053recipe_header.cc.html28
-rw-r--r--html/056shape_shifting_recipe.cc.html1
-rw-r--r--html/071recipe.cc.html18
-rw-r--r--html/081print.mu.html363
-rw-r--r--html/084console.mu.html10
-rw-r--r--html/088file.mu.html40
-rw-r--r--html/091socket.cc.html43
-rw-r--r--html/092socket.mu.html212
9 files changed, 348 insertions, 369 deletions
diff --git a/html/042name.cc.html b/html/042name.cc.html
index d7027161..098fee41 100644
--- a/html/042name.cc.html
+++ b/html/042name.cc.html
@@ -93,6 +93,7 @@ Name = Name_snapshot<span class="Delimiter">;</span>
       <span class="Normal">if</span> <span class="Delimiter">(</span>is_integer<span class="Delimiter">(</span>ingredient<span class="Delimiter">.</span>name<span class="Delimiter">))</span> <span class="Identifier">continue</span><span class="Delimiter">;</span>
       <span class="Normal">if</span> <span class="Delimiter">(</span>!already_transformed<span class="Delimiter">(</span>ingredient<span class="Delimiter">,</span> names<span class="Delimiter">))</span> <span class="Delimiter">{</span>
         raise &lt;&lt; maybe<span class="Delimiter">(</span>caller<span class="Delimiter">.</span>name<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;use before set: '&quot;</span> &lt;&lt; ingredient<span class="Delimiter">.</span>name &lt;&lt; <span class="Constant">&quot;'</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; end<span class="Delimiter">();</span>
+        <span class="Comment">// use-before-set Error</span>
         <span class="Identifier">return</span><span class="Delimiter">;</span>
       <span class="Delimiter">}</span>
       <span class="Normal">int</span> v = lookup_name<span class="Delimiter">(</span>ingredient<span class="Delimiter">,</span> r<span class="Delimiter">);</span>
@@ -135,6 +136,7 @@ Name = Name_snapshot<span class="Delimiter">;</span>
 <span class="Normal">bool</span> is_disqualified<span class="Delimiter">(</span><span class="Comment">/*</span><span class="Comment">mutable</span><span class="Comment">*/</span> reagent&amp; x<span class="Delimiter">,</span> <span class="Normal">const</span> instruction&amp; inst<span class="Delimiter">,</span> <span class="Normal">const</span> string&amp; recipe_name<span class="Delimiter">)</span> <span class="Delimiter">{</span>
   <span class="Normal">if</span> <span class="Delimiter">(</span>!x<span class="Delimiter">.</span>type<span class="Delimiter">)</span> <span class="Delimiter">{</span>
     raise &lt;&lt; maybe<span class="Delimiter">(</span>recipe_name<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;missing type for '&quot;</span> &lt;&lt; x<span class="Delimiter">.</span>original_string &lt;&lt; <span class="Constant">&quot;' in '&quot;</span> &lt;&lt; inst<span class="Delimiter">.</span>original_string &lt;&lt; <span class="Constant">&quot;'</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; end<span class="Delimiter">();</span>
+    <span class="Comment">// missing-type Error 1</span>
     <span class="Identifier">return</span> <span class="Constant">true</span><span class="Delimiter">;</span>
   <span class="Delimiter">}</span>
   <span class="Normal">if</span> <span class="Delimiter">(</span>is_raw<span class="Delimiter">(</span>x<span class="Delimiter">))</span> <span class="Identifier">return</span> <span class="Constant">true</span><span class="Delimiter">;</span>
diff --git a/html/053recipe_header.cc.html b/html/053recipe_header.cc.html
index d379b46a..021db5f4 100644
--- a/html/053recipe_header.cc.html
+++ b/html/053recipe_header.cc.html
@@ -258,6 +258,34 @@ put<span class="Delimiter">(</span>Recipe_ordinal<span class="Delimiter">,</span
   <span class="Identifier">break</span><span class="Delimiter">;</span>
 <span class="Delimiter">}</span>
 
+<span class="Comment">//: more useful error messages if someone forgets 'load-ingredients'</span>
+
+<span class="Delimiter">:(scenario load_ingredients_missing_error)</span>
+<span class="Special">% Hide_errors = true;</span>
+<span class="muRecipe">def</span> foo a:num [
+  local-scope
+  <span class="Normal">b</span>:num<span class="Special"> &lt;- </span>add a:num<span class="Delimiter">,</span> <span class="Constant">1</span>
+]
+<span class="traceContains">+error: foo: use before set: 'a'</span>
+<span class="traceContains">+error:   did you forget 'load-ingredients'?</span>
+
+<span class="Delimiter">:(after &quot;use-before-set Error&quot;)</span>
+<span class="Normal">if</span> <span class="Delimiter">(</span>is_present_in_ingredients<span class="Delimiter">(</span>caller<span class="Delimiter">,</span> ingredient<span class="Delimiter">.</span>name<span class="Delimiter">))</span>
+  raise &lt;&lt; <span class="Constant">&quot;  did you forget 'load-ingredients'?</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; end<span class="Delimiter">();</span>
+
+<span class="Delimiter">:(scenario load_ingredients_missing_error_2)</span>
+<span class="Special">% Hide_errors = true;</span>
+<span class="muRecipe">def</span> foo a:num [
+  local-scope
+  <span class="Normal">b</span>:num<span class="Special"> &lt;- </span>add a<span class="Delimiter">,</span> <span class="Constant">1</span>
+]
+<span class="traceContains">+error: foo: missing type for 'a' in 'b:num &lt;- add a, 1'</span>
+<span class="traceContains">+error:   did you forget 'load-ingredients'?</span>
+
+<span class="Delimiter">:(after &quot;missing-type Error 1&quot;)</span>
+<span class="Normal">if</span> <span class="Delimiter">(</span>is_present_in_ingredients<span class="Delimiter">(</span>get<span class="Delimiter">(</span>Recipe<span class="Delimiter">,</span> get<span class="Delimiter">(</span>Recipe_ordinal<span class="Delimiter">,</span> recipe_name<span class="Delimiter">)),</span> x<span class="Delimiter">.</span>name<span class="Delimiter">))</span>
+  raise &lt;&lt; <span class="Constant">&quot;  did you forget 'load-ingredients'?</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; end<span class="Delimiter">();</span>
+
 <span class="SalientComment">//:: Check all calls against headers.</span>
 
 <span class="Delimiter">:(scenario show_clear_error_on_bad_call)</span>
diff --git a/html/056shape_shifting_recipe.cc.html b/html/056shape_shifting_recipe.cc.html
index fa7aa789..32aef874 100644
--- a/html/056shape_shifting_recipe.cc.html
+++ b/html/056shape_shifting_recipe.cc.html
@@ -361,6 +361,7 @@ recipe_ordinal new_variant<span class="Delimiter">(</span>recipe_ordinal exempla
 <span class="Normal">void</span> accumulate_type_ingredients<span class="Delimiter">(</span><span class="Normal">const</span> type_tree* exemplar_type<span class="Delimiter">,</span> <span class="Normal">const</span> type_tree* refinement_type<span class="Delimiter">,</span> map&lt;string<span class="Delimiter">,</span> <span class="Normal">const</span> type_tree*&gt;&amp; mappings<span class="Delimiter">,</span> <span class="Normal">const</span> recipe&amp; exemplar<span class="Delimiter">,</span> <span class="Normal">const</span> reagent&amp; exemplar_reagent<span class="Delimiter">,</span> <span class="Normal">const</span> instruction&amp; call_instruction<span class="Delimiter">,</span> <span class="Normal">const</span> recipe&amp; caller_recipe<span class="Delimiter">,</span> <span class="Normal">bool</span>* error<span class="Delimiter">)</span> <span class="Delimiter">{</span>
   <span class="Normal">if</span> <span class="Delimiter">(</span>!exemplar_type<span class="Delimiter">)</span> <span class="Identifier">return</span><span class="Delimiter">;</span>
   <span class="Normal">if</span> <span class="Delimiter">(</span>!refinement_type<span class="Delimiter">)</span> <span class="Delimiter">{</span>
+    <span class="Comment">// probably a bug in mu</span>
     <span class="Comment">// todo: make this smarter; only flag an error if exemplar_type contains some *new* type ingredient</span>
     raise &lt;&lt; maybe<span class="Delimiter">(</span>exemplar<span class="Delimiter">.</span>name<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;missing type ingredient for &quot;</span> &lt;&lt; exemplar_reagent<span class="Delimiter">.</span>original_string &lt;&lt; <span class="cSpecial">'\n'</span> &lt;&lt; end<span class="Delimiter">();</span>
     raise &lt;&lt; <span class="Constant">&quot;  (called from '&quot;</span> &lt;&lt; to_original_string<span class="Delimiter">(</span>call_instruction<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;')</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; end<span class="Delimiter">();</span>
diff --git a/html/071recipe.cc.html b/html/071recipe.cc.html
index 2a63b5e8..7db245d8 100644
--- a/html/071recipe.cc.html
+++ b/html/071recipe.cc.html
@@ -375,7 +375,7 @@ $error: <span class="Constant">0</span>
   <span class="Delimiter">}</span>
 ]
 <span class="Comment"># error should be as if foo is not a recipe</span>
-<span class="traceContains">+error: main: missing type for foo in 'break-if foo'</span>
+<span class="traceContains">+error: main: missing type for 'foo' in 'break-if foo'</span>
 
 <span class="Delimiter">:(before &quot;End JUMP_IF Checks&quot;)</span>
 check_for_recipe_literals<span class="Delimiter">(</span>inst<span class="Delimiter">,</span> get<span class="Delimiter">(</span>Recipe<span class="Delimiter">,</span> r<span class="Delimiter">));</span>
@@ -384,10 +384,22 @@ check_for_recipe_literals<span class="Delimiter">(</span>inst<span class="Delimi
 <span class="Delimiter">:(code)</span>
 <span class="Normal">void</span> check_for_recipe_literals<span class="Delimiter">(</span><span class="Normal">const</span> instruction&amp; inst<span class="Delimiter">,</span> <span class="Normal">const</span> recipe&amp; caller<span class="Delimiter">)</span> <span class="Delimiter">{</span>
   <span class="Normal">for</span> <span class="Delimiter">(</span><span class="Normal">int</span> i = <span class="Constant">0</span><span class="Delimiter">;</span>  i &lt; SIZE<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>ingredients<span class="Delimiter">);</span>  ++i<span class="Delimiter">)</span> <span class="Delimiter">{</span>
-    <span class="Normal">if</span> <span class="Delimiter">(</span>is_mu_recipe<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>ingredients<span class="Delimiter">.</span>at<span class="Delimiter">(</span>i<span class="Delimiter">)))</span>
-      raise &lt;&lt; maybe<span class="Delimiter">(</span>caller<span class="Delimiter">.</span>name<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;missing type for &quot;</span> &lt;&lt; inst<span class="Delimiter">.</span>ingredients<span class="Delimiter">.</span>at<span class="Delimiter">(</span>i<span class="Delimiter">).</span>original_string &lt;&lt; <span class="Constant">&quot; in '&quot;</span> &lt;&lt; inst<span class="Delimiter">.</span>original_string &lt;&lt; <span class="Constant">&quot;'</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; end<span class="Delimiter">();</span>
+    <span class="Normal">if</span> <span class="Delimiter">(</span>is_mu_recipe<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>ingredients<span class="Delimiter">.</span>at<span class="Delimiter">(</span>i<span class="Delimiter">)))</span> <span class="Delimiter">{</span>
+      raise &lt;&lt; maybe<span class="Delimiter">(</span>caller<span class="Delimiter">.</span>name<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;missing type for '&quot;</span> &lt;&lt; inst<span class="Delimiter">.</span>ingredients<span class="Delimiter">.</span>at<span class="Delimiter">(</span>i<span class="Delimiter">).</span>original_string &lt;&lt; <span class="Constant">&quot;' in '&quot;</span> &lt;&lt; inst<span class="Delimiter">.</span>original_string &lt;&lt; <span class="Constant">&quot;'</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; end<span class="Delimiter">();</span>
+      <span class="Normal">if</span> <span class="Delimiter">(</span>is_present_in_ingredients<span class="Delimiter">(</span>caller<span class="Delimiter">,</span> inst<span class="Delimiter">.</span>ingredients<span class="Delimiter">.</span>at<span class="Delimiter">(</span>i<span class="Delimiter">).</span>name<span class="Delimiter">))</span>
+        raise &lt;&lt; <span class="Constant">&quot;  did you forget 'load-ingredients'?</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; end<span class="Delimiter">();</span>
+    <span class="Delimiter">}</span>
   <span class="Delimiter">}</span>
 <span class="Delimiter">}</span>
+
+<span class="Delimiter">:(scenario load_ingredients_missing_error_3)</span>
+<span class="Special">% Hide_errors = true;</span>
+<span class="muRecipe">def</span> foo <span class="Delimiter">{</span>f: <span class="Delimiter">(</span>recipe num<span class="muRecipe"> -&gt; </span>num<span class="Delimiter">)}</span> [
+  local-scope
+  <span class="Normal">b</span>:num<span class="Special"> &lt;- </span>call f<span class="Delimiter">,</span> <span class="Constant">1</span>
+]
+<span class="traceContains">+error: foo: missing type for 'f' in 'b:num &lt;- call f, 1'</span>
+<span class="traceContains">+error:   did you forget 'load-ingredients'?</span>
 </pre>
 </body>
 </html>
diff --git a/html/081print.mu.html b/html/081print.mu.html
index 4955c3b1..241cf857 100644
--- a/html/081print.mu.html
+++ b/html/081print.mu.html
@@ -62,44 +62,40 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color
 <span class="muRecipe">def</span> clear-screen screen:&amp;:screen<span class="muRecipe"> -&gt; </span>screen:&amp;:screen [
   <span class="Constant">local-scope</span>
   <span class="Constant">load-ingredients</span>
-  <span class="Comment"># if x exists</span>
   <span class="Delimiter">{</span>
-    <span class="muControl">break-unless</span> screen
-    <span class="Comment"># clear fake screen</span>
-    buf:&amp;:@:screen-cell <span class="Special">&lt;-</span> get *screen, <span class="Constant">data:offset</span>
-    max:num <span class="Special">&lt;-</span> length *buf
-    i:num <span class="Special">&lt;-</span> copy<span class="Constant"> 0</span>
-    <span class="Delimiter">{</span>
-      done?:bool <span class="Special">&lt;-</span> greater-or-equal i, max
-      <span class="muControl">break-if</span> done?
-      curr:screen-cell <span class="Special">&lt;-</span> merge <span class="Constant">0/empty</span>, <span class="Constant">7/white</span>
-      *buf <span class="Special">&lt;-</span> put-index *buf, i, curr
-      i <span class="Special">&lt;-</span> add i,<span class="Constant"> 1</span>
-      <span class="muControl">loop</span>
-    <span class="Delimiter">}</span>
-    <span class="Comment"># reset cursor</span>
-    *screen <span class="Special">&lt;-</span> put *screen, <span class="Constant">cursor-row:offset</span>,<span class="Constant"> 0</span>
-    *screen <span class="Special">&lt;-</span> put *screen, <span class="Constant">cursor-column:offset</span>,<span class="Constant"> 0</span>
+    <span class="muControl">break-if</span> screen
+    <span class="Comment"># real screen</span>
+    clear-display
     <span class="muControl">return</span>
   <span class="Delimiter">}</span>
-  <span class="Comment"># otherwise, real screen</span>
-  clear-display
+  <span class="Comment"># fake screen</span>
+  buf:&amp;:@:screen-cell <span class="Special">&lt;-</span> get *screen, <span class="Constant">data:offset</span>
+  max:num <span class="Special">&lt;-</span> length *buf
+  i:num <span class="Special">&lt;-</span> copy<span class="Constant"> 0</span>
+  <span class="Delimiter">{</span>
+    done?:bool <span class="Special">&lt;-</span> greater-or-equal i, max
+    <span class="muControl">break-if</span> done?
+    curr:screen-cell <span class="Special">&lt;-</span> merge <span class="Constant">0/empty</span>, <span class="Constant">7/white</span>
+    *buf <span class="Special">&lt;-</span> put-index *buf, i, curr
+    i <span class="Special">&lt;-</span> add i,<span class="Constant"> 1</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+  <span class="Comment"># reset cursor</span>
+  *screen <span class="Special">&lt;-</span> put *screen, <span class="Constant">cursor-row:offset</span>,<span class="Constant"> 0</span>
+  *screen <span class="Special">&lt;-</span> put *screen, <span class="Constant">cursor-column:offset</span>,<span class="Constant"> 0</span>
 ]
 
 <span class="muRecipe">def</span> sync-screen screen:&amp;:screen<span class="muRecipe"> -&gt; </span>screen:&amp;:screen [
   <span class="Constant">local-scope</span>
   <span class="Constant">load-ingredients</span>
-  <span class="Delimiter">{</span>
-    <span class="muControl">break-if</span> screen
-    sync-display
-  <span class="Delimiter">}</span>
-  <span class="Comment"># do nothing for fake screens</span>
+  <span class="muControl">return-if</span> screen  <span class="Comment"># do nothing for fake screens</span>
+  sync-display
 ]
 
 <span class="muRecipe">def</span> fake-screen-is-empty? screen:&amp;:screen<span class="muRecipe"> -&gt; </span>result:bool [
   <span class="Constant">local-scope</span>
   <span class="Constant">load-ingredients</span>
-  <span class="muControl">return-unless</span> screen, <span class="Constant">1/true</span>
+  <span class="muControl">return-unless</span> screen, <span class="Constant">1/true</span>  <span class="Comment"># do nothing for real screens</span>
   buf:&amp;:@:screen-cell <span class="Special">&lt;-</span> get *screen, <span class="Constant">data:offset</span>
   i:num <span class="Special">&lt;-</span> copy<span class="Constant"> 0</span>
   len:num <span class="Special">&lt;-</span> length *buf
@@ -134,76 +130,76 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color
   c2:num <span class="Special">&lt;-</span> character-to-code c
   trace<span class="Constant"> 90</span>, <span class="Constant">[print-character]</span>, c2
   <span class="Delimiter">{</span>
-    <span class="Comment"># if x exists</span>
-    <span class="Comment"># (handle special cases exactly like in the real screen)</span>
-    <span class="muControl">break-unless</span> screen
-    width:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">num-columns:offset</span>
-    height:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">num-rows:offset</span>
-    <span class="Comment"># if cursor is out of bounds, silently exit</span>
-    row:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">cursor-row:offset</span>
-    legal?:bool <span class="Special">&lt;-</span> greater-or-equal row,<span class="Constant"> 0</span>
-    <span class="muControl">return-unless</span> legal?
-    legal? <span class="Special">&lt;-</span> lesser-than row, height
-    <span class="muControl">return-unless</span> legal?
-    column:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">cursor-column:offset</span>
-    legal? <span class="Special">&lt;-</span> greater-or-equal column,<span class="Constant"> 0</span>
-    <span class="muControl">return-unless</span> legal?
-    legal? <span class="Special">&lt;-</span> lesser-than column, width
-    <span class="muControl">return-unless</span> legal?
+    <span class="Comment"># real screen</span>
+    <span class="muControl">break-if</span> screen
+    print-character-to-display c, color, bg-color
+    <span class="muControl">return</span>
+  <span class="Delimiter">}</span>
+  <span class="Comment"># fake screen</span>
+  <span class="Comment"># (handle special cases exactly like in the real screen)</span>
+  width:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">num-columns:offset</span>
+  height:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">num-rows:offset</span>
+  <span class="Comment"># if cursor is out of bounds, silently exit</span>
+  row:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">cursor-row:offset</span>
+  legal?:bool <span class="Special">&lt;-</span> greater-or-equal row,<span class="Constant"> 0</span>
+  <span class="muControl">return-unless</span> legal?
+  legal? <span class="Special">&lt;-</span> lesser-than row, height
+  <span class="muControl">return-unless</span> legal?
+  column:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">cursor-column:offset</span>
+  legal? <span class="Special">&lt;-</span> greater-or-equal column,<span class="Constant"> 0</span>
+  <span class="muControl">return-unless</span> legal?
+  legal? <span class="Special">&lt;-</span> lesser-than column, width
+  <span class="muControl">return-unless</span> legal?
 <span class="CommentedCode">#?     $print [print-character (], row, [, ], column, [): ], c, 10/newline</span>
-    <span class="Comment"># special-case: newline</span>
-    <span class="Delimiter">{</span>
-      newline?:bool <span class="Special">&lt;-</span> equal c, <span class="Constant">10/newline</span>
-      <span class="muControl">break-unless</span> newline?
-      <span class="Delimiter">{</span>
-        <span class="Comment"># unless cursor is already at bottom</span>
-        bottom:num <span class="Special">&lt;-</span> subtract height,<span class="Constant"> 1</span>
-        at-bottom?:bool <span class="Special">&lt;-</span> greater-or-equal row, bottom
-        <span class="muControl">break-if</span> at-bottom?
-        <span class="Comment"># move it to the next row</span>
-        column <span class="Special">&lt;-</span> copy<span class="Constant"> 0</span>
-        *screen <span class="Special">&lt;-</span> put *screen, <span class="Constant">cursor-column:offset</span>, column
-        row <span class="Special">&lt;-</span> add row,<span class="Constant"> 1</span>
-        *screen <span class="Special">&lt;-</span> put *screen, <span class="Constant">cursor-row:offset</span>, row
-      <span class="Delimiter">}</span>
-      <span class="muControl">return</span>
-    <span class="Delimiter">}</span>
-    <span class="Comment"># save character in fake screen</span>
-    index:num <span class="Special">&lt;-</span> multiply row, width
-    index <span class="Special">&lt;-</span> add index, column
-    buf:&amp;:@:screen-cell <span class="Special">&lt;-</span> get *screen, <span class="Constant">data:offset</span>
-    len:num <span class="Special">&lt;-</span> length *buf
-    <span class="Comment"># special-case: backspace</span>
+  <span class="Comment"># special-case: newline</span>
+  <span class="Delimiter">{</span>
+    newline?:bool <span class="Special">&lt;-</span> equal c, <span class="Constant">10/newline</span>
+    <span class="muControl">break-unless</span> newline?
     <span class="Delimiter">{</span>
-      backspace?:bool <span class="Special">&lt;-</span> equal c,<span class="Constant"> 8</span>
-      <span class="muControl">break-unless</span> backspace?
-      <span class="Delimiter">{</span>
-        <span class="Comment"># unless cursor is already at left margin</span>
-        at-left?:bool <span class="Special">&lt;-</span> lesser-or-equal column,<span class="Constant"> 0</span>
-        <span class="muControl">break-if</span> at-left?
-        <span class="Comment"># clear previous location</span>
-        column <span class="Special">&lt;-</span> subtract column,<span class="Constant"> 1</span>
-        *screen <span class="Special">&lt;-</span> put *screen, <span class="Constant">cursor-column:offset</span>, column
-        index <span class="Special">&lt;-</span> subtract index,<span class="Constant"> 1</span>
-        cursor:screen-cell <span class="Special">&lt;-</span> merge <span class="Constant">32/space</span>, <span class="Constant">7/white</span>
-        *buf <span class="Special">&lt;-</span> put-index *buf, index, cursor
-      <span class="Delimiter">}</span>
-      <span class="muControl">return</span>
+      <span class="Comment"># unless cursor is already at bottom</span>
+      bottom:num <span class="Special">&lt;-</span> subtract height,<span class="Constant"> 1</span>
+      at-bottom?:bool <span class="Special">&lt;-</span> greater-or-equal row, bottom
+      <span class="muControl">break-if</span> at-bottom?
+      <span class="Comment"># move it to the next row</span>
+      column <span class="Special">&lt;-</span> copy<span class="Constant"> 0</span>
+      *screen <span class="Special">&lt;-</span> put *screen, <span class="Constant">cursor-column:offset</span>, column
+      row <span class="Special">&lt;-</span> add row,<span class="Constant"> 1</span>
+      *screen <span class="Special">&lt;-</span> put *screen, <span class="Constant">cursor-row:offset</span>, row
     <span class="Delimiter">}</span>
-    cursor:screen-cell <span class="Special">&lt;-</span> merge c, color
-    *buf <span class="Special">&lt;-</span> put-index *buf, index, cursor
-    <span class="Comment"># increment column unless it's already all the way to the right</span>
+    <span class="muControl">return</span>
+  <span class="Delimiter">}</span>
+  <span class="Comment"># save character in fake screen</span>
+  index:num <span class="Special">&lt;-</span> multiply row, width
+  index <span class="Special">&lt;-</span> add index, column
+  buf:&amp;:@:screen-cell <span class="Special">&lt;-</span> get *screen, <span class="Constant">data:offset</span>
+  len:num <span class="Special">&lt;-</span> length *buf
+  <span class="Comment"># special-case: backspace</span>
+  <span class="Delimiter">{</span>
+    backspace?:bool <span class="Special">&lt;-</span> equal c,<span class="Constant"> 8</span>
+    <span class="muControl">break-unless</span> backspace?
     <span class="Delimiter">{</span>
-      right:num <span class="Special">&lt;-</span> subtract width,<span class="Constant"> 1</span>
-      at-right?:bool <span class="Special">&lt;-</span> greater-or-equal column, right
-      <span class="muControl">break-if</span> at-right?
-      column <span class="Special">&lt;-</span> add column,<span class="Constant"> 1</span>
+      <span class="Comment"># unless cursor is already at left margin</span>
+      at-left?:bool <span class="Special">&lt;-</span> lesser-or-equal column,<span class="Constant"> 0</span>
+      <span class="muControl">break-if</span> at-left?
+      <span class="Comment"># clear previous location</span>
+      column <span class="Special">&lt;-</span> subtract column,<span class="Constant"> 1</span>
       *screen <span class="Special">&lt;-</span> put *screen, <span class="Constant">cursor-column:offset</span>, column
+      index <span class="Special">&lt;-</span> subtract index,<span class="Constant"> 1</span>
+      cursor:screen-cell <span class="Special">&lt;-</span> merge <span class="Constant">32/space</span>, <span class="Constant">7/white</span>
+      *buf <span class="Special">&lt;-</span> put-index *buf, index, cursor
     <span class="Delimiter">}</span>
     <span class="muControl">return</span>
   <span class="Delimiter">}</span>
-  <span class="Comment"># otherwise, real screen</span>
-  print-character-to-display c, color, bg-color
+  cursor:screen-cell <span class="Special">&lt;-</span> merge c, color
+  *buf <span class="Special">&lt;-</span> put-index *buf, index, cursor
+  <span class="Comment"># increment column unless it's already all the way to the right</span>
+  <span class="Delimiter">{</span>
+    right:num <span class="Special">&lt;-</span> subtract width,<span class="Constant"> 1</span>
+    at-right?:bool <span class="Special">&lt;-</span> greater-or-equal column, right
+    <span class="muControl">break-if</span> at-right?
+    column <span class="Special">&lt;-</span> add column,<span class="Constant"> 1</span>
+    *screen <span class="Special">&lt;-</span> put *screen, <span class="Constant">cursor-column:offset</span>, column
+  <span class="Delimiter">}</span>
 ]
 
 <span class="muScenario">scenario</span> print-character-at-top-left [
@@ -400,27 +396,27 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color
   <span class="Constant">local-scope</span>
   <span class="Constant">load-ingredients</span>
   space:char <span class="Special">&lt;-</span> copy <span class="Constant">0/nul</span>
-  <span class="Comment"># if x exists, clear line in fake screen</span>
   <span class="Delimiter">{</span>
-    <span class="muControl">break-unless</span> screen
-    width:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">num-columns:offset</span>
-    column:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">cursor-column:offset</span>
-    original-column:num <span class="Special">&lt;-</span> copy column
-    <span class="Comment"># space over the entire line</span>
-    <span class="Delimiter">{</span>
-      right:num <span class="Special">&lt;-</span> subtract width,<span class="Constant"> 1</span>
-      done?:bool <span class="Special">&lt;-</span> greater-or-equal column, right
-      <span class="muControl">break-if</span> done?
-      print screen, space
-      column <span class="Special">&lt;-</span> add column,<span class="Constant"> 1</span>
-      <span class="muControl">loop</span>
-    <span class="Delimiter">}</span>
-    <span class="Comment"># now back to where the cursor was</span>
-    *screen <span class="Special">&lt;-</span> put *screen, <span class="Constant">cursor-column:offset</span>, original-column
+    <span class="muControl">break-if</span> screen
+    <span class="Comment"># real screen</span>
+    clear-line-on-display
     <span class="muControl">return</span>
   <span class="Delimiter">}</span>
-  <span class="Comment"># otherwise, real screen</span>
-  clear-line-on-display
+  <span class="Comment"># fake screen</span>
+  width:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">num-columns:offset</span>
+  column:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">cursor-column:offset</span>
+  original-column:num <span class="Special">&lt;-</span> copy column
+  <span class="Comment"># space over the entire line</span>
+  <span class="Delimiter">{</span>
+    right:num <span class="Special">&lt;-</span> subtract width,<span class="Constant"> 1</span>
+    done?:bool <span class="Special">&lt;-</span> greater-or-equal column, right
+    <span class="muControl">break-if</span> done?
+    print screen, space
+    column <span class="Special">&lt;-</span> add column,<span class="Constant"> 1</span>
+    <span class="muControl">loop</span>
+  <span class="Delimiter">}</span>
+  <span class="Comment"># now back to where the cursor was</span>
+  *screen <span class="Special">&lt;-</span> put *screen, <span class="Constant">cursor-column:offset</span>, original-column
 ]
 
 <span class="muRecipe">def</span> clear-line-until screen:&amp;:screen, right:num/inclusive<span class="muRecipe"> -&gt; </span>screen:&amp;:screen [
@@ -446,28 +442,29 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color
 <span class="muRecipe">def</span> cursor-position screen:&amp;:screen<span class="muRecipe"> -&gt; </span>row:num, column:num [
   <span class="Constant">local-scope</span>
   <span class="Constant">load-ingredients</span>
-  <span class="Comment"># if x exists, lookup cursor in fake screen</span>
   <span class="Delimiter">{</span>
-    <span class="muControl">break-unless</span> screen
-    row:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">cursor-row:offset</span>
-    column:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">cursor-column:offset</span>
+    <span class="muControl">break-if</span> screen
+    <span class="Comment"># real screen</span>
+    row, column <span class="Special">&lt;-</span> cursor-position-on-display
     <span class="muControl">return</span>
   <span class="Delimiter">}</span>
-  row, column <span class="Special">&lt;-</span> cursor-position-on-display
+  <span class="Comment"># fake screen</span>
+  row:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">cursor-row:offset</span>
+  column:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">cursor-column:offset</span>
 ]
 
 <span class="muRecipe">def</span> move-cursor screen:&amp;:screen, new-row:num, new-column:num<span class="muRecipe"> -&gt; </span>screen:&amp;:screen [
   <span class="Constant">local-scope</span>
   <span class="Constant">load-ingredients</span>
-  <span class="Comment"># if x exists, move cursor in fake screen</span>
   <span class="Delimiter">{</span>
-    <span class="muControl">break-unless</span> screen
-    *screen <span class="Special">&lt;-</span> put *screen, <span class="Constant">cursor-row:offset</span>, new-row
-    *screen <span class="Special">&lt;-</span> put *screen, <span class="Constant">cursor-column:offset</span>, new-column
+    <span class="muControl">break-if</span> screen
+    <span class="Comment"># real screen</span>
+    move-cursor-on-display new-row, new-column
     <span class="muControl">return</span>
   <span class="Delimiter">}</span>
-  <span class="Comment"># otherwise, real screen</span>
-  move-cursor-on-display new-row, new-column
+  <span class="Comment"># fake screen</span>
+  *screen <span class="Special">&lt;-</span> put *screen, <span class="Constant">cursor-row:offset</span>, new-row
+  *screen <span class="Special">&lt;-</span> put *screen, <span class="Constant">cursor-column:offset</span>, new-column
 ]
 
 <span class="muScenario">scenario</span> clear-line-erases-printed-characters [
@@ -504,85 +501,73 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color
 <span class="muRecipe">def</span> cursor-down screen:&amp;:screen<span class="muRecipe"> -&gt; </span>screen:&amp;:screen [
   <span class="Constant">local-scope</span>
   <span class="Constant">load-ingredients</span>
-  <span class="Comment"># if x exists, move cursor in fake screen</span>
   <span class="Delimiter">{</span>
-    <span class="muControl">break-unless</span> screen
-    <span class="Delimiter">{</span>
-      <span class="Comment"># increment row unless it's already all the way down</span>
-      height:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">num-rows:offset</span>
-      row:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">cursor-row:offset</span>
-      max:num <span class="Special">&lt;-</span> subtract height,<span class="Constant"> 1</span>
-      at-bottom?:bool <span class="Special">&lt;-</span> greater-or-equal row, max
-      <span class="muControl">break-if</span> at-bottom?
-      row <span class="Special">&lt;-</span> add row,<span class="Constant"> 1</span>
-      *screen <span class="Special">&lt;-</span> put *screen, <span class="Constant">cursor-row:offset</span>, row
-    <span class="Delimiter">}</span>
+    <span class="muControl">break-if</span> screen
+    <span class="Comment"># real screen</span>
+    move-cursor-down-on-display
     <span class="muControl">return</span>
   <span class="Delimiter">}</span>
-  <span class="Comment"># otherwise, real screen</span>
-  move-cursor-down-on-display
+  <span class="Comment"># fake screen</span>
+  height:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">num-rows:offset</span>
+  row:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">cursor-row:offset</span>
+  max:num <span class="Special">&lt;-</span> subtract height,<span class="Constant"> 1</span>
+  at-bottom?:bool <span class="Special">&lt;-</span> greater-or-equal row, max
+  <span class="muControl">return-if</span> at-bottom?
+  row <span class="Special">&lt;-</span> add row,<span class="Constant"> 1</span>
+  *screen <span class="Special">&lt;-</span> put *screen, <span class="Constant">cursor-row:offset</span>, row
 ]
 
 <span class="muRecipe">def</span> cursor-up screen:&amp;:screen<span class="muRecipe"> -&gt; </span>screen:&amp;:screen [
   <span class="Constant">local-scope</span>
   <span class="Constant">load-ingredients</span>
-  <span class="Comment"># if x exists, move cursor in fake screen</span>
   <span class="Delimiter">{</span>
-    <span class="muControl">break-unless</span> screen
-    <span class="Delimiter">{</span>
-      <span class="Comment"># decrement row unless it's already all the way up</span>
-      row:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">cursor-row:offset</span>
-      at-top?:bool <span class="Special">&lt;-</span> lesser-or-equal row,<span class="Constant"> 0</span>
-      <span class="muControl">break-if</span> at-top?
-      row <span class="Special">&lt;-</span> subtract row,<span class="Constant"> 1</span>
-      *screen <span class="Special">&lt;-</span> put *screen, <span class="Constant">cursor-row:offset</span>, row
-    <span class="Delimiter">}</span>
+    <span class="muControl">break-if</span> screen
+    <span class="Comment"># real screen</span>
+    move-cursor-up-on-display
     <span class="muControl">return</span>
   <span class="Delimiter">}</span>
-  <span class="Comment"># otherwise, real screen</span>
-  move-cursor-up-on-display
+  <span class="Comment"># fake screen</span>
+  row:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">cursor-row:offset</span>
+  at-top?:bool <span class="Special">&lt;-</span> lesser-or-equal row,<span class="Constant"> 0</span>
+  <span class="muControl">return-if</span> at-top?
+  row <span class="Special">&lt;-</span> subtract row,<span class="Constant"> 1</span>
+  *screen <span class="Special">&lt;-</span> put *screen, <span class="Constant">cursor-row:offset</span>, row
 ]
 
 <span class="muRecipe">def</span> cursor-right screen:&amp;:screen<span class="muRecipe"> -&gt; </span>screen:&amp;:screen [
   <span class="Constant">local-scope</span>
   <span class="Constant">load-ingredients</span>
-  <span class="Comment"># if x exists, move cursor in fake screen</span>
   <span class="Delimiter">{</span>
-    <span class="muControl">break-unless</span> screen
-    <span class="Delimiter">{</span>
-      <span class="Comment"># increment column unless it's already all the way to the right</span>
-      width:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">num-columns:offset</span>
-      column:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">cursor-column:offset</span>
-      max:num <span class="Special">&lt;-</span> subtract width,<span class="Constant"> 1</span>
-      at-bottom?:bool <span class="Special">&lt;-</span> greater-or-equal column, max
-      <span class="muControl">break-if</span> at-bottom?
-      column <span class="Special">&lt;-</span> add column,<span class="Constant"> 1</span>
-      *screen <span class="Special">&lt;-</span> put *screen, <span class="Constant">cursor-column:offset</span>, column
-    <span class="Delimiter">}</span>
+    <span class="muControl">break-if</span> screen
+    <span class="Comment"># real screen</span>
+    move-cursor-right-on-display
     <span class="muControl">return</span>
   <span class="Delimiter">}</span>
-  <span class="Comment"># otherwise, real screen</span>
-  move-cursor-right-on-display
+  <span class="Comment"># fake screen</span>
+  width:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">num-columns:offset</span>
+  column:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">cursor-column:offset</span>
+  max:num <span class="Special">&lt;-</span> subtract width,<span class="Constant"> 1</span>
+  at-bottom?:bool <span class="Special">&lt;-</span> greater-or-equal column, max
+  <span class="muControl">return-if</span> at-bottom?
+  column <span class="Special">&lt;-</span> add column,<span class="Constant"> 1</span>
+  *screen <span class="Special">&lt;-</span> put *screen, <span class="Constant">cursor-column:offset</span>, column
 ]
 
 <span class="muRecipe">def</span> cursor-left screen:&amp;:screen<span class="muRecipe"> -&gt; </span>screen:&amp;:screen [
   <span class="Constant">local-scope</span>
   <span class="Constant">load-ingredients</span>
-  <span class="Comment"># if x exists, move cursor in fake screen</span>
   <span class="Delimiter">{</span>
-    <span class="muControl">break-unless</span> screen
-    <span class="Delimiter">{</span>
-      <span class="Comment"># decrement column unless it's already all the way to the left</span>
-      column:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">cursor-column:offset</span>
-      at-top?:bool <span class="Special">&lt;-</span> lesser-or-equal column,<span class="Constant"> 0</span>
-      <span class="muControl">break-if</span> at-top?
-      column <span class="Special">&lt;-</span> subtract column,<span class="Constant"> 1</span>
-      *screen <span class="Special">&lt;-</span> put *screen, <span class="Constant">cursor-column:offset</span>, column
-    <span class="Delimiter">}</span>
+    <span class="muControl">break-if</span> screen
+    <span class="Comment"># real screen</span>
+    move-cursor-left-on-display
     <span class="muControl">return</span>
   <span class="Delimiter">}</span>
-  <span class="Comment"># otherwise, real screen</span>
-  move-cursor-left-on-display
+  <span class="Comment"># fake screen</span>
+  column:num <span class="Special">&lt;-</span> get *screen, <span class="Constant">cursor-column:offset</span>
+  at-top?:bool <span class="Special">&lt;-</span> lesser-or-equal column,<span class="Constant"> 0</span>
+  <span class="muControl">return-if</span> at-top?
+  column <span class="Special">&lt;-</span> subtract column,<span class="Constant"> 1</span>
+  *screen <span class="Special">&lt;-</span> put *screen, <span class="Constant">cursor-column:offset</span>, column
 ]
 
 <span class="muRecipe">def</span> cursor-to-start-of-line screen:&amp;:screen<span class="muRecipe"> -&gt; </span>screen:&amp;:screen [
@@ -610,76 +595,58 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color
 <span class="muRecipe">def</span> screen-width screen:&amp;:screen<span class="muRecipe"> -&gt; </span>width:num [
   <span class="Constant">local-scope</span>
   <span class="Constant">load-ingredients</span>
-  <span class="Comment"># if x exists, move cursor in fake screen</span>
   <span class="Delimiter">{</span>
     <span class="muControl">break-unless</span> screen
+    <span class="Comment"># fake screen</span>
     width <span class="Special">&lt;-</span> get *screen, <span class="Constant">num-columns:offset</span>
     <span class="muControl">return</span>
   <span class="Delimiter">}</span>
-  <span class="Comment"># otherwise, real screen</span>
+  <span class="Comment"># real screen</span>
   width <span class="Special">&lt;-</span> display-width
 ]
 
 <span class="muRecipe">def</span> screen-height screen:&amp;:screen<span class="muRecipe"> -&gt; </span>height:num [
   <span class="Constant">local-scope</span>
   <span class="Constant">load-ingredients</span>
-  <span class="Comment"># if x exists, move cursor in fake screen</span>
   <span class="Delimiter">{</span>
     <span class="muControl">break-unless</span> screen
+    <span class="Comment"># fake screen</span>
     height <span class="Special">&lt;-</span> get *screen, <span class="Constant">num-rows:offset</span>
     <span class="muControl">return</span>
   <span class="Delimiter">}</span>
-  <span class="Comment"># otherwise, real screen</span>
+  <span class="Comment"># real screen</span>
   height <span class="Special">&lt;-</span> display-height
 ]
 
 <span class="muRecipe">def</span> hide-cursor screen:&amp;:screen<span class="muRecipe"> -&gt; </span>screen:&amp;:screen [
   <span class="Constant">local-scope</span>
   <span class="Constant">load-ingredients</span>
-  <span class="Comment"># if x exists (not real display), do nothing</span>
-  <span class="Delimiter">{</span>
-    <span class="muControl">break-unless</span> screen
-    <span class="muControl">return</span>
-  <span class="Delimiter">}</span>
-  <span class="Comment"># otherwise, real screen</span>
+  <span class="muControl">reply-if</span> screen  <span class="Comment"># fake screen; do nothing</span>
+  <span class="Comment"># real screen</span>
   hide-cursor-on-display
 ]
 
 <span class="muRecipe">def</span> show-cursor screen:&amp;:screen<span class="muRecipe"> -&gt; </span>screen:&amp;:screen [
   <span class="Constant">local-scope</span>
   <span class="Constant">load-ingredients</span>
-  <span class="Comment"># if x exists (not real display), do nothing</span>
-  <span class="Delimiter">{</span>
-    <span class="muControl">break-unless</span> screen
-    <span class="muControl">return</span>
-  <span class="Delimiter">}</span>
-  <span class="Comment"># otherwise, real screen</span>
+  <span class="muControl">reply-if</span> screen  <span class="Comment"># fake screen; do nothing</span>
+  <span class="Comment"># real screen</span>
   show-cursor-on-display
 ]
 
 <span class="muRecipe">def</span> hide-screen screen:&amp;:screen<span class="muRecipe"> -&gt; </span>screen:&amp;:screen [
   <span class="Constant">local-scope</span>
   <span class="Constant">load-ingredients</span>
-  <span class="Comment"># if x exists (not real display), do nothing</span>
-  <span class="Comment"># todo: help test this</span>
-  <span class="Delimiter">{</span>
-    <span class="muControl">break-unless</span> screen
-    <span class="muControl">return</span>
-  <span class="Delimiter">}</span>
-  <span class="Comment"># otherwise, real screen</span>
+  <span class="muControl">reply-if</span> screen  <span class="Comment"># fake screen; do nothing</span>
+  <span class="Comment"># real screen</span>
   hide-display
 ]
 
 <span class="muRecipe">def</span> show-screen screen:&amp;:screen<span class="muRecipe"> -&gt; </span>screen:&amp;:screen [
   <span class="Constant">local-scope</span>
   <span class="Constant">load-ingredients</span>
-  <span class="Comment"># if x exists (not real display), do nothing</span>
-  <span class="Comment"># todo: help test this</span>
-  <span class="Delimiter">{</span>
-    <span class="muControl">break-unless</span> screen
-    <span class="muControl">return</span>
-  <span class="Delimiter">}</span>
-  <span class="Comment"># otherwise, real screen</span>
+  <span class="muControl">reply-if</span> screen  <span class="Comment"># fake screen; do nothing</span>
+  <span class="Comment"># real screen</span>
   show-display
 ]
 
diff --git a/html/084console.mu.html b/html/084console.mu.html
index 9271231a..5cb819de 100644
--- a/html/084console.mu.html
+++ b/html/084console.mu.html
@@ -123,7 +123,9 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color
   <span class="Constant">load-ingredients</span>
   <span class="Delimiter">{</span>
     _, found?:bool <span class="Special">&lt;-</span> read-event console
-    <span class="muControl">loop-unless</span> found?
+    <span class="muControl">break-if</span> found?
+    switch
+    <span class="muControl">loop</span>
   <span class="Delimiter">}</span>
 ]
 
@@ -131,11 +133,7 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color
 <span class="muRecipe">def</span> has-more-events? console:&amp;:console<span class="muRecipe"> -&gt; </span>result:bool [
   <span class="Constant">local-scope</span>
   <span class="Constant">load-ingredients</span>
-  <span class="Delimiter">{</span>
-    <span class="muControl">break-unless</span> console
-    <span class="Comment"># fake consoles should be plenty fast; never skip</span>
-    <span class="muControl">return</span> <span class="Constant">0/false</span>
-  <span class="Delimiter">}</span>
+  <span class="muControl">return-if</span> console, <span class="Constant">0/false</span>  <span class="Comment"># fake consoles should be plenty fast; never skip</span>
   result <span class="Special">&lt;-</span> interactions-left?
 ]
 </pre>
diff --git a/html/088file.mu.html b/html/088file.mu.html
index e6ef6e98..26fa0c7c 100644
--- a/html/088file.mu.html
+++ b/html/088file.mu.html
@@ -47,15 +47,21 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color
   <span class="Constant">local-scope</span>
   <span class="Constant">load-ingredients</span>
   <span class="Delimiter">{</span>
-    <span class="muControl">break-if</span> resources
-    <span class="Comment"># real file system</span>
-    file:num <span class="Special">&lt;-</span> $open-file-for-reading filename
-    assert file, <span class="Constant">[file not found]</span>
-    contents:&amp;:source:char, sink:&amp;:sink:char <span class="Special">&lt;-</span> new-channel<span class="Constant"> 30</span>
-    start-running receive-from-file file, sink
+    <span class="muControl">break-unless</span> resources
+    <span class="Comment"># fake file system</span>
+    contents <span class="Special">&lt;-</span> start-reading-from-fake-resources resources, filename
     <span class="muControl">return</span>
   <span class="Delimiter">}</span>
-  <span class="Comment"># fake file system</span>
+  <span class="Comment"># real file system</span>
+  file:num <span class="Special">&lt;-</span> $open-file-for-reading filename
+  assert file, <span class="Constant">[file not found]</span>
+  contents:&amp;:source:char, sink:&amp;:sink:char <span class="Special">&lt;-</span> new-channel<span class="Constant"> 30</span>
+  start-running receive-from-file file, sink
+]
+
+<span class="muRecipe">def</span> start-reading-from-fake-resources resources:&amp;:resources, resource:text<span class="muRecipe"> -&gt; </span>contents:&amp;:source:char [
+  <span class="Constant">local-scope</span>
+  <span class="Constant">load-ingredients</span>
   i:num <span class="Special">&lt;-</span> copy<span class="Constant"> 0</span>
   data:&amp;:@:resource <span class="Special">&lt;-</span> get *resources, <span class="Constant">data:offset</span>
   len:num <span class="Special">&lt;-</span> length *data
@@ -64,8 +70,8 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color
     <span class="muControl">break-if</span> done?
     tmp:resource <span class="Special">&lt;-</span> index *data, i
     i <span class="Special">&lt;-</span> add i,<span class="Constant"> 1</span>
-    curr-filename:text <span class="Special">&lt;-</span> get tmp, <span class="Constant">name:offset</span>
-    found?:bool <span class="Special">&lt;-</span> equal filename, curr-filename
+    curr-resource:text <span class="Special">&lt;-</span> get tmp, <span class="Constant">name:offset</span>
+    found?:bool <span class="Special">&lt;-</span> equal resource, curr-resource
     <span class="muControl">loop-unless</span> found?
     contents:&amp;:source:char, sink:&amp;:sink:char <span class="Special">&lt;-</span> new-channel<span class="Constant"> 30</span>
     curr-contents:text <span class="Special">&lt;-</span> get tmp, <span class="Constant">contents:offset</span>
@@ -109,16 +115,16 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color
   <span class="Constant">load-ingredients</span>
   source:&amp;:source:char, sink:&amp;:sink:char <span class="Special">&lt;-</span> new-channel<span class="Constant"> 30</span>
   <span class="Delimiter">{</span>
-    <span class="muControl">break-if</span> resources
-    <span class="Comment"># real file system</span>
-    file:num <span class="Special">&lt;-</span> $open-file-for-writing filename
-    assert file, <span class="Constant">[no such file]</span>
-    routine-id <span class="Special">&lt;-</span> start-running transmit-to-file file, source
+    <span class="muControl">break-unless</span> resources
+    <span class="Comment"># fake file system</span>
+    <span class="Comment"># beware: doesn't support multiple concurrent writes yet</span>
+    routine-id <span class="Special">&lt;-</span> start-running transmit-to-fake-file resources, filename, source
     <span class="muControl">reply</span>
   <span class="Delimiter">}</span>
-  <span class="Comment"># fake file system</span>
-  <span class="Comment"># beware: doesn't support multiple concurrent writes yet</span>
-  routine-id <span class="Special">&lt;-</span> start-running transmit-to-fake-file resources, filename, source
+  <span class="Comment"># real file system</span>
+  file:num <span class="Special">&lt;-</span> $open-file-for-writing filename
+  assert file, <span class="Constant">[no such file]</span>
+  routine-id <span class="Special">&lt;-</span> start-running transmit-to-file file, source
 ]
 
 <span class="muRecipe">def</span> transmit-to-file file:num, source:&amp;:source:char<span class="muRecipe"> -&gt; </span>source:&amp;:source:char [
diff --git a/html/091socket.cc.html b/html/091socket.cc.html
index 67f11eb1..e205508f 100644
--- a/html/091socket.cc.html
+++ b/html/091socket.cc.html
@@ -235,22 +235,32 @@ put<span class="Delimiter">(</span>Recipe_ordinal<span class="Delimiter">,</span
     raise &lt;&lt; maybe<span class="Delimiter">(</span>get<span class="Delimiter">(</span>Recipe<span class="Delimiter">,</span> r<span class="Delimiter">).</span>name<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;second ingredient of '$read-from-socket' should be a number, but got '&quot;</span> &lt;&lt; to_string<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>ingredients<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">))</span> &lt;&lt; <span class="Constant">&quot;'</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; end<span class="Delimiter">();</span>
     <span class="Identifier">break</span><span class="Delimiter">;</span>
   <span class="Delimiter">}</span>
-  <span class="Normal">if</span> <span class="Delimiter">(</span>SIZE<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>products<span class="Delimiter">)</span> != <span class="Constant">2</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
-    raise &lt;&lt; maybe<span class="Delimiter">(</span>get<span class="Delimiter">(</span>Recipe<span class="Delimiter">,</span> r<span class="Delimiter">).</span>name<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;'$read-from-socket' requires exactly two products, but got '&quot;</span> &lt;&lt; inst<span class="Delimiter">.</span>original_string &lt;&lt; <span class="Constant">&quot;'</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; end<span class="Delimiter">();</span>
+  <span class="Normal">int</span> nprod = SIZE<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>products<span class="Delimiter">);</span>
+  <span class="Normal">if</span> <span class="Delimiter">(</span>nprod == <span class="Constant">0</span> || nprod &gt; <span class="Constant">4</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
+    raise &lt;&lt; maybe<span class="Delimiter">(</span>get<span class="Delimiter">(</span>Recipe<span class="Delimiter">,</span> r<span class="Delimiter">).</span>name<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;'$read-from-socket' requires 1-4 products, but got '&quot;</span> &lt;&lt; inst<span class="Delimiter">.</span>original_string &lt;&lt; <span class="Constant">&quot;'</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; end<span class="Delimiter">();</span>
     <span class="Identifier">break</span><span class="Delimiter">;</span>
   <span class="Delimiter">}</span>
   <span class="Normal">if</span> <span class="Delimiter">(</span>!is_mu_text<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">)))</span> <span class="Delimiter">{</span>
     raise &lt;&lt; maybe<span class="Delimiter">(</span>get<span class="Delimiter">(</span>Recipe<span class="Delimiter">,</span> r<span class="Delimiter">).</span>name<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;first product of '$read-from-socket' should be a text (address array character), but got '&quot;</span> &lt;&lt; to_string<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">))</span> &lt;&lt; <span class="Constant">&quot;'</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; end<span class="Delimiter">();</span>
     <span class="Identifier">break</span><span class="Delimiter">;</span>
   <span class="Delimiter">}</span>
-  <span class="Normal">if</span> <span class="Delimiter">(</span>!is_mu_boolean<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">1</span><span class="Delimiter">)))</span> <span class="Delimiter">{</span>
-    raise &lt;&lt; maybe<span class="Delimiter">(</span>get<span class="Delimiter">(</span>Recipe<span class="Delimiter">,</span> r<span class="Delimiter">).</span>name<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;second product of '$read-from-socket' should be a boolean (eof?), but got '&quot;</span> &lt;&lt; to_string<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">1</span><span class="Delimiter">))</span> &lt;&lt; <span class="Constant">&quot;'</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; end<span class="Delimiter">();</span>
+  <span class="Normal">if</span> <span class="Delimiter">(</span>nprod &gt; <span class="Constant">1</span> &amp;&amp; !is_mu_boolean<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">1</span><span class="Delimiter">)))</span> <span class="Delimiter">{</span>
+    raise &lt;&lt; maybe<span class="Delimiter">(</span>get<span class="Delimiter">(</span>Recipe<span class="Delimiter">,</span> r<span class="Delimiter">).</span>name<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;second product of '$read-from-socket' should be a boolean (data received?), but got '&quot;</span> &lt;&lt; to_string<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">1</span><span class="Delimiter">))</span> &lt;&lt; <span class="Constant">&quot;'</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; end<span class="Delimiter">();</span>
+    <span class="Identifier">break</span><span class="Delimiter">;</span>
+  <span class="Delimiter">}</span>
+  <span class="Normal">if</span> <span class="Delimiter">(</span>nprod &gt; <span class="Constant">2</span> &amp;&amp; !is_mu_boolean<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">2</span><span class="Delimiter">)))</span> <span class="Delimiter">{</span>
+    raise &lt;&lt; maybe<span class="Delimiter">(</span>get<span class="Delimiter">(</span>Recipe<span class="Delimiter">,</span> r<span class="Delimiter">).</span>name<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;third product of '$read-from-socket' should be a boolean (eof?), but got '&quot;</span> &lt;&lt; to_string<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">2</span><span class="Delimiter">))</span> &lt;&lt; <span class="Constant">&quot;'</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; end<span class="Delimiter">();</span>
+    <span class="Identifier">break</span><span class="Delimiter">;</span>
+  <span class="Delimiter">}</span>
+  <span class="Normal">if</span> <span class="Delimiter">(</span>nprod &gt; <span class="Constant">3</span> &amp;&amp; !is_mu_number<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">3</span><span class="Delimiter">)))</span> <span class="Delimiter">{</span>
+    raise &lt;&lt; maybe<span class="Delimiter">(</span>get<span class="Delimiter">(</span>Recipe<span class="Delimiter">,</span> r<span class="Delimiter">).</span>name<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;fourth product of '$read-from-socket' should be a number (error code), but got '&quot;</span> &lt;&lt; to_string<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">3</span><span class="Delimiter">))</span> &lt;&lt; <span class="Constant">&quot;'</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; end<span class="Delimiter">();</span>
     <span class="Identifier">break</span><span class="Delimiter">;</span>
   <span class="Delimiter">}</span>
   <span class="Identifier">break</span><span class="Delimiter">;</span>
 <span class="Delimiter">}</span>
 <span class="Delimiter">:(before &quot;End Primitive Recipe Implementations&quot;)</span>
 <span class="Normal">case</span> _READ_FROM_SOCKET: <span class="Delimiter">{</span>
+  products<span class="Delimiter">.</span>resize<span class="Delimiter">(</span><span class="Constant">4</span><span class="Delimiter">);</span>
   <span class="Normal">long</span> <span class="Normal">long</span> <span class="Normal">int</span> x = <span class="Normal">static_cast</span>&lt;<span class="Normal">long</span> <span class="Normal">long</span> <span class="Normal">int</span>&gt;<span class="Delimiter">(</span>ingredients<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">).</span>at<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">));</span>
   socket_t* socket = <span class="Normal">reinterpret_cast</span>&lt;socket_t*&gt;<span class="Delimiter">(</span>x<span class="Delimiter">);</span>
   <span class="Comment">// 1. we'd like to simply read() from the socket</span>
@@ -260,26 +270,37 @@ put<span class="Delimiter">(</span>Recipe_ordinal<span class="Delimiter">,</span
   <span class="Comment">// 3. but poll() will block on EOF, so only use poll() on the very first</span>
   <span class="Comment">// $read-from-socket on a socket</span>
   <span class="Normal">if</span> <span class="Delimiter">(</span>!socket<span class="Delimiter">-&gt;</span>polled<span class="Delimiter">)</span> <span class="Delimiter">{</span>
-    socket<span class="Delimiter">-&gt;</span>polled = <span class="Constant">true</span><span class="Delimiter">;</span>
     pollfd p<span class="Delimiter">;</span>
     bzero<span class="Delimiter">(</span>&amp;p<span class="Delimiter">,</span> <span class="Normal">sizeof</span><span class="Delimiter">(</span>p<span class="Delimiter">));</span>
     p<span class="Delimiter">.</span>fd = socket<span class="Delimiter">-&gt;</span>fd<span class="Delimiter">;</span>
     p<span class="Delimiter">.</span>events = POLLIN | POLLHUP<span class="Delimiter">;</span>
-    <span class="Normal">if</span> <span class="Delimiter">(</span>poll<span class="Delimiter">(</span>&amp;p<span class="Delimiter">,</span> <span class="Comment">/*</span><span class="Comment">num pollfds</span><span class="Comment">*/</span><span class="Constant">1</span><span class="Delimiter">,</span> <span class="Comment">/*</span><span class="Comment">no timeout</span><span class="Comment">*/</span>-<span class="Constant">1</span><span class="Delimiter">)</span> &lt;= <span class="Constant">0</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
+    <span class="Normal">int</span> status = poll<span class="Delimiter">(</span>&amp;p<span class="Delimiter">,</span> <span class="Comment">/*</span><span class="Comment">num pollfds</span><span class="Comment">*/</span><span class="Constant">1</span><span class="Delimiter">,</span> <span class="Comment">/*</span><span class="Comment">timeout</span><span class="Comment">*/</span><span class="Constant">100</span><span class="Comment">/*</span><span class="Comment">ms</span><span class="Comment">*/</span><span class="Delimiter">);</span>
+    <span class="Normal">if</span> <span class="Delimiter">(</span>status == <span class="Constant">0</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
+      products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">).</span>push_back<span class="Delimiter">(</span><span class="Comment">/*</span><span class="Comment">no data</span><span class="Comment">*/</span><span class="Constant">0</span><span class="Delimiter">);</span>
+      products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">1</span><span class="Delimiter">).</span>push_back<span class="Delimiter">(</span><span class="Comment">/*</span><span class="Comment">found</span><span class="Comment">*/</span><span class="Constant">false</span><span class="Delimiter">);</span>
+      products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">2</span><span class="Delimiter">).</span>push_back<span class="Delimiter">(</span><span class="Comment">/*</span><span class="Comment">eof</span><span class="Comment">*/</span><span class="Constant">false</span><span class="Delimiter">);</span>
+      products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">3</span><span class="Delimiter">).</span>push_back<span class="Delimiter">(</span><span class="Comment">/*</span><span class="Comment">error</span><span class="Comment">*/</span><span class="Constant">0</span><span class="Delimiter">);</span>
+      <span class="Identifier">break</span><span class="Delimiter">;</span>
+    <span class="Delimiter">}</span>
+    <span class="Normal">else</span> <span class="Normal">if</span> <span class="Delimiter">(</span>status &lt; <span class="Constant">0</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
+      <span class="Normal">int</span> error_code = errno<span class="Delimiter">;</span>
       raise &lt;&lt; maybe<span class="Delimiter">(</span>current_recipe_name<span class="Delimiter">())</span> &lt;&lt; <span class="Constant">&quot;error in $read-from-socket</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; end<span class="Delimiter">();</span>
-      products<span class="Delimiter">.</span>resize<span class="Delimiter">(</span><span class="Constant">2</span><span class="Delimiter">);</span>
-      products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">).</span>push_back<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">);</span>
-      products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">1</span><span class="Delimiter">).</span>push_back<span class="Delimiter">(</span><span class="Constant">false</span><span class="Delimiter">);</span>
+      products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">).</span>push_back<span class="Delimiter">(</span><span class="Comment">/*</span><span class="Comment">no data</span><span class="Comment">*/</span><span class="Constant">0</span><span class="Delimiter">);</span>
+      products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">1</span><span class="Delimiter">).</span>push_back<span class="Delimiter">(</span><span class="Comment">/*</span><span class="Comment">found</span><span class="Comment">*/</span><span class="Constant">false</span><span class="Delimiter">);</span>
+      products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">2</span><span class="Delimiter">).</span>push_back<span class="Delimiter">(</span><span class="Comment">/*</span><span class="Comment">eof</span><span class="Comment">*/</span><span class="Constant">false</span><span class="Delimiter">);</span>
+      products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">3</span><span class="Delimiter">).</span>push_back<span class="Delimiter">(</span>error_code<span class="Delimiter">);</span>
       <span class="Identifier">break</span><span class="Delimiter">;</span>
     <span class="Delimiter">}</span>
+    socket<span class="Delimiter">-&gt;</span>polled = <span class="Constant">true</span><span class="Delimiter">;</span>
   <span class="Delimiter">}</span>
   <span class="Normal">int</span> bytes = <span class="Normal">static_cast</span>&lt;<span class="Normal">int</span>&gt;<span class="Delimiter">(</span>ingredients<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">1</span><span class="Delimiter">).</span>at<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">));</span>
   <span class="Normal">char</span>* contents = <span class="Normal">new</span> <span class="Normal">char</span>[bytes]<span class="Delimiter">;</span>
   bzero<span class="Delimiter">(</span>contents<span class="Delimiter">,</span> bytes<span class="Delimiter">);</span>
   <span class="Normal">int</span> bytes_read = recv<span class="Delimiter">(</span>socket<span class="Delimiter">-&gt;</span>fd<span class="Delimiter">,</span> contents<span class="Delimiter">,</span> bytes-<span class="Comment">/*</span><span class="Comment">terminal null</span><span class="Comment">*/</span><span class="Constant">1</span><span class="Delimiter">,</span> MSG_DONTWAIT<span class="Delimiter">);</span>
-  products<span class="Delimiter">.</span>resize<span class="Delimiter">(</span><span class="Constant">2</span><span class="Delimiter">);</span>
   products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">).</span>push_back<span class="Delimiter">(</span>new_mu_text<span class="Delimiter">(</span>contents<span class="Delimiter">));</span>
-  products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">1</span><span class="Delimiter">).</span>push_back<span class="Delimiter">(</span>bytes_read &lt;= <span class="Constant">0</span><span class="Delimiter">);</span>
+  products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">1</span><span class="Delimiter">).</span>push_back<span class="Delimiter">(</span><span class="Comment">/*</span><span class="Comment">found</span><span class="Comment">*/</span><span class="Constant">true</span><span class="Delimiter">);</span>
+  products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">2</span><span class="Delimiter">).</span>push_back<span class="Delimiter">(</span><span class="Comment">/*</span><span class="Comment">eof</span><span class="Comment">*/</span>bytes_read &lt;= <span class="Constant">0</span><span class="Delimiter">);</span>
+  products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">3</span><span class="Delimiter">).</span>push_back<span class="Delimiter">(</span><span class="Comment">/*</span><span class="Comment">error</span><span class="Comment">*/</span><span class="Constant">0</span><span class="Delimiter">);</span>
   <span class="Normal">delete</span> contents<span class="Delimiter">;</span>
   <span class="Identifier">break</span><span class="Delimiter">;</span>
 <span class="Delimiter">}</span>
diff --git a/html/092socket.mu.html b/html/092socket.mu.html
index 3b18e2cd..78321183 100644
--- a/html/092socket.mu.html
+++ b/html/092socket.mu.html
@@ -19,7 +19,6 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color
 .Comment { color: #9090ff; }
 .Constant { color: #00a0a0; }
 .Special { color: #c00000; }
-.CommentedCode { color: #6c6c6c; }
 .muRecipe { color: #ff8700; }
 .muScenario { color: #00af00; }
 -->
@@ -33,98 +32,95 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color
 </head>
 <body>
 <pre id='vimCodeElement'>
-<span class="Comment"># Wrappers around socket primitives that take a 'local-network' object and are</span>
-<span class="Comment"># thus easier to test.</span>
-<span class="Comment">#</span>
-<span class="Comment"># The current semantics of fake port-connections don't match UNIX socket ones,</span>
-<span class="Comment"># but we'll improve them as we learn more.</span>
+<span class="Comment"># Wrappers around socket primitives that are easier to test.</span>
 
-<span class="muData">container</span> local-network [
-  data:&amp;:@:port-connection
-]
-
-<span class="Comment"># Port connections represent connections to ports on localhost.</span>
-<span class="Comment"># Before passing a local-network object to network functions</span>
-<span class="Comment"># `start-reading-socket` and `start-writing-socket`, add port-connections to</span>
-<span class="Comment"># the local-network.</span>
-<span class="Comment">#</span>
-<span class="Comment"># For reading, `receive-from-socket` will check for a</span>
-<span class="Comment"># port-connection on the port parameter that's been passed in. If there's</span>
-<span class="Comment"># no port-connection for that port, it will return nothing and log an error.</span>
-<span class="Comment"># If there is a port-connection for that port, it will transmit the contents</span>
-<span class="Comment"># to the provided sink.</span>
-<span class="Comment">#</span>
-<span class="Comment"># For writing, `start-writing-socket` returns a sink connecting the</span>
-<span class="Comment"># caller to the socket on the passed-in port.</span>
-<span class="muData">container</span> port-connection [
-  port:num
-  contents:text
-]
-
-<span class="muRecipe">def</span> new-port-connection port:num, contents:text<span class="muRecipe"> -&gt; </span>p:&amp;:port-connection [
+<span class="Comment"># To test server operations, just run a real client against localhost.</span>
+<span class="muScenario">scenario</span> example-server-test [
   <span class="Constant">local-scope</span>
-  <span class="Constant">load-ingredients</span>
-  p:&amp;:port-connection <span class="Special">&lt;-</span> new <span class="Constant">port-connection:type</span>
-  *p <span class="Special">&lt;-</span> merge port, contents
+  <span class="Comment"># test server without a fake on a random (real) port</span>
+  <span class="Comment"># that way repeatedly running the test will give ports time to timeout and</span>
+  <span class="Comment"># close before reusing them</span>
+  make-random-nondeterministic
+  port:num <span class="Special">&lt;-</span> random-in-range <span class="Constant">0/real-random-numbers</span>,<span class="Constant"> 8000</span>,<span class="Constant"> 8100</span>
+  run [
+    socket:num <span class="Special">&lt;-</span> $open-server-socket port
+    assert socket, <span class="Constant">[ </span>
+<span class="Constant">F - example-server-test: $open-server-socket failed]</span>
+    handler-routine:number <span class="Special">&lt;-</span> start-running serve-one-request socket, example-handler
+  ]
+  source:&amp;:source:char <span class="Special">&lt;-</span> start-reading-from-network <span class="Constant">0/real-resources</span>, <span class="Constant">[localhost/]</span>, port
+  response:text <span class="Special">&lt;-</span> drain source
+  10:@:char/<span class="Special">raw</span> <span class="Special">&lt;-</span> copy *response
+  memory-should-contain [
+    10:array:character <span class="Special">&lt;-</span> <span class="Constant">[abc]</span>
+  ]
 ]
-
-<span class="muRecipe">def</span> new-fake-network<span class="muRecipe"> -&gt; </span>n:&amp;:local-network [
+<span class="Comment"># helper just for this scenario</span>
+<span class="muRecipe">def</span> example-handler query:text<span class="muRecipe"> -&gt; </span>response:text [
   <span class="Constant">local-scope</span>
   <span class="Constant">load-ingredients</span>
-  n:&amp;:local-network <span class="Special">&lt;-</span> new <span class="Constant">local-network:type</span>
-  local-network-ports:&amp;:@:port-connection <span class="Special">&lt;-</span> new <span class="Constant">port-connection:type</span>,<span class="Constant"> 0</span>
-  *n <span class="Special">&lt;-</span> put *n, <span class="Constant">data:offset</span>, local-network-ports
+  <span class="muControl">reply</span> <span class="Constant">[abc]</span>
 ]
 
-<span class="muScenario">scenario</span> write-to-fake-socket [
+<span class="Comment"># To test client operations, use `assume-resources` with a filename that</span>
+<span class="Comment"># begins with a hostname. (Filenames starting with '/' are assumed to be</span>
+<span class="Comment"># local.)</span>
+<span class="muScenario">scenario</span> example-client-test [
   <span class="Constant">local-scope</span>
-  single-port-network:&amp;:local-network <span class="Special">&lt;-</span> new-fake-network
-  sink:&amp;:sink:char, writer:num/routine <span class="Special">&lt;-</span> start-writing-socket single-port-network,<span class="Constant"> 8080</span>
-  sink <span class="Special">&lt;-</span> write sink, <span class="Constant">120/x</span>
-  close sink
-  wait-for-routine writer
-  tested-port-connections:&amp;:@:port-connection <span class="Special">&lt;-</span> get *single-port-network, <span class="Constant">data:offset</span>
-  tested-port-connection:port-connection <span class="Special">&lt;-</span> index *tested-port-connections,<span class="Constant"> 0</span>
-  contents:text <span class="Special">&lt;-</span> get tested-port-connection, <span class="Constant">contents:offset</span>
+  assume-resources [
+    <span class="Constant">[example.com/]</span> <span class="Special">&lt;-</span> [
+<span class="Constant">      |abc|</span>
+    ]
+  ]
+  run [
+    source:&amp;:source:char <span class="Special">&lt;-</span> start-reading-from-network resources, <span class="Constant">[example.com/]</span>
+  ]
+  contents:text <span class="Special">&lt;-</span> drain source
   10:@:char/<span class="Special">raw</span> <span class="Special">&lt;-</span> copy *contents
   memory-should-contain [
-    10:array:character <span class="Special">&lt;-</span> <span class="Constant">[x]</span>
+    10:array:character <span class="Special">&lt;-</span> <span class="Constant">[abc</span>
+<span class="Constant">]</span>
   ]
 ]
 
+<span class="muData">type</span> request-handler = (recipe text<span class="muRecipe"> -&gt; </span>text)
+
+<span class="muRecipe">def</span> serve-one-request socket:num, request-handler:request-handler [
+  <span class="Constant">local-scope</span>
+  <span class="Constant">load-ingredients</span>
+  session:num <span class="Special">&lt;-</span> $accept socket
+  assert session, <span class="Constant">[ </span>
+<span class="Constant">F - example-server-test: $accept failed]</span>
+  contents:&amp;:source:char, sink:&amp;:sink:char <span class="Special">&lt;-</span> new-channel<span class="Constant"> 30</span>
+  sink <span class="Special">&lt;-</span> start-running receive-from-socket session, sink
+  query:text <span class="Special">&lt;-</span> drain contents
+  response:text <span class="Special">&lt;-</span> call request-handler, query
+  write-to-socket session, response
+  $close-socket session
+]
+
 <span class="muRecipe">def</span> start-reading-from-network resources:&amp;:resources, uri:text<span class="muRecipe"> -&gt; </span>contents:&amp;:source:char [
   <span class="Constant">local-scope</span>
   <span class="Constant">load-ingredients</span>
   <span class="Delimiter">{</span>
-    <span class="muControl">break-if</span> resources
-    <span class="Comment"># real network</span>
-    host:text, path:text <span class="Special">&lt;-</span> split-at uri, <span class="Constant">47/slash</span>
-    socket:num <span class="Special">&lt;-</span> $open-client-socket host, <span class="Constant">80/http-port</span>
-    assert socket, <span class="Constant">[contents]</span>
-    req:text <span class="Special">&lt;-</span> interpolate <span class="Constant">[GET _ HTTP/1.1]</span>, path
-    request-socket socket, req
-    contents:&amp;:source:char, sink:&amp;:sink:char <span class="Special">&lt;-</span> new-channel<span class="Constant"> 10000</span>
-    start-running receive-from-socket socket, sink
+    port:num, port-found?:boolean <span class="Special">&lt;-</span> <span class="Constant">next-ingredient</span>
+    <span class="muControl">break-if</span> port-found?
+    port <span class="Special">&lt;-</span> copy <span class="Constant">80/http-port</span>
+  <span class="Delimiter">}</span>
+  <span class="Delimiter">{</span>
+    <span class="muControl">break-unless</span> resources
+    <span class="Comment"># fake network</span>
+    contents <span class="Special">&lt;-</span> start-reading-from-fake-resources resources, uri
     <span class="muControl">return</span>
   <span class="Delimiter">}</span>
-  <span class="Comment"># fake network</span>
-<span class="CommentedCode">#?   i:num &lt;- copy 0</span>
-<span class="CommentedCode">#?   data:&amp;:@:resource &lt;- get *fs, data:offset</span>
-<span class="CommentedCode">#?   len:num &lt;- length *data</span>
-<span class="CommentedCode">#?   {</span>
-<span class="CommentedCode">#?     done?:bool &lt;- greater-or-equal i, len</span>
-<span class="CommentedCode">#?     break-if done?</span>
-<span class="CommentedCode">#?     tmp:resource &lt;- index *data, i</span>
-<span class="CommentedCode">#?     i &lt;- add i, 1</span>
-<span class="CommentedCode">#?     curr-filename:text &lt;- get tmp, name:offset</span>
-<span class="CommentedCode">#?     found?:bool &lt;- equal filename, curr-filename</span>
-<span class="CommentedCode">#?     loop-unless found?</span>
-<span class="CommentedCode">#?     contents:&amp;:source:char, sink:&amp;:sink:char &lt;- new-channel 30</span>
-<span class="CommentedCode">#?     curr-contents:text &lt;- get tmp, contents:offset</span>
-<span class="CommentedCode">#?     start-running transmit-from-text curr-contents, sink</span>
-<span class="CommentedCode">#?     return</span>
-<span class="CommentedCode">#?   }</span>
-  <span class="muControl">return</span> <span class="Constant">0/not-found</span>
+  <span class="Comment"># real network</span>
+  host:text, path:text <span class="Special">&lt;-</span> split-at uri, <span class="Constant">47/slash</span>
+  socket:num <span class="Special">&lt;-</span> $open-client-socket host, port
+  assert socket, <span class="Constant">[contents]</span>
+  req:text <span class="Special">&lt;-</span> interpolate <span class="Constant">[GET _ HTTP/1.1]</span>, path
+  request-socket socket, req
+  contents:&amp;:source:char, sink:&amp;:sink:char <span class="Special">&lt;-</span> new-channel<span class="Constant"> 10000</span>
+  start-running receive-from-socket socket, sink
 ]
 
 <span class="muRecipe">def</span> request-socket socket:num, s:text<span class="muRecipe"> -&gt; </span>socket:num [
@@ -138,70 +134,18 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color
   $write-to-socket socket, <span class="Constant">10/lf</span>
 ]
 
-<span class="muRecipe">def</span> start-writing-socket network:&amp;:local-network, port:num<span class="muRecipe"> -&gt; </span>sink:&amp;:sink:char, routine-id:num [
-  <span class="Constant">local-scope</span>
-  <span class="Constant">load-ingredients</span>
-  source:&amp;:source:char, sink:&amp;:sink:char <span class="Special">&lt;-</span> new-channel<span class="Constant"> 30</span>
-  <span class="Delimiter">{</span>
-    <span class="muControl">break-if</span> network
-    socket:num <span class="Special">&lt;-</span> $open-server-socket port
-    session:num <span class="Special">&lt;-</span> $accept socket
-    <span class="Comment"># TODO Create channel implementation of write-to-socket.</span>
-    <span class="muControl">return</span> sink, <span class="Constant">0/routine-id</span>
-  <span class="Delimiter">}</span>
-  <span class="Comment"># fake network</span>
-  routine-id <span class="Special">&lt;-</span> start-running transmit-to-fake-socket network, port, source
-]
-
-<span class="muRecipe">def</span> transmit-to-fake-socket network:&amp;:local-network, port:num, source:&amp;:source:char<span class="muRecipe"> -&gt; </span>network:&amp;:local-network, source:&amp;:source:char [
-  <span class="Constant">local-scope</span>
-  <span class="Constant">load-ingredients</span>
-  <span class="Comment"># compute new port connection contents</span>
-  buf:&amp;:buffer <span class="Special">&lt;-</span> new-buffer<span class="Constant"> 30</span>
-  <span class="Delimiter">{</span>
-    c:char, done?:bool, source <span class="Special">&lt;-</span> read source
-    <span class="muControl">break-unless</span> c
-    buf <span class="Special">&lt;-</span> append buf, c
-    <span class="muControl">break-if</span> done?
-    <span class="muControl">loop</span>
-  <span class="Delimiter">}</span>
-  contents:text <span class="Special">&lt;-</span> buffer-to-array buf
-  new-port-connection:&amp;:port-connection <span class="Special">&lt;-</span> new-port-connection port, contents
-  <span class="Comment"># Got the contents of the channel, time to write to fake port.</span>
-  i:num <span class="Special">&lt;-</span> copy<span class="Constant"> 0</span>
-  port-connections:&amp;:@:port-connection <span class="Special">&lt;-</span> get *network, <span class="Constant">data:offset</span>
-  len:num <span class="Special">&lt;-</span> length *port-connections
-  <span class="Delimiter">{</span>
-    done?:bool <span class="Special">&lt;-</span> greater-or-equal i, len
-    <span class="muControl">break-if</span> done?
-    current:port-connection <span class="Special">&lt;-</span> index *port-connections, i
-    current-port:num <span class="Special">&lt;-</span> get current, <span class="Constant">port:offset</span>
-    ports-match?:bool <span class="Special">&lt;-</span> equal current-port, port
-    i <span class="Special">&lt;-</span> add i,<span class="Constant"> 1</span>
-    <span class="muControl">loop-unless</span> ports-match?
-    <span class="Comment"># Found an existing connection on this port, overwrite.</span>
-    put-index *port-connections, i, *new-port-connection
-    <span class="muControl">reply</span>
-  <span class="Delimiter">}</span>
-  <span class="Comment"># Couldn't find an existing connection on this port, initialize a new one.</span>
-  new-len:num <span class="Special">&lt;-</span> add len,<span class="Constant"> 1</span>
-  new-port-connections:&amp;:@:port-connection <span class="Special">&lt;-</span> new <span class="Constant">port-connection:type</span>, new-len
-  put *network, <span class="Constant">data:offset</span>, new-port-connections
-  i:num <span class="Special">&lt;-</span> copy<span class="Constant"> 0</span>
-  <span class="Delimiter">{</span>
-    done?:bool <span class="Special">&lt;-</span> greater-or-equal i, len
-    <span class="muControl">break-if</span> done?
-    tmp:port-connection <span class="Special">&lt;-</span> index *port-connections, i
-    put-index *new-port-connections, i, tmp
-  <span class="Delimiter">}</span>
-  put-index *new-port-connections, len, *new-port-connection
-]
-
 <span class="muRecipe">def</span> receive-from-socket socket:num, sink:&amp;:sink:char<span class="muRecipe"> -&gt; </span>sink:&amp;:sink:char [
   <span class="Constant">local-scope</span>
   <span class="Constant">load-ingredients</span>
   <span class="Delimiter">{</span>
-    req:text, eof?:bool <span class="Special">&lt;-</span> $read-from-socket socket, <span class="Constant">4096/bytes</span>
+<span class="Constant">    +next-attempt</span>
+    req:text, found?:bool, eof?:bool, error:num <span class="Special">&lt;-</span> $read-from-socket socket, <span class="Constant">4096/bytes</span>
+    <span class="muControl">break-if</span> error
+    <span class="Delimiter">{</span>
+      <span class="muControl">break-if</span> found?
+      switch
+      <span class="muControl">loop</span> <span class="Constant">+next-attempt</span>
+    <span class="Delimiter">}</span>
     bytes-read:num <span class="Special">&lt;-</span> length *req
     i:num <span class="Special">&lt;-</span> copy<span class="Constant"> 0</span>
     <span class="Delimiter">{</span>