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_address == event_data_address+/*skip alloc id*/1+size);
    117   // wrap the array of events in a console object
    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 }