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 these functions 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++;
 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 = /*space for refcount and length*/2 + num_events*size_of_event();
 62   int event_data_address = allocate(size);
 63   // store length
 64   put(Memory, event_data_address+/*skip refcount*/1, num_events);
 65   int curr_address = event_data_address + /*skip refcount and length*/2;
 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(9999, "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(9999, "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(9999, "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(9999, "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(9999, "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_address == event_data_address+size);
117   // wrap the array of events in a console object
118   int console_address = allocate(size_of_console());
119   trace(9999, "mem") << "storing console in " << console_address << end();
120   put(Memory, CONSOLE, console_address);
121   trace(9999, "mem") << "storing console data in " << console_address+/*skip refcount*/1+/*offset of 'data' in container 'events'*/1 << end();
122   put(Memory, console_address+/*skip refcount*/1+/*offset of 'data' in container 'events'*/1, event_data_address);
123   // increment refcount for event data
124   put(Memory, event_data_address, 1);
125   // increment refcount for console
126   put(Memory, console_address, 1);
127   break;
128 }
129 
130 :(before "End Globals")
131 map<string, int> Key;
132 :(before "End One-time Setup")
133 initialize_key_names();
134 :(code)
135 void initialize_key_names() {
136   Key["F1"] = TB_KEY_F1;
137   Key["F2"] = TB_KEY_F2;
138   Key["F3"] = TB_KEY_F3;
139   Key["F4"] = TB_KEY_F4;
140   Key["F5"] = TB_KEY_F5;
141   Key["F6"] = TB_KEY_F6;
142   Key["F7"] = TB_KEY_F7;
143   Key["F8"] = TB_KEY_F8;
144   Key["F9"] = TB_KEY_F9;
145   Key["F10"] = TB_KEY_F10;
146   Key["F11"] = TB_KEY_F11;
147   Key["F12"] = TB_KEY_F12;
148   Key["insert"] = TB_KEY_INSERT;
149   Key["delete"] = TB_KEY_DELETE;
150   Key["home"] = TB_KEY_HOME;
151   Key["end"] = TB_KEY_END;
152   Key["page-up"] = TB_KEY_PGUP;
153   Key["page-down"] = TB_KEY_PGDN;
154   Key["up-arrow"] = TB_KEY_ARROW_UP;
155   Key["down-arrow"] = TB_KEY_ARROW_DOWN;
156   Key["left-arrow"] = TB_KEY_ARROW_LEFT;
157   Key["right-arrow"] = TB_KEY_ARROW_RIGHT;
158   Key["ctrl-a"] = TB_KEY_CTRL_A;
159   Key["ctrl-b"] = TB_KEY_CTRL_B;
160   Key["ctrl-c"] = TB_KEY_CTRL_C;
161   Key["ctrl-d"] = TB_KEY_CTRL_D;
162   Key["ctrl-e"] = TB_KEY_CTRL_E;
163   Key["ctrl-f"] = TB_KEY_CTRL_F;
164   Key["ctrl-g"] = TB_KEY_CTRL_G;
165   Key["backspace"] = TB_KEY_BACKSPACE;
166   Key["ctrl-h"] = TB_KEY_CTRL_H;
167   Key["tab"] = TB_KEY_TAB;
168   Key["ctrl-i"] = TB_KEY_CTRL_I;
169   Key["ctrl-j"] = TB_KEY_CTRL_J;
170   Key["enter"] = TB_KEY_NEWLINE;  // ignore CR/LF distinction; there is only 'enter'
171   Key["ctrl-k"] = TB_KEY_CTRL_K;
172   Key["ctrl-l"] = TB_KEY_CTRL_L;
173   Key["ctrl-m"] = TB_KEY_CTRL_M;
174   Key["ctrl-n"] = TB_KEY_CTRL_N;
175   Key["ctrl-o"] = TB_KEY_CTRL_O;
176   Key["ctrl-p"] = TB_KEY_CTRL_P;
177   Key["ctrl-q"] = TB_KEY_CTRL_Q;
178   Key["ctrl-r"] = TB_KEY_CTRL_R;
179   Key["ctrl-s"] = TB_KEY_CTRL_S;
180   Key["ctrl-t"] = TB_KEY_CTRL_T;
181   Key["ctrl-u"] = TB_KEY_CTRL_U;
182   Key["ctrl-v"] = TB_KEY_CTRL_V;
183   Key["ctrl-w"] = TB_KEY_CTRL_W;
184   Key["ctrl-x"] = TB_KEY_CTRL_X;
185   Key["ctrl-y"] = TB_KEY_CTRL_Y;
186   Key["ctrl-z"] = TB_KEY_CTRL_Z;
187   Key["escape"] = TB_KEY_ESC;
188 }
189 
190 :(after "Begin check_or_set_invalid_types(r)")
191 if (is_scenario(caller))
192   initialize_special_name(r);
193 :(code)
194 bool is_scenario(const recipe& caller) {
195   return starts_with(caller.name, "scenario_");
196 }
197 void initialize_special_name(reagent& r) {
198   if (r.type) return;
199   // no need for screen
200   if (r.name == "console") r.type = new_type_tree("address:console");
201   // End Initialize Type Of Special Name In Scenario(r)
202 }
203 
204 :(scenario events_in_scenario)
205 scenario events-in-scenario [
206   assume-console [
207     type [abc]
208     left-click 0, 1
209     press up-arrow
210     type [d]
211   ]
212   run [
213     # 3 keyboard events; each event occupies 4 locations
214     1:event <- read-event console
215     5:event <- read-event console
216     9:event <- read-event console
217     # mouse click
218     13:event <- read-event console
219     # non-character keycode
220     17:event <- read-event console
221     # final keyboard event
222     21:event <- read-event console
223   ]
224   memory-should-contain [
225     1 <- 0  # 'text'
226     2 <- 97  # 'a'
227     3 <- 0  # unused
228     4 <- 0  # unused
229     5 <- 0  # 'text'
230     6 <- 98  # 'b'
231     7 <- 0  # unused
232     8 <- 0  # unused
233     9 <- 0  # 'text'
234     10 <- 99  # 'c'
235     11 <- 0  # unused
236     12 <- 0  # unused
237     13 <- 2  # 'mouse'
238     14 <- 65513  # mouse click
239     15 <- 0  # row
240     16 <- 1  # column
241     17 <- 1  # 'keycode'
242     18 <- 65517  # up arrow
243     19 <- 0  # unused
244     20 <- 0  # unused
245     21 <- 0  # 'text'
246     22 <- 100  # 'd'
247     23 <- 0  # unused
248     24 <- 0  # unused
249     25 <- 0
250   ]
251 ]
252 
253 //: Deal with special keys and unmatched brackets by allowing each test to
254 //: independently choose the unicode symbol to denote them.
255 :(before "End Primitive Recipe Declarations")
256 REPLACE_IN_CONSOLE,
257 :(before "End Primitive Recipe Numbers")
258 put(Recipe_ordinal, "replace-in-console", REPLACE_IN_CONSOLE);
259 :(before "End Primitive Recipe Checks")
260 case REPLACE_IN_CONSOLE: {
261   break;
262 }
263 :(before "End Primitive Recipe Implementations")
264 case REPLACE_IN_CONSOLE: {
265   assert(scalar(ingredients.at(0)));
266   if (!get_or_insert(Memory, CONSOLE)) {
267     raise << "console not initialized\n" << end();
268     break;
269   }
270   int console_address = get_or_insert(Memory, CONSOLE);
271   int console_data = get_or_insert(Memory, console_address+1);
272   int length = get_or_insert(Memory, console_data);  // array length
273   for (int i = 0, curr = console_data+1;  i < length;  ++i, curr+=size_of_event()) {
274     if (get_or_insert(Memory, curr) != /*text*/0) continue;
275     if (get_or_insert(Memory, curr+1) != ingredients.at(0).at(0)) continue;
276     for (int n = 0;  n < size_of_event();  ++n)
277       put(Memory, curr+n, ingredients.at(1).at(n));
278   }
279   break;
280 }
281 
282 :(code)
283 int count_events(const recipe& r) {
284   int result = 0;
285   for (int i = 0;  i < SIZE(r.steps);  ++i) {
286     const instruction& curr = r.steps.at(i);
287     if (curr.name == "type")
288       result += unicode_length(curr.ingredients.at(0).name);
289     else
290       ++result;
291   }
292   return result;
293 }
294 
295 int size_of_event() {
296   // memoize result if already computed
297   static int result = 0;
298   if (result) return result;
299   type_tree* type = new type_tree("event");
300   result = size_of(type);
301   delete type;
302   return result;
303 }
304 
305 int size_of_console() {
306   // memoize result if already computed
307   static int result = 0;
308   if (result) return result;
309   assert(get(Type_ordinal, "console"));
310   type_tree* type = new type_tree("console");
311   result = size_of(type)+/*refcount*/1;
312   delete type;
313   return result;
314 }