about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--020run.cc1
-rw-r--r--070display.cc79
-rw-r--r--074keyboard.mu90
-rw-r--r--075scenario_console.cc (renamed from 079scenario_events.cc)180
-rw-r--r--075scenario_keyboard.cc84
-rw-r--r--076scenario_console_test.mu25
-rw-r--r--076scenario_keyboard_test.mu23
-rw-r--r--077mouse.cc39
-rw-r--r--078mouse.mu43
-rwxr-xr-xbuild_and_test_until4
10 files changed, 222 insertions, 346 deletions
diff --git a/020run.cc b/020run.cc
index 3a9c08e5..cf4cab0c 100644
--- a/020run.cc
+++ b/020run.cc
@@ -72,6 +72,7 @@ void run_current_routine()
     // Instructions below will write to 'products'.
     vector<vector<double> > products;
 //?     cerr << "AAA 8: " << current_instruction().operation << " ^" << Recipe[current_instruction().operation].name << "$\n"; //? 1
+//?     cerr << "% " << current_recipe_name() << "/" << current_step_index() << ": " << Memory[1013] << ' ' << Memory[1014] << '\n'; //? 1
     switch (current_instruction().operation) {
       // Primitive Recipe Implementations
       case COPY: {
diff --git a/070display.cc b/070display.cc
index 0ca9fd13..8cedf9e3 100644
--- a/070display.cc
+++ b/070display.cc
@@ -1,7 +1,7 @@
 //: Take charge of the text-mode display and keyboard.
 
-// uncomment to debug console programs
 :(before "End Globals")
+// uncomment to debug console programs
 //? ofstream LOG("log.txt");
 
 //:: Display management
@@ -219,47 +219,70 @@ case DISPLAY_HEIGHT: {
   break;
 }
 
-//:: Keyboard management
+//:: Keyboard/mouse management
 
 :(before "End Primitive Recipe Declarations")
-WAIT_FOR_KEY_FROM_KEYBOARD,
+WAIT_FOR_SOME_INTERACTION,
 :(before "End Primitive Recipe Numbers")
-Recipe_number["wait-for-key-from-keyboard"] = WAIT_FOR_KEY_FROM_KEYBOARD;
+Recipe_number["wait-for-some-interaction"] = WAIT_FOR_SOME_INTERACTION;
 :(before "End Primitive Recipe Implementations")
-case WAIT_FOR_KEY_FROM_KEYBOARD: {
+case WAIT_FOR_SOME_INTERACTION: {
   tb_event event;
-  do {
-    tb_poll_event(&event);
-  } while (event.type != TB_EVENT_KEY);
-  long long int result = event.key ? event.key : event.ch;
-  if (result == TB_KEY_CTRL_C) tb_shutdown(), exit(1);
-  if (result == TB_KEY_BACKSPACE2) result = TB_KEY_BACKSPACE;
-  if (result == TB_KEY_CARRIAGE_RETURN) result = TB_KEY_NEWLINE;
-  products.resize(1);
-  products.at(0).push_back(result);
+  tb_poll_event(&event);
   break;
 }
 
 :(before "End Primitive Recipe Declarations")
-READ_KEY_FROM_KEYBOARD,
+CHECK_FOR_INTERACTION,
 :(before "End Primitive Recipe Numbers")
-Recipe_number["read-key-from-keyboard"] = READ_KEY_FROM_KEYBOARD;
+Recipe_number["check-for-interaction"] = CHECK_FOR_INTERACTION;
 :(before "End Primitive Recipe Implementations")
-case READ_KEY_FROM_KEYBOARD: {
+case CHECK_FOR_INTERACTION: {
+  products.resize(2);  // result and status
   tb_event event;
   int event_type = tb_peek_event(&event, 5/*ms*/);
-  long long int result = 0;
-  long long int found = false;
-//?   cerr << event_type << '\n'; //? 1
+  if (event_type == TB_EVENT_KEY && event.ch) {
+    products.at(0).push_back(/*text event*/0);
+    products.at(0).push_back(event.ch);
+    products.at(0).push_back(0);
+    products.at(0).push_back(0);
+    products.at(1).push_back(/*found*/true);
+    break;
+  }
+  // treat keys within ascii as unicode characters
+  if (event_type == TB_EVENT_KEY && event.key < 0xff) {
+    products.at(0).push_back(/*text event*/0);
+    if (event.key == TB_KEY_CTRL_C) tb_shutdown(), exit(1);
+    if (event.key == TB_KEY_BACKSPACE2) event.key = TB_KEY_BACKSPACE;
+    if (event.key == TB_KEY_CARRIAGE_RETURN) event.key = TB_KEY_NEWLINE;
+    products.at(0).push_back(event.key);
+    products.at(0).push_back(0);
+    products.at(0).push_back(0);
+    products.at(1).push_back(/*found*/true);
+    break;
+  }
+  // keys outside ascii aren't unicode characters but arbitrary termbox inventions
   if (event_type == TB_EVENT_KEY) {
-    result = event.key ? event.key : event.ch;
-    if (result == TB_KEY_CTRL_C) tb_shutdown(), exit(1);
-    if (result == TB_KEY_BACKSPACE2) result = TB_KEY_BACKSPACE;
-    if (result == TB_KEY_CARRIAGE_RETURN) result = TB_KEY_NEWLINE;
-    found = true;
+    products.at(0).push_back(/*keycode event*/1);
+    products.at(0).push_back(event.key);
+    products.at(0).push_back(0);
+    products.at(0).push_back(0);
+    products.at(1).push_back(/*found*/true);
+    break;
   }
-  products.resize(2);
-  products.at(0).push_back(result);
-  products.at(1).push_back(found);
+  if (event_type == TB_EVENT_MOUSE) {
+    products.at(0).push_back(/*mouse event*/1);
+    products.at(0).push_back(event.key);  // which button, etc.
+    products.at(0).push_back(event.y);  // row
+    products.at(0).push_back(event.x);  // column
+    products.at(1).push_back(/*found*/true);
+    break;
+  }
+  // ignore TB_EVENT_RESIZE events for now
+  products.at(0).push_back(0);
+  products.at(0).push_back(0);
+  products.at(0).push_back(0);
+  products.at(0).push_back(0);
+  products.at(1).push_back(/*found*/false);
   break;
 }
diff --git a/074keyboard.mu b/074keyboard.mu
index 0d348e55..9538546e 100644
--- a/074keyboard.mu
+++ b/074keyboard.mu
@@ -1,72 +1,90 @@
-# Wrappers around keyboard primitives that take a 'keyboard' object and are thus
-# easier to test.
+# Wrappers around interaction primitives that take a potentially fake object
+# and are thus easier to test.
 
-# display:screen as keyboard:__? Can't think of another word.
-container keyboard [
+exclusive-container event [
+  text:character
+  keycode:number  # keys on keyboard without a unicode representation
+  pointer:single-touch-event  # mouse, track ball, etc.
+  # update the assume-console handler if you add more variants
+]
+
+container single-touch-event [
+  type:number
+  row:number
+  column:number
+]
+
+container console [
   index:number
-  data:address:array:character
+  data:address:array:event
 ]
 
-recipe new-fake-keyboard [
+recipe new-fake-console [
   default-space:address:array:location <- new location:type, 30:literal
-  result:address:keyboard <- new keyboard:type
-  buf:address:address:array:character <- get-address result:address:keyboard/deref, data:offset
+  result:address:console <- new console:type
+  buf:address:address:array:character <- get-address result:address:console/deref, data:offset
 #?   $start-tracing #? 1
   buf:address:address:array:character/deref <- next-ingredient
 #?   $stop-tracing #? 1
-  idx:address:number <- get-address result:address:keyboard/deref, index:offset
+  idx:address:number <- get-address result:address:console/deref, index:offset
   idx:address:number/deref <- copy 0:literal
-  reply result:address:keyboard
+  reply result:address:console
 ]
 
-recipe read-key [
+recipe read-event [
   default-space:address:array:location <- new location:type, 30:literal
-  x:address:keyboard <- next-ingredient
+  x:address:console <- next-ingredient
   {
-    break-unless x:address:keyboard
-    idx:address:number <- get-address x:address:keyboard/deref, index:offset
-    buf:address:array:character <- get x:address:keyboard/deref, data:offset
-    max:number <- length buf:address:array:character/deref
+    break-unless x:address:console
+    idx:address:number <- get-address x:address:console/deref, index:offset
+    buf:address:array:event <- get x:address:console/deref, data:offset
     {
+      max:number <- length buf:address:array:event/deref
       done?:boolean <- greater-or-equal idx:address:number/deref, max:number
       break-unless done?:boolean
-      reply 0:literal/eof, 1:literal/found, x:address:keyboard/same-as-ingredient:0
+      dummy:address:event <- new event:type
+      reply dummy:address:event/deref, x:address:console/same-as-ingredient:0, 1:literal/found, 1:literal/quit
     }
-    c:character <- index buf:address:array:character/deref, idx:address:number/deref
+    result:event <- index buf:address:array:event/deref, idx:address:number/deref
     idx:address:number/deref <- add idx:address:number/deref, 1:literal
-    reply c:character, 1:literal/found, x:address:keyboard/same-as-ingredient:0
+    reply result:event, x:address:console/same-as-ingredient:0, 1:literal/found, 0:literal/quit
   }
-  # real keyboard input is infrequent; avoid polling it too much
+  # real event source is infrequent; avoid polling it too much
   switch
-  c:character, found?:boolean <- read-key-from-keyboard
-  reply c:character, found?:boolean, x:address:keyboard/same-as-ingredient:0
+  result:event, found?:boolean <- check-for-interaction
+  reply result:event, x:address:console/same-as-ingredient:0, found?:boolean, 0:literal/quit
 ]
 
-recipe wait-for-key [
+recipe read-key [
   default-space:address:array:location <- new location:type, 30:literal
-  x:address:keyboard <- next-ingredient
-  {
-    break-unless x:address:keyboard
-    # on fake keyboards 'wait-for-key' behaves just like 'read-key'
-    c:character, found?:boolean, x:address:keyboard <- read-key x:address:keyboard
-    reply c:character, x:address:keyboard/same-as-ingredient:0
-  }
-  c:character <- wait-for-key-from-keyboard
-  reply c:character, x:address:keyboard/same-as-ingredient:0
+#?   $print default-space:address:array:location #? 1
+#?   $exit #? 1
+#?   $start-tracing #? 1
+  console:address <- next-ingredient
+  x:event, console:address, found?:boolean, quit?:boolean <- read-event console:address
+#?   $print [aaa 1] #? 1
+  reply-if quit?:boolean, 0:literal, console:address/same-as-ingredient:0, found?:boolean, quit?:boolean
+#?   $print [aaa 2] #? 1
+  reply-unless found?:boolean, 0:literal, console:address/same-as-ingredient:0, found?:boolean, quit?:boolean
+#?   $print [aaa 3] #? 1
+  c:address:character <- maybe-convert x:event, text:variant
+  reply-unless c:address:character, 0:literal, console:address/same-as-ingredient:0, 0:literal/found, 0:literal/quit
+#?   $print [aaa 4] #? 1
+  reply c:address:character/deref, console:address/same-as-ingredient:0, 1:literal/found, 0:literal/quit
 ]
 
 recipe send-keys-to-channel [
   default-space:address:array:location <- new location:type, 30:literal
-  keyboard:address <- next-ingredient
+  console:address <- next-ingredient
   chan:address:channel <- next-ingredient
   screen:address <- next-ingredient
   {
-    c:character, found?:boolean, keyboard:address <- read-key keyboard:address
+    c:character, console:address, found?:boolean, quit?:boolean <- read-key console:address
     loop-unless found?:boolean
-#?     print-integer screen:address, c:character #? 1
+    break-if quit?:boolean
+    assert c:character, [invalid event, expected text]
     print-character screen:address, c:character
     chan:address:channel <- write chan:address:channel, c:character
-    # todo: eof
     loop
   }
 ]
diff --git a/079scenario_events.cc b/075scenario_console.cc
index 5641f885..6bc25715 100644
--- a/079scenario_events.cc
+++ b/075scenario_console.cc
@@ -1,68 +1,51 @@
-//: For testing both keyboard and mouse, use 'assume-events' rather than
-//: 'assume-keyboard'.
-//:
-//: This layer is tightly coupled with the definition of the 'event' type.
+//: Clean syntax to manipulate and check the console in scenarios.
+//: Instruction 'assume-console' implicitly creates a variable called
+//: 'console' that is accessible inside other 'run' instructions in the
+//: scenario. Like with the fake screen, 'assume-console' transparently
+//: supports unicode.
 
 :(scenarios run_mu_scenario)
-:(scenario events_in_scenario)
-scenario events-in-scenario [
-  assume-events [
+:(scenario keyboard_in_scenario)
+scenario keyboard-in-scenario [
+  assume-console [
     type [abc]
-    left-click 0, 1
-    type [d]
   ]
   run [
-    # 3 keyboard events; each event occupies 4 locations
-#?     $start-tracing
-    1:event <- read-event events:address
-    5:event <- read-event events:address
-    9:event <- read-event events:address
-    # mouse click
-    13:event <- read-event events:address
-    # final keyboard event
-    17:event <- read-event events:address
+    1:character, console:address, 2:boolean <- read-key console:address
+    3:character, console:address, 4:boolean <- read-key console:address
+    5:character, console:address, 6:boolean <- read-key console:address
+    7:character, console:address, 8:boolean, 9:boolean <- read-key console:address
   ]
   memory-should-contain [
-    1 <- 0  # type 'keyboard'
-    2 <- 97  # 'a'
-    3 <- 0  # unused
-    4 <- 0  # unused
-    5 <- 0  # type 'keyboard'
-    6 <- 98  # 'b'
-    7 <- 0  # unused
-    8 <- 0  # unused
-    9 <- 0  # type 'keyboard'
-    10 <- 99  # 'c'
-    11 <- 0  # unused
-    12 <- 0  # unused
-    13 <- 1  # type 'mouse'
-    14 <- 65513  # mouse click
-    15 <- 0  # row
-    16 <- 1  # column
-    17 <- 0  # type 'keyboard'
-    18 <- 100  # 'd'
-    19 <- 0  # unused
-    20 <- 0  # unused
-    21 <- 0
+    1 <- 97  # 'a'
+    2 <- 1
+    3 <- 98  # 'b'
+    4 <- 1
+    5 <- 99  # 'c'
+    6 <- 1
+    7 <- 0  # unset
+    8 <- 1
+    9 <- 1  # end of test events
   ]
 ]
 
-// 'events' is a special variable like 'keyboard' and 'screen'
 :(before "End Scenario Globals")
-const long long int EVENTS = Next_predefined_global_for_scenarios++;
+const long long int CONSOLE = Next_predefined_global_for_scenarios++;
 :(before "End Predefined Scenario Locals In Run")
-Name[tmp_recipe.at(0)]["events"] = EVENTS;
+Name[tmp_recipe.at(0)]["console"] = CONSOLE;
+
+//: allow naming just for 'console'
 :(before "End is_special_name Cases")
-if (s == "events") return true;
+if (s == "console") return true;
 
-//: Unlike assume-keyboard, assume-events is easiest to implement as just a
+//: Unlike assume-keyboard, assume-console is easiest to implement as just a
 //: primitive recipe.
 :(before "End Primitive Recipe Declarations")
-ASSUME_EVENTS,
+ASSUME_CONSOLE,
 :(before "End Primitive Recipe Numbers")
-Recipe_number["assume-events"] = ASSUME_EVENTS;
+Recipe_number["assume-console"] = ASSUME_CONSOLE;
 :(before "End Primitive Recipe Implementations")
-case ASSUME_EVENTS: {
+case ASSUME_CONSOLE: {
 //?   cerr << "aaa: " << current_instruction().ingredients.at(0).name << '\n'; //? 1
   // create a temporary recipe just for parsing; it won't contain valid instructions
   istringstream in("[" + current_instruction().ingredients.at(0).name + "]");
@@ -78,13 +61,18 @@ case ASSUME_EVENTS: {
   for (long long int i = 0; i < SIZE(r.steps); ++i) {
     const instruction& curr = r.steps.at(i);
     if (curr.name == "left-click") {
-      Memory[Current_routine->alloc] = /*tag for 'mouse-event' variant of 'event' exclusive-container*/1;
+      Memory[Current_routine->alloc] = /*tag for 'single-touch-event' variant of 'event' exclusive-container*/2;
       Memory[Current_routine->alloc+1+/*offset of 'type' in 'mouse-event'*/0] = TB_KEY_MOUSE_LEFT;
       Memory[Current_routine->alloc+1+/*offset of 'row' in 'mouse-event'*/1] = to_integer(curr.ingredients.at(0).name);
       Memory[Current_routine->alloc+1+/*offset of 'column' in 'mouse-event'*/2] = to_integer(curr.ingredients.at(1).name);
 //?       cerr << "AA left click: " << Memory[Current_routine->alloc+2] << ' ' << Memory[Current_routine->alloc+3] << '\n'; //? 1
       Current_routine->alloc += size_of_event();
     }
+    else if (curr.name == "press") {
+      Memory[Current_routine->alloc] = /*tag for 'keycode' variant of 'event' exclusive-container*/1;
+      Memory[Current_routine->alloc+1] = to_integer(curr.ingredients.at(0).name);
+      Current_routine->alloc += size_of_event();
+    }
     // End Event Handlers
     else {
       // keyboard input
@@ -93,12 +81,13 @@ case ASSUME_EVENTS: {
       const char* raw_contents = contents.c_str();
       long long int num_keyboard_events = unicode_length(contents);
       long long int curr = 0;
+//?       cerr << "AAA: " << num_keyboard_events << '\n'; //? 1
       for (long long int i = 0; i < num_keyboard_events; ++i) {
         Memory[Current_routine->alloc] = /*tag for 'keyboard-event' variant of 'event' exclusive-container*/0;
         uint32_t curr_character;
         assert(curr < SIZE(contents));
         tb_utf8_char_to_unicode(&curr_character, &raw_contents[curr]);
-//?         cerr << "AA keyboard: " << curr_character << '\n'; //? 1
+//?         cerr << "AA keyboard: " << curr_character << '\n'; //? 2
         Memory[Current_routine->alloc+/*skip exclusive container tag*/1] = curr_character;
         curr += tb_utf8_char_length(raw_contents[curr]);
         Current_routine->alloc += size_of_event();
@@ -108,9 +97,12 @@ case ASSUME_EVENTS: {
   assert(Current_routine->alloc == event_data_address+size);
   // wrap the array of events in an event object
   ensure_space(size_of_events());
-  Memory[EVENTS] = Current_routine->alloc;
+  Memory[CONSOLE] = Current_routine->alloc;
   Current_routine->alloc += size_of_events();
-  Memory[Memory[EVENTS]+/*offset of 'data' in container 'events'*/1] = event_data_address;
+//?   cerr << "writing " << event_data_address << " to location " << Memory[CONSOLE]+1 << '\n'; //? 1
+  Memory[Memory[CONSOLE]+/*offset of 'data' in container 'events'*/1] = event_data_address;
+//?   cerr << Memory[Memory[CONSOLE]+1] << '\n'; //? 1
+//?   cerr << "alloc now " << Current_routine->alloc << '\n'; //? 1
   break;
 }
 
@@ -143,52 +135,58 @@ long long int size_of_events() {
   static long long int result = 0;
   if (result) return result;
   vector<type_number> type;
-  type.push_back(Type_number["events"]);
+  assert(Type_number["console"]);
+  type.push_back(Type_number["console"]);
   result = size_of(type);
   return result;
 }
 
-//: Warn if a scenario uses both 'keyboard' and 'events'.
-
-:(scenario recipes_should_not_use_events_alongside_keyboard)
-% Hide_warnings = true;
-scenario recipes-should-not-use-events-alongside-keyboard [
-  assume-keyboard [abc]
-  assume-events []
-]
-+warn: can't use 'keyboard' and 'events' in the same program/scenario
-
-:(before "End Globals")
-bool Keyboard_used = false;
-bool Events_used = false;
-:(before "End Setup")
-Keyboard_used = Events_used = false;
-:(after "case ASSUME_EVENTS:")
-//? cerr << "events!\n"; //? 1
-Events_used = true;
-:(before "Running One Instruction")
-//? cerr << current_instruction().to_string() << '\n'; //? 1
-for (long long int i = 0; i < SIZE(current_instruction().ingredients); ++i) {
-  if (current_instruction().ingredients.at(i).value == KEYBOARD) Keyboard_used = true;
-  if (current_instruction().ingredients.at(i).value == EVENTS) Events_used = true;
-}
-for (long long int i = 0; i < SIZE(current_instruction().products); ++i) {
-  if (current_instruction().products.at(i).value == KEYBOARD) Keyboard_used = true;
-  if (current_instruction().products.at(i).value == EVENTS) Events_used = true;
-}
-:(before "End of Instruction")  // might miss some early returns like 'reply'
-//? cerr << Keyboard_used << Events_used << '\n'; //? 1
-if (Keyboard_used && Events_used)
-  raise << "can't use 'keyboard' and 'events' in the same program/scenario\n" << die();
-
-:(scenario recipes_should_not_use_events_alongside_keyboard_including_nested_run)
-% Hide_warnings = true;
-scenario recipes-should-not-use-events-alongside-keyboard-including-nested-run [
-  assume-events [
+:(scenario events_in_scenario)
+scenario events-in-scenario [
+  assume-console [
     type [abc]
+    left-click 0, 1
+    press 65515  # up arrow
+    type [d]
   ]
   run [
-    keyboard:location <- copy 0:literal  # unsafe
+    # 3 keyboard events; each event occupies 4 locations
+#?     $start-tracing #? 2
+    1:event <- read-event console:address
+    5:event <- read-event console:address
+    9:event <- read-event console:address
+    # mouse click
+    13:event <- read-event console:address
+    # non-character keycode
+    17:event <- read-event console:address
+    # final keyboard event
+    21:event <- read-event console:address
+  ]
+  memory-should-contain [
+    1 <- 0  # 'text'
+    2 <- 97  # 'a'
+    3 <- 0  # unused
+    4 <- 0  # unused
+    5 <- 0  # 'text'
+    6 <- 98  # 'b'
+    7 <- 0  # unused
+    8 <- 0  # unused
+    9 <- 0  # 'text'
+    10 <- 99  # 'c'
+    11 <- 0  # unused
+    12 <- 0  # unused
+    13 <- 2  # 'mouse'
+    14 <- 65513  # mouse click
+    15 <- 0  # row
+    16 <- 1  # column
+    17 <- 1  # 'keycode'
+    18 <- 65515  # up arrow
+    19 <- 0  # unused
+    20 <- 0  # unused
+    21 <- 0  # 'text'
+    22 <- 100  # 'd'
+    23 <- 0  # unused
+    24 <- 0  # unused
+    25 <- 0
   ]
 ]
-+warn: can't use 'keyboard' and 'events' in the same program/scenario
diff --git a/075scenario_keyboard.cc b/075scenario_keyboard.cc
deleted file mode 100644
index 6a5da304..00000000
--- a/075scenario_keyboard.cc
+++ /dev/null
@@ -1,84 +0,0 @@
-//: Clean syntax to manipulate and check the keyboard in scenarios.
-//: Instruction 'assume-keyboard' implicitly creates a variable called
-//: 'keyboard' that is accessible inside other 'run' instructions in the
-//: scenario. Like with the fake screen, 'assume-keyboard' transparently
-//: supports unicode.
-
-:(scenarios run_mu_scenario)
-:(scenario keyboard_in_scenario)
-scenario keyboard-in-scenario [
-  assume-keyboard [abc]
-  run [
-    1:character, 2:boolean, keyboard:address <- read-key keyboard:address
-    3:character, 4:boolean, keyboard:address <- read-key keyboard:address
-    5:character, 6:boolean, keyboard:address <- read-key keyboard:address
-    7:character, 8:boolean, keyboard:address <- read-key keyboard:address
-  ]
-  memory-should-contain [
-    1 <- 97  # 'a'
-    2 <- 1
-    3 <- 98  # 'b'
-    4 <- 1
-    5 <- 99  # 'c'
-    6 <- 1
-    7 <- 0  # eof
-    8 <- 1
-  ]
-]
-
-:(before "End Scenario Globals")
-const long long int KEYBOARD = Next_predefined_global_for_scenarios++;
-:(before "End Predefined Scenario Locals In Run")
-Name[tmp_recipe.at(0)]["keyboard"] = KEYBOARD;
-
-//: allow naming just for 'keyword'
-:(before "End is_special_name Cases")
-if (s == "keyboard") return true;
-
-:(before "End Rewrite Instruction(curr)")
-// rewrite `assume-keyboard string` to
-//   ```
-//   keyboard:address <- new string  # hacky reuse of location
-//   keyboard:address <- new-fake-keyboard keyboard:address
-//   ```
-if (curr.name == "assume-keyboard") {
-  // insert first instruction
-  curr.operation = Recipe_number["new"];
-  curr.name = "new";
-  assert(curr.products.empty());
-  curr.products.push_back(reagent("keyboard:address"));
-  curr.products.at(0).set_value(KEYBOARD);
-  result.steps.push_back(curr);  // hacky that "Rewrite Instruction" is converting to multiple instructions
-  // leave second instruction in curr
-  curr.clear();
-  curr.operation = Recipe_number["new-fake-keyboard"];
-  curr.name = "new-fake-keyboard";
-  assert(curr.ingredients.empty());
-  curr.ingredients.push_back(reagent("keyboard:address"));
-  curr.ingredients.at(0).set_value(KEYBOARD);
-  assert(curr.products.empty());
-  curr.products.push_back(reagent("keyboard:address"));
-  curr.products.at(0).set_value(KEYBOARD);
-}
-
-//: Since we don't yet have a clean way to represent characters like backspace
-//: in literal strings we can't easily pretend they were typed into the fake
-//: keyboard. So we'll use special unicode characters in the literal and then
-//: manually replace them with backspace.
-:(before "End Primitive Recipe Declarations")
-REPLACE_IN_KEYBOARD,
-:(before "End Primitive Recipe Numbers")
-Recipe_number["replace-in-keyboard"] = REPLACE_IN_KEYBOARD;
-:(before "End Primitive Recipe Implementations")
-case REPLACE_IN_KEYBOARD: {
-  long long int size = Memory[KEYBOARD];
-  assert(scalar(ingredients.at(0)));
-  assert(scalar(ingredients.at(1)));
-  for (long long int curr = KEYBOARD+1; curr <= KEYBOARD+size; ++curr) {
-    if (Memory[curr] == ingredients.at(0).at(0)) {
-//?       cerr << "replacing\n"; //? 1
-      Memory[curr] = ingredients.at(1).at(0);
-    }
-  }
-  break;
-}
diff --git a/076scenario_console_test.mu b/076scenario_console_test.mu
new file mode 100644
index 00000000..bfa72861
--- /dev/null
+++ b/076scenario_console_test.mu
@@ -0,0 +1,25 @@
+# To check our support for consoles in scenarios, rewrite tests from
+# scenario_console.mu
+# Tests for console interface.
+
+scenario read-key-in-mu [
+  assume-console [
+    type [abc]
+  ]
+  run [
+    1:character, console:address, 2:boolean <- read-key console:address
+    3:character, console:address, 4:boolean <- read-key console:address
+    5:character, console:address, 6:boolean <- read-key console:address
+    7:character, console:address, 8:boolean <- read-key console:address
+  ]
+  memory-should-contain [
+    1 <- 97  # 'a'
+    2 <- 1
+    3 <- 98  # 'b'
+    4 <- 1
+    5 <- 99  # 'c'
+    6 <- 1
+    7 <- 0  # eof
+    8 <- 1
+  ]
+]
diff --git a/076scenario_keyboard_test.mu b/076scenario_keyboard_test.mu
deleted file mode 100644
index 7e6ccee3..00000000
--- a/076scenario_keyboard_test.mu
+++ /dev/null
@@ -1,23 +0,0 @@
-# To check our support for keyboards in scenarios, rewrite tests from
-# scenario_keyboard.mu
-# Tests for keyboard interface.
-
-scenario read-key-in-mu [
-  assume-keyboard [abc]
-  run [
-    1:character, 2:boolean, keyboard:address <- read-key keyboard:address
-    3:character, 4:boolean, keyboard:address <- read-key keyboard:address
-    5:character, 6:boolean, keyboard:address <- read-key keyboard:address
-    7:character, 8:boolean, keyboard:address <- read-key keyboard:address
-  ]
-  memory-should-contain [
-    1 <- 97  # 'a'
-    2 <- 1
-    3 <- 98  # 'b'
-    4 <- 1
-    5 <- 99  # 'c'
-    6 <- 1
-    7 <- 0  # eof
-    8 <- 1
-  ]
-]
diff --git a/077mouse.cc b/077mouse.cc
deleted file mode 100644
index 124e8db2..00000000
--- a/077mouse.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-:(before "End Primitive Recipe Declarations")
-READ_KEYBOARD_OR_MOUSE_EVENT,
-:(before "End Primitive Recipe Numbers")
-Recipe_number["read-keyboard-or-mouse-event"] = READ_KEYBOARD_OR_MOUSE_EVENT;
-:(before "End Primitive Recipe Implementations")
-case READ_KEYBOARD_OR_MOUSE_EVENT: {
-  products.resize(2);  // result and status
-  tb_event event;
-  int event_type = tb_peek_event(&event, 5/*ms*/);
-  if (event_type == TB_EVENT_KEY) {
-    products.at(0).push_back(/*keyboard event*/0);
-    long long key = event.key ? event.key : event.ch;
-    if (key == TB_KEY_CTRL_C) tb_shutdown(), exit(1);
-    if (key == TB_KEY_BACKSPACE2) key = TB_KEY_BACKSPACE;
-    if (key == TB_KEY_CARRIAGE_RETURN) key = TB_KEY_NEWLINE;
-    products.at(0).push_back(key);
-    products.at(0).push_back(0);
-    products.at(0).push_back(0);
-    products.at(1).push_back(/*found*/true);
-    break;
-  }
-  if (event_type == TB_EVENT_MOUSE) {
-    products.at(0).push_back(/*mouse event*/1);
-//?     tb_shutdown(); //? 1
-//?     cerr << event_type << ' ' << event.key << ' ' << event.y << ' ' << event.x << '\n'; //? 1
-//?     exit(0); //? 1
-    products.at(0).push_back(event.key);  // which button, etc.
-    products.at(0).push_back(event.y);  // row
-    products.at(0).push_back(event.x);  // column
-    products.at(1).push_back(/*found*/true);
-    break;
-  }
-  products.at(0).push_back(0);
-  products.at(0).push_back(0);
-  products.at(0).push_back(0);
-  products.at(0).push_back(0);
-  products.at(1).push_back(/*found*/false);
-  break;
-}
diff --git a/078mouse.mu b/078mouse.mu
deleted file mode 100644
index c00dc9e9..00000000
--- a/078mouse.mu
+++ /dev/null
@@ -1,43 +0,0 @@
-exclusive-container event [
-  # update the ASSUME_EVENTS handler if you add more variants
-  keyboard:keyboard-event
-  mouse:mouse-event
-]
-
-container keyboard-event [
-  key:character
-]
-
-container mouse-event [
-  type:character
-  row:number
-  column:number
-]
-
-container events [
-  index:number
-  data:address:array:event  
-]
-
-recipe read-event [
-  default-space:address:array:location <- new location:type, 30:literal
-  x:address:events <- next-ingredient
-  {
-    break-unless x:address:events
-    idx:address:number <- get-address x:address:events/deref, index:offset
-    buf:address:array:event <- get x:address:events/deref, data:offset
-    {
-      max:number <- length buf:address:array:event/deref
-      done?:boolean <- greater-or-equal idx:address:number/deref, max:number
-      break-unless done?:boolean
-      dummy:address:event <- new event:type
-      reply dummy:address:event/deref, x:address:events/same-as-ingredient:0, 1:literal/found, 1:literal/quit
-    }
-    result:event <- index buf:address:array:event/deref, idx:address:number/deref
-    idx:address:number/deref <- add idx:address:number/deref, 1:literal
-    reply result:event, x:address:events/same-as-ingredient:0, 1:literal/found, 0:literal/quit
-  }
-  # real event source
-  result:event, found?:boolean <- read-keyboard-or-mouse-event
-  reply result:event, x:address:events/same-as-ingredient:0, found?:boolean, 0:literal/quit
-]
diff --git a/build_and_test_until b/build_and_test_until
index f9aac38e..9b817c9d 100755
--- a/build_and_test_until
+++ b/build_and_test_until
@@ -8,5 +8,5 @@ make tangle/tangle
 make enumerate/enumerate
 ./tangle/tangle $(./enumerate/enumerate --until $* |grep -v '.mu$') |grep -v "^\s*//:" > mu.cc
 cat /dev/null $(./enumerate/enumerate --until $* |grep '.mu$') > core.mu
-make valgrind
-#? make test #? 1
+#? make valgrind
+make test #? 1