https://github.com/akkartik/mu/blob/master/085scenario_console.cc
  1 //: Clean syntax to manipulate and check the console in scenarios.
  2 //: Instruction 'assume-console' implicitly creates a variable called
  3 //: 'console' that is accessible inside other 'run' instructions in the
  4 //: scenario. Like with the fake screen, 'assume-console' transparently
  5 //: supports unicode.
  6 
  7 //: first make sure we don't mangle this instruction in other transforms
  8 :(before "End initialize_transform_rewrite_literal_string_to_text()")
  9 recipes_taking_literal_strings.insert("assume-console");
 10 
 11 :(scenarios run_mu_scenario)
 12 :(scenario keyboard_in_scenario)
 13 scenario keyboard-in-scenario [
 14   assume-console [
 15     type [abc]
 16   ]
 17   run [
 18     1:char, 2:bool <- read-key console
 19     3:char, 4:bool <- read-key console
 20     5:char, 6:bool <- read-key console
 21     7:char, 8:bool, 9:bool <- read-key console
 22   ]
 23   memory-should-contain [
 24     1 <- 97  # 'a'
 25     2 <- 1
 26     3 <- 98  # 'b'
 27     4 <- 1
 28     5 <- 99  # 'c'
 29     6 <- 1
 30     7 <- 0  # unset
 31     8 <- 1
 32     9 <- 1  # end of test events
 33   ]
 34 ]
 35 
 36 :(before "End Scenario Globals")
 37 extern const int CONSOLE = next_predefined_global_for_scenarios(/*size_of(address:console)*/2);
 38 //: give 'console' a fixed location in scenarios
 39 :(before "End Special Scenario Variable Names(r)")
 40 Name[r]["console"] = CONSOLE;
 41 //: make 'console' always a raw location in scenarios
 42 :(before "End is_special_name Special-cases")
 43 if (s == "console") return true;
 44 
 45 :(before "End Primitive Recipe Declarations")
 46 ASSUME_CONSOLE,
 47 :(before "End Primitive Recipe Numbers")
 48 put(Recipe_ordinal, "assume-console", ASSUME_CONSOLE);
 49 :(before "End Primitive Recipe Checks")
 50 case ASSUME_CONSOLE: {
 51   break;
 52 }
 53 :(before "End Primitive Recipe Implementations")
 54 case ASSUME_CONSOLE: {
 55   // create a temporary recipe just for parsing; it won't contain valid instructions
 56   istringstream in("[" + current_instruction().ingredients.at(0).name + "]");
 57   recipe r;
 58   slurp_body(in, r);
 59   int num_events = count_events(r);
 60   // initialize the events like in new-fake-console
 61   int size = /*length*/1 + num_events*size_of_event();
 62   int event_data_address = allocate(size);
 63   // store length
 64   put(Memory, event_data_address+/*skip alloc id*/1, num_events);
 65   int curr_address = event_data_address + /*skip alloc id*/1 + /*skip length*/1;
 66   for (int i = 0;  i < SIZE(r.steps);  ++i) {
 67     const instruction& inst = r.steps.at(i);
 68     if (inst.name == "left-click") {
 69       trace("mem") << "storing 'left-click' event starting at " << Current_routine->alloc << end();
 70       put(Memory, curr_address, /*tag for 'touch-event' variant of 'event' exclusive-container*/2);
 71       put(Memory, curr_address+/*skip tag*/1+/*offset of 'type' in 'mouse-event'*/0, TB_KEY_MOUSE_LEFT);
 72       put(Memory, curr_address+/*skip tag*/1+/*offset of 'row' in 'mouse-event'*/1, to_integer(inst.ingredients.at(0).name));
 73       put(Memory, curr_address+/*skip tag*/1+/*offset of 'column' in 'mouse-event'*/2, to_integer(inst.ingredients.at(1).name));
 74       curr_address += size_of_event();
 75     }
 76     else if (inst.name == "press") {
 77       trace("mem") << "storing 'press' event starting at " << curr_address << end();
 78       string key = inst.ingredients.at(0).name;
 79       if (is_integer(key))
 80         put(Memory, curr_address+1, to_integer(key));
 81       else if (contains_key(Key, key))
 82         put(Memory, curr_address+1, Key[key]);
 83       else
 84         raise << "assume-console: can't press '" << key << "'\n" << end();
 85       if (get_or_insert(Memory, curr_address+1) < 256)
 86         // these keys are in ascii
 87         put(Memory, curr_address, /*tag for 'text' variant of 'event' exclusive-container*/0);
 88       else {
 89         // distinguish from unicode
 90         put(Memory, curr_address, /*tag for 'keycode' variant of 'event' exclusive-container*/1);
 91       }
 92       curr_address += size_of_event();
 93     }
 94     // End Event Handlers
 95     else {
 96       // keyboard input
 97       assert(inst.name == "type");
 98       trace("mem") << "storing 'type' event starting at " << curr_address << end();
 99       const string& contents = inst.ingredients.at(0).name;
100       const char* raw_contents = contents.c_str();
101       int num_keyboard_events = unicode_length(contents);
102       int curr = 0;
103       for (int i = 0;  i < num_keyboard_events;  ++i) {
104         trace("mem") << "storing 'text' tag at " << curr_address << end();
105         put(Memory, curr_address, /*tag for 'text' variant of 'event' exclusive-container*/0);
106         uint32_t curr_character;
107         assert(curr < SIZE(contents));
108         tb_utf8_char_to_unicode(&curr_character, &raw_contents[curr]);
109         trace("mem") << "storing character " << curr_character << " at " << curr_address+/*skip exclusive container tag*/1 << end();
110         put(Memory, curr_address+/*skip exclusive container tag*/1, curr_character);
111         curr += tb_utf8_char_length(raw_contents[curr]);
112         curr_address += size_of_event();
113       }
114     }
115   }
116   assert(curr_
ct 118 int console_address = allocate(size_of_console()); 119 trace("mem") << "storing console in " << console_address << end(); 120 put(Memory, CONSOLE+/*skip alloc id*/1, console_address); 121 trace("mem") << "storing console data in " << console_address+/*offset of 'data' in container 'events'*/1 << end(); 122 put(Memory, console_address+/*skip alloc id*/1+/*offset of 'data' in container 'events'*/1+/*skip alloc id of 'data'*/1, event_data_address); 123 break; 124 } 125 126 :(before "End Globals") 127 map<string, int> Key; 128 :(before "End One-time Setup") 129 initialize_key_names(); 130 :(code) 131 void initialize_key_names() { 132 Key["F1"] = TB_KEY_F1; 133 Key["F2"] = TB_KEY_F2; 134 Key["F3"] = TB_KEY_F3; 135 Key["F4"] = TB_KEY_F4; 136 Key["F5"] = TB_KEY_F5; 137 Key["F6"] = TB_KEY_F6; 138 Key["F7"] = TB_KEY_F7; 139 Key["F8"] = TB_KEY_F8; 140 Key["F9"] = TB_KEY_F9; 141 Key["F10"] = TB_KEY_F10; 142 Key["F11"] = TB_KEY_F11; 143 Key["F12"] = TB_KEY_F12; 144 Key["insert"] = TB_KEY_INSERT; 145 Key["delete"] = TB_KEY_DELETE; 146 Key["home"] = TB_KEY_HOME; 147 Key["end"] = TB_KEY_END; 148 Key["page-up"] = TB_KEY_PGUP; 149 Key["page-down"] = TB_KEY_PGDN; 150 Key["up-arrow"] = TB_KEY_ARROW_UP; 151 Key["down-arrow"] = TB_KEY_ARROW_DOWN; 152 Key["left-arrow"] = TB_KEY_ARROW_LEFT; 153 Key["right-arrow"] = TB_KEY_ARROW_RIGHT; 154 Key["ctrl-a"] = TB_KEY_CTRL_A; 155 Key["ctrl-b"] = TB_KEY_CTRL_B; 156 Key["ctrl-c"] = TB_KEY_CTRL_C; 157 Key["ctrl-d"] = TB_KEY_CTRL_D; 158 Key["ctrl-e"] = TB_KEY_CTRL_E; 159 Key["ctrl-f"] = TB_KEY_CTRL_F; 160 Key["ctrl-g"] = TB_KEY_CTRL_G; 161 Key["backspace"] = TB_KEY_BACKSPACE; 162 Key["ctrl-h"] = TB_KEY_CTRL_H; 163 Key["tab"] = TB_KEY_TAB; 164 Key["ctrl-i"] = TB_KEY_CTRL_I; 165 Key["ctrl-j"] = TB_KEY_CTRL_J; 166 Key["enter"] = TB_KEY_NEWLINE; // ignore CR/LF distinction; there is only 'enter' 167 Key["ctrl-k"] = TB_KEY_CTRL_K; 168 Key["ctrl-l"] = TB_KEY_CTRL_L; 169 Key["ctrl-m"] = TB_KEY_CTRL_M; 170 Key["ctrl-n"] = TB_KEY_CTRL_N; 171 Key["ctrl-o"] = TB_KEY_CTRL_O; 172 Key["ctrl-p"] = TB_KEY_CTRL_P; 173 Key["ctrl-q"] = TB_KEY_CTRL_Q; 174 Key["ctrl-r"] = TB_KEY_CTRL_R; 175 Key["ctrl-s"] = TB_KEY_CTRL_S; 176 Key["ctrl-t"] = TB_KEY_CTRL_T; 177 Key["ctrl-u"] = TB_KEY_CTRL_U; 178 Key["ctrl-v"] = TB_KEY_CTRL_V; 179 Key["ctrl-w"] = TB_KEY_CTRL_W; 180 Key["ctrl-x"] = TB_KEY_CTRL_X; 181 Key["ctrl-y"] = TB_KEY_CTRL_Y; 182 Key["ctrl-z"] = TB_KEY_CTRL_Z; 183 Key["escape"] = TB_KEY_ESC; 184 Key["ctrl-slash"] = TB_KEY_CTRL_SLASH; 185 } 186 187 :(after "Begin check_or_set_invalid_types(r)") 188 if (is_scenario(caller)) 189 initialize_special_name(r); 190 :(code) 191 bool is_scenario(const recipe& caller) { 192 return starts_with(caller.name, "scenario_"); 193 } 194 void initialize_special_name(reagent& r) { 195 if (r.type) return; 196 // no need for screen 197 if (r.name == "console") r.type = new_type_tree("address:console"); 198 // End Initialize Type Of Special Name In Scenario(r) 199 } 200 201 :(scenario events_in_scenario) 202 scenario events-in-scenario [ 203 assume-console [ 204 type [abc] 205 left-click 0, 1 206 press up-arrow 207 type [d] 208 ] 209 run [ 210 # 3 keyboard events; each event occupies 4 locations 211 1:event <- read-event console 212 5:event <- read-event console 213 9:event <- read-event console 214 # mouse click 215 13:event <- read-event console 216 # non-character keycode 217 17:event <- read-event console 218 # final keyboard event 219 21:event <- read-event console 220 ] 221 memory-should-contain [ 222 1 <- 0 # 'text' 223 2 <- 97 # 'a' 224 3 <- 0 # unused 225 4 <- 0 # unused 226 5 <- 0 # 'text' 227 6 <- 98 # 'b' 228 7 <- 0 # unused 229 8 <- 0 # unused 230 9 <- 0 # 'text' 231 10 <- 99 # 'c' 232 11 <- 0 # unused 233 12 <- 0 # unused 234 13 <- 2 # 'mouse' 235 14 <- 65513 # mouse click 236 15 <- 0 # row 237 16 <- 1 # column 238 17 <- 1 # 'keycode' 239 18 <- 65517 # up arrow 240 19 <- 0 # unused 241 20 <- 0 # unused 242 21 <- 0 # 'text' 243 22 <- 100 # 'd' 244 23 <- 0 # unused 245 24 <- 0 # unused 246 25 <- 0 247 ] 248 ] 249 250 //: Deal with special keys and unmatched brackets by allowing each test to 251 //: independently choose the unicode symbol to denote them. 252 :(before "End Primitive Recipe Declarations") 253 REPLACE_IN_CONSOLE, 254 :(before "End Primitive Recipe Numbers") 255 put(Recipe_ordinal, "replace-in-console", REPLACE_IN_CONSOLE); 256 :(before "End Primitive Recipe Checks") 257 case REPLACE_IN_CONSOLE: { 258 break; 259 } 260 :(before "End Primitive Recipe Implementations") 261 case REPLACE_IN_CONSOLE: { 262 assert(scalar(ingredients.at(0))); 263 if (!get_or_insert(Memory, CONSOLE)) { 264 raise << "console not initialized\n" << end(); 265 break; 266 } 267 int console_address = get_or_insert(Memory, CONSOLE); 268 int console_data = get_or_insert(Memory, console_address+1); 269 int length = get_or_insert(Memory, console_data); // array length 270 for (int i = 0, curr = console_data+1; i < length; ++i, curr+=size_of_event()) { 271 if (get_or_insert(Memory, curr) != /*text*/0) continue; 272 if (get_or_insert(Memory, curr+1) != ingredients.at(0).at(0)) continue; 273 for (int n = 0; n < size_of_event(); ++n) 274 put(Memory, curr+n, ingredients.at(1).at(n)); 275 } 276 break; 277 } 278 279 :(code) 280 int count_events(const recipe& r) { 281 int result = 0; 282 for (int i = 0; i < SIZE(r.steps); ++i) { 283 const instruction& curr = r.steps.at(i); 284 if (curr.name == "type") 285 result += unicode_length(curr.ingredients.at(0).name); 286 else 287 ++result; 288 } 289 return result; 290 } 291 292 int size_of_event() { 293 // memoize result if already computed 294 static int result = 0; 295 if (result) return result; 296 type_tree* type = new type_tree("event"); 297 result = size_of(type); 298 delete type; 299 return result; 300 } 301 302 int size_of_console() { 303 // memoize result if already computed 304 static int result = 0; 305 if (result) return result; 306 assert(get(Type_ordinal, "console")); 307 type_tree* type = new type_tree("console"); 308 result = size_of(type); 309 delete type; 310 return result; 311 }