mu/archive/1.vm/085scenario_console.cc

318 lines
11 KiB
C++
Raw Normal View History

//: 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.
2015-06-15 20:58:08 +00:00
2017-11-04 01:01:59 +00:00
//: first make sure we don't mangle this instruction in other transforms
2016-08-14 04:18:53 +00:00
:(before "End initialize_transform_rewrite_literal_string_to_text()")
recipes_taking_literal_strings.insert("assume-console");
5001 - drop the :(scenario) DSL I've been saying for a while[1][2][3] that adding extra abstractions makes things harder for newcomers, and adding new notations doubly so. And then I notice this DSL in my own backyard. Makes me feel like a hypocrite. [1] https://news.ycombinator.com/item?id=13565743#13570092 [2] https://lobste.rs/s/to8wpr/configuration_files_are_canary_warning [3] https://lobste.rs/s/mdmcdi/little_languages_by_jon_bentley_1986#c_3miuf2 The implementation of the DSL was also highly hacky: a) It was happening in the tangle/ tool, but was utterly unrelated to tangling layers. b) There were several persnickety constraints on the different kinds of lines and the specific order they were expected in. I kept finding bugs where the translator would silently do the wrong thing. Or the error messages sucked, and readers may be stuck looking at the generated code to figure out what happened. Fixing error messages would require a lot more code, which is one of my arguments against DSLs in the first place: they may be easy to implement, but they're hard to design to go with the grain of the underlying platform. They require lots of iteration. Is that effort worth prioritizing in this project? On the other hand, the DSL did make at least some readers' life easier, the ones who weren't immediately put off by having to learn a strange syntax. There were fewer quotes to parse, fewer backslash escapes. Anyway, since there are also people who dislike having to put up with strange syntaxes, we'll call that consideration a wash and tear this DSL out. --- This commit was sheer drudgery. Hopefully it won't need to be redone with a new DSL because I grow sick of backslashes.
2019-03-13 01:56:55 +00:00
:(code)
void test_keyboard_in_scenario() {
run_mu_scenario(
"scenario keyboard-in-scenario [\n"
" assume-console [\n"
" type [abc]\n"
" ]\n"
" run [\n"
" 1:char, 2:bool <- read-key console\n"
" 3:char, 4:bool <- read-key console\n"
" 5:char, 6:bool <- read-key console\n"
" 7:char, 8:bool, 9:bool <- read-key console\n"
" ]\n"
" memory-should-contain [\n"
" 1 <- 97\n" // 'a'
" 2 <- 1\n"
" 3 <- 98\n" // 'b'
" 4 <- 1\n"
" 5 <- 99\n" // 'c'
" 6 <- 1\n"
" 7 <- 0\n" // unset
" 8 <- 1\n"
" 9 <- 1\n" // end of test events
" ]\n"
"]\n"
);
}
2015-06-15 20:58:08 +00:00
:(before "End Scenario Globals")
extern const int CONSOLE = next_predefined_global_for_scenarios(/*size_of(address:console)*/2);
//: give 'console' a fixed location in scenarios
:(before "End Special Scenario Variable Names(r)")
Name[r]["console"] = CONSOLE;
//: make 'console' always a raw location in scenarios
:(before "End is_special_name Special-cases")
if (s == "console") return true;
2015-06-15 20:58:08 +00:00
:(before "End Primitive Recipe Declarations")
ASSUME_CONSOLE,
2015-06-15 20:58:08 +00:00
:(before "End Primitive Recipe Numbers")
put(Recipe_ordinal, "assume-console", ASSUME_CONSOLE);
2015-10-02 00:30:14 +00:00
:(before "End Primitive Recipe Checks")
case ASSUME_CONSOLE: {
break;
}
2015-06-15 20:58:08 +00:00
:(before "End Primitive Recipe Implementations")
case ASSUME_CONSOLE: {
2015-06-15 20:58:08 +00:00
// create a temporary recipe just for parsing; it won't contain valid instructions
istringstream in("[" + current_instruction().ingredients.at(0).name + "]");
recipe r;
slurp_body(in, r);
int num_events = count_events(r);
// initialize the events like in new-fake-console
int size = /*length*/1 + num_events*size_of_event();
int event_data_address = allocate(size);
// store length
put(Memory, event_data_address+/*skip alloc id*/1, num_events);
int curr_address = event_data_address + /*skip alloc id*/1 + /*skip length*/1;
2016-10-20 05:10:35 +00:00
for (int i = 0; i < SIZE(r.steps); ++i) {
2016-08-21 15:40:01 +00:00
const instruction& inst = r.steps.at(i);
if (inst.name == "left-click") {
trace(Callstack_depth+1, "mem") << "storing 'left-click' event starting at " << Current_routine->alloc << end();
put(Memory, curr_address, /*tag for 'touch-event' variant of 'event' exclusive-container*/2);
put(Memory, curr_address+/*skip tag*/1+/*offset of 'type' in 'mouse-event'*/0, TB_KEY_MOUSE_LEFT);
put(Memory, curr_address+/*skip tag*/1+/*offset of 'row' in 'mouse-event'*/1, to_integer(inst.ingredients.at(0).name));
put(Memory, curr_address+/*skip tag*/1+/*offset of 'column' in 'mouse-event'*/2, to_integer(inst.ingredients.at(1).name));
curr_address += size_of_event();
2015-06-15 20:58:08 +00:00
}
2016-08-21 15:40:01 +00:00
else if (inst.name == "press") {
trace(Callstack_depth+1, "mem") << "storing 'press' event starting at " << curr_address << end();
2016-08-21 15:40:01 +00:00
string key = inst.ingredients.at(0).name;
if (is_integer(key))
put(Memory, curr_address+1, to_integer(key));
else if (contains_key(Key, key))
put(Memory, curr_address+1, Key[key]);
else
raise << "assume-console: can't press '" << key << "'\n" << end();
if (get_or_insert(Memory, curr_address+1) < 256)
// these keys are in ascii
put(Memory, curr_address, /*tag for 'text' variant of 'event' exclusive-container*/0);
else {
// distinguish from unicode
put(Memory, curr_address, /*tag for 'keycode' variant of 'event' exclusive-container*/1);
}
curr_address += size_of_event();
}
2015-06-15 20:58:08 +00:00
// End Event Handlers
else {
// keyboard input
2016-08-21 15:40:01 +00:00
assert(inst.name == "type");
trace(Callstack_depth+1, "mem") << "storing 'type' event starting at " << curr_address << end();
2016-08-21 15:40:01 +00:00
const string& contents = inst.ingredients.at(0).name;
2015-06-15 20:58:08 +00:00
const char* raw_contents = contents.c_str();
int num_keyboard_events = unicode_length(contents);
int curr = 0;
2016-10-20 05:10:35 +00:00
for (int i = 0; i < num_keyboard_events; ++i) {
trace(Callstack_depth+1, "mem") << "storing 'text' tag at " << curr_address << end();
put(Memory, curr_address, /*tag for 'text' variant of 'event' exclusive-container*/0);
2015-06-15 20:58:08 +00:00
uint32_t curr_character;
assert(curr < SIZE(contents));
tb_utf8_char_to_unicode(&curr_character, &raw_contents[curr]);
trace(Callstack_depth+1, "mem") << "storing character " << curr_character << " at " << curr_address+/*skip exclusive container tag*/1 << end();
put(Memory, curr_address+/*skip exclusive container tag*/1, curr_character);
2015-06-15 20:58:08 +00:00
curr += tb_utf8_char_length(raw_contents[curr]);
curr_address += size_of_event();
2015-06-15 20:58:08 +00:00
}
}
}
assert(curr_address == event_data_address+/*skip alloc id*/1+size);
// wrap the array of events in a console object
int console_address = allocate(size_of_console());
trace(Callstack_depth+1, "mem") << "storing console in " << console_address << end();
put(Memory, CONSOLE+/*skip alloc id*/1, console_address);
trace(Callstack_depth+1, "mem") << "storing console data in " << console_address+/*offset of 'data' in container 'events'*/1 << end();
put(Memory, console_address+/*skip alloc id*/1+/*offset of 'data' in container 'events'*/1+/*skip alloc id of 'data'*/1, event_data_address);
2015-06-15 20:58:08 +00:00
break;
}
:(before "End Globals")
map<string, int> Key;
:(before "End One-time Setup")
initialize_key_names();
:(code)
void initialize_key_names() {
Key["F1"] = TB_KEY_F1;
Key["F2"] = TB_KEY_F2;
Key["F3"] = TB_KEY_F3;
Key["F4"] = TB_KEY_F4;
Key["F5"] = TB_KEY_F5;
Key["F6"] = TB_KEY_F6;
Key["F7"] = TB_KEY_F7;
Key["F8"] = TB_KEY_F8;
Key["F9"] = TB_KEY_F9;
Key["F10"] = TB_KEY_F10;
Key["F11"] = TB_KEY_F11;
Key["F12"] = TB_KEY_F12;
Key["insert"] = TB_KEY_INSERT;
Key["delete"] = TB_KEY_DELETE;
Key["home"] = TB_KEY_HOME;
Key["end"] = TB_KEY_END;
Key["page-up"] = TB_KEY_PGUP;
Key["page-down"] = TB_KEY_PGDN;
Key["up-arrow"] = TB_KEY_ARROW_UP;
Key["down-arrow"] = TB_KEY_ARROW_DOWN;
Key["left-arrow"] = TB_KEY_ARROW_LEFT;
Key["right-arrow"] = TB_KEY_ARROW_RIGHT;
Key["ctrl-a"] = TB_KEY_CTRL_A;
Key["ctrl-b"] = TB_KEY_CTRL_B;
Key["ctrl-c"] = TB_KEY_CTRL_C;
Key["ctrl-d"] = TB_KEY_CTRL_D;
Key["ctrl-e"] = TB_KEY_CTRL_E;
Key["ctrl-f"] = TB_KEY_CTRL_F;
Key["ctrl-g"] = TB_KEY_CTRL_G;
Key["backspace"] = TB_KEY_BACKSPACE;
Key["ctrl-h"] = TB_KEY_CTRL_H;
Key["tab"] = TB_KEY_TAB;
Key["ctrl-i"] = TB_KEY_CTRL_I;
Key["ctrl-j"] = TB_KEY_CTRL_J;
2015-08-30 00:57:13 +00:00
Key["enter"] = TB_KEY_NEWLINE; // ignore CR/LF distinction; there is only 'enter'
Key["ctrl-k"] = TB_KEY_CTRL_K;
Key["ctrl-l"] = TB_KEY_CTRL_L;
Key["ctrl-m"] = TB_KEY_CTRL_M;
Key["ctrl-n"] = TB_KEY_CTRL_N;
Key["ctrl-o"] = TB_KEY_CTRL_O;
Key["ctrl-p"] = TB_KEY_CTRL_P;
Key["ctrl-q"] = TB_KEY_CTRL_Q;
Key["ctrl-r"] = TB_KEY_CTRL_R;
Key["ctrl-s"] = TB_KEY_CTRL_S;
Key["ctrl-t"] = TB_KEY_CTRL_T;
Key["ctrl-u"] = TB_KEY_CTRL_U;
Key["ctrl-v"] = TB_KEY_CTRL_V;
Key["ctrl-w"] = TB_KEY_CTRL_W;
Key["ctrl-x"] = TB_KEY_CTRL_X;
Key["ctrl-y"] = TB_KEY_CTRL_Y;
Key["ctrl-z"] = TB_KEY_CTRL_Z;
Key["escape"] = TB_KEY_ESC;
Key["ctrl-slash"] = TB_KEY_CTRL_SLASH;
}
:(after "Begin check_or_set_invalid_types(r)")
if (is_scenario(caller))
initialize_special_name(r);
:(code)
bool is_scenario(const recipe& caller) {
return starts_with(caller.name, "scenario_");
}
void initialize_special_name(reagent& r) {
if (r.type) return;
// no need for screen
if (r.name == "console") r.type = new_type_tree("address:console");
// End Initialize Type Of Special Name In Scenario(r)
}
5001 - drop the :(scenario) DSL I've been saying for a while[1][2][3] that adding extra abstractions makes things harder for newcomers, and adding new notations doubly so. And then I notice this DSL in my own backyard. Makes me feel like a hypocrite. [1] https://news.ycombinator.com/item?id=13565743#13570092 [2] https://lobste.rs/s/to8wpr/configuration_files_are_canary_warning [3] https://lobste.rs/s/mdmcdi/little_languages_by_jon_bentley_1986#c_3miuf2 The implementation of the DSL was also highly hacky: a) It was happening in the tangle/ tool, but was utterly unrelated to tangling layers. b) There were several persnickety constraints on the different kinds of lines and the specific order they were expected in. I kept finding bugs where the translator would silently do the wrong thing. Or the error messages sucked, and readers may be stuck looking at the generated code to figure out what happened. Fixing error messages would require a lot more code, which is one of my arguments against DSLs in the first place: they may be easy to implement, but they're hard to design to go with the grain of the underlying platform. They require lots of iteration. Is that effort worth prioritizing in this project? On the other hand, the DSL did make at least some readers' life easier, the ones who weren't immediately put off by having to learn a strange syntax. There were fewer quotes to parse, fewer backslash escapes. Anyway, since there are also people who dislike having to put up with strange syntaxes, we'll call that consideration a wash and tear this DSL out. --- This commit was sheer drudgery. Hopefully it won't need to be redone with a new DSL because I grow sick of backslashes.
2019-03-13 01:56:55 +00:00
void test_events_in_scenario() {
run_mu_scenario(
"scenario events-in-scenario [\n"
" assume-console [\n"
" type [abc]\n"
" left-click 0, 1\n"
" press up-arrow\n"
" type [d]\n"
" ]\n"
" run [\n"
// 3 keyboard events; each event occupies 4 locations
" 1:event <- read-event console\n"
" 5:event <- read-event console\n"
" 9:event <- read-event console\n"
// mouse click
" 13:event <- read-event console\n"
// non-character keycode
" 17:event <- read-event console\n"
// final keyboard event
" 21:event <- read-event console\n"
" ]\n"
" memory-should-contain [\n"
" 1 <- 0\n" // 'text'
" 2 <- 97\n" // 'a'
" 3 <- 0\n" // unused
" 4 <- 0\n" // unused
" 5 <- 0\n" // 'text'
" 6 <- 98\n" // 'b'
" 7 <- 0\n" // unused
" 8 <- 0\n" // unused
" 9 <- 0\n" // 'text'
" 10 <- 99\n" // 'c'
" 11 <- 0\n" // unused
" 12 <- 0\n" // unused
" 13 <- 2\n" // 'mouse'
" 14 <- 65513\n" // mouse click
" 15 <- 0\n" // row
" 16 <- 1\n" // column
" 17 <- 1\n" // 'keycode'
" 18 <- 65517\n" // up arrow
" 19 <- 0\n" // unused
" 20 <- 0\n" // unused
" 21 <- 0\n" // 'text'
" 22 <- 100\n" // 'd'
" 23 <- 0\n" // unused
" 24 <- 0\n" // unused
" 25 <- 0\n"
" ]\n"
"]\n"
);
}
2015-06-23 18:51:17 +00:00
//: Deal with special keys and unmatched brackets by allowing each test to
//: independently choose the unicode symbol to denote them.
:(before "End Primitive Recipe Declarations")
REPLACE_IN_CONSOLE,
:(before "End Primitive Recipe Numbers")
put(Recipe_ordinal, "replace-in-console", REPLACE_IN_CONSOLE);
2015-10-02 00:30:14 +00:00
:(before "End Primitive Recipe Checks")
case REPLACE_IN_CONSOLE: {
break;
}
:(before "End Primitive Recipe Implementations")
case REPLACE_IN_CONSOLE: {
assert(scalar(ingredients.at(0)));
if (!get_or_insert(Memory, CONSOLE)) {
2016-02-26 21:04:55 +00:00
raise << "console not initialized\n" << end();
break;
}
int console_address = get_or_insert(Memory, CONSOLE);
int console_data = get_or_insert(Memory, console_address+1);
int length = get_or_insert(Memory, console_data); // array length
2016-10-20 05:10:35 +00:00
for (int i = 0, curr = console_data+1; i < length; ++i, curr+=size_of_event()) {
if (get_or_insert(Memory, curr) != /*text*/0) continue;
if (get_or_insert(Memory, curr+1) != ingredients.at(0).at(0)) continue;
2016-10-20 05:10:35 +00:00
for (int n = 0; n < size_of_event(); ++n)
put(Memory, curr+n, ingredients.at(1).at(n));
}
break;
}
2015-06-23 18:51:17 +00:00
:(code)
int count_events(const recipe& r) {
int result = 0;
2016-10-20 05:10:35 +00:00
for (int i = 0; i < SIZE(r.steps); ++i) {
2015-06-23 18:51:17 +00:00
const instruction& curr = r.steps.at(i);
if (curr.name == "type")
result += unicode_length(curr.ingredients.at(0).name);
else
++result;
2015-06-23 18:51:17 +00:00
}
return result;
}
int size_of_event() {
2015-06-23 18:51:17 +00:00
// memoize result if already computed
static int result = 0;
2015-06-23 18:51:17 +00:00
if (result) return result;
2016-07-02 05:34:50 +00:00
type_tree* type = new type_tree("event");
2015-06-23 18:51:17 +00:00
result = size_of(type);
delete type;
2015-06-23 18:51:17 +00:00
return result;
}
int size_of_console() {
2015-06-23 18:51:17 +00:00
// memoize result if already computed
static int result = 0;
2015-06-23 18:51:17 +00:00
if (result) return result;
assert(get(Type_ordinal, "console"));
2016-07-02 05:34:50 +00:00
type_tree* type = new type_tree("console");
result = size_of(type);
delete type;
2015-06-23 18:51:17 +00:00
return result;
}