From cd9bb850caeca88747a25436fc65c67c6d5cd89a Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Sat, 27 Aug 2016 20:49:03 -0700 Subject: 3266 --- html/003trace.cc.html | 3 +- html/010vm.cc.html | 12 +-- html/011load.cc.html | 2 +- html/016dilated_reagent.cc.html | 23 ++--- html/020run.cc.html | 9 +- html/032array.cc.html | 2 +- html/034address.cc.html | 14 +-- html/036refcount.cc.html | 3 +- html/037abandon.cc.html | 4 +- html/038new_text.cc.html | 31 +++---- html/039location_array.cc.html | 2 +- html/042name.cc.html | 49 ++++++----- html/043space.cc.html | 19 ++-- html/044space_surround.cc.html | 6 +- html/046global.cc.html | 4 +- html/050scenario.cc.html | 10 +-- html/056shape_shifting_recipe.cc.html | 2 +- html/071recipe.cc.html | 50 ++++++++++- html/072scheduler.cc.html | 13 ++- html/073wait.cc.html | 144 +++++++++++++++++++++++++++---- html/075channel.mu.html | 8 +- html/080display.cc.html | 10 +-- html/082scenario_screen.cc.html | 60 +++++++++++-- html/085scenario_console.cc.html | 75 ++++++++-------- html/087file.cc.html | 54 ++++++++++-- html/088file.mu.html | 73 ++++++++++++++-- html/089scenario_filesystem.cc.html | 23 +++-- html/090scenario_filesystem_test.mu.html | 74 ++++++++++++++++ html/chessboard.mu.html | 30 +++---- html/filesystem.mu.html | 2 - 30 files changed, 601 insertions(+), 210 deletions(-) diff --git a/html/003trace.cc.html b/html/003trace.cc.html index 33c54ba1..9011e3eb 100644 --- a/html/003trace.cc.html +++ b/html/003trace.cc.html @@ -185,12 +185,13 @@ Hide_errors = false;NULL; +int Trace_errors = 0; // used only when Trace_stream is NULL // Top-level helper. IMPORTANT: can't nest #define trace(...) !Trace_stream ? cerr /*print nothing*/ : Trace_stream->stream(__VA_ARGS__) // Errors are a special layer. -#define raise (!Trace_stream ? (tb_shutdown(),cerr) /*do print*/ : Trace_stream->stream(Error_depth, "error")) +#define raise (!Trace_stream ? (tb_shutdown(),++Trace_errors,cerr) /*do print*/ : Trace_stream->stream(Error_depth, "error")) // Inside tests, fail any tests that displayed (unexpected) errors. // Expected errors in tests should always be hidden and silently checked for. :(before "End Test Teardown") diff --git a/html/010vm.cc.html b/html/010vm.cc.html index 508f7483..cc6b5360 100644 --- a/html/010vm.cc.html +++ b/html/010vm.cc.html @@ -684,13 +684,13 @@ ostream& operator<<(const string& in) { if (in.empty()) return ""; - int len = SIZE(in); - while (len > 1) { - if (in.at(len-1) != '0') break; - --len; + int length = SIZE(in); + while (length > 1) { + if (in.at(length-1) != '0') break; + --length; } - if (in.at(len-1) == '.') --len; - return in.substr(0, len); + if (in.at(length-1) == '.') --length; + return in.substr(0, length); } void test_trim_floating_point() { diff --git a/html/011load.cc.html b/html/011load.cc.html index b9194212..16f666e4 100644 --- a/html/011load.cc.html +++ b/html/011load.cc.html @@ -162,7 +162,7 @@ vector<recipe_ordinal> load(istream& in raise << "instruction prematurely ended with '<-'\n" << end(); return false; } - curr->old_name = curr->name = *p; p++; + curr->old_name = curr->name = *p; ++p; // curr->operation will be set in a later layer for (; p != words.end(); ++p) diff --git a/html/016dilated_reagent.cc.html b/html/016dilated_reagent.cc.html index d7de0ec6..67bf9462 100644 --- a/html/016dilated_reagent.cc.html +++ b/html/016dilated_reagent.cc.html @@ -89,32 +89,35 @@ $error: 0 // Assume the first letter is an open bracket, and read everything until the // matching close bracket. -// We balance {} () and []. And we skip one character after '\'. +// We balance {} () and []. string slurp_balanced_bracket(istream& in) { ostringstream result; char c; list<char> open_brackets; while (in >> c) { - if (c == '\\') { - // always silently skip the next character - result << c; - if (!(in >> c)) break; - result << c; - continue; - } if (c == '(') open_brackets.push_back(c); if (c == ')') { + if (open_brackets.empty() || open_brackets.back() != '(') { + raise << "unbalanced ')'\n" << end(); + continue; + } assert(open_brackets.back() == '('); open_brackets.pop_back(); } if (c == '[') open_brackets.push_back(c); if (c == ']') { - assert(open_brackets.back() == '['); + if (open_brackets.empty() || open_brackets.back() != '[') { + raise << "unbalanced ']'\n" << end(); + continue; + } open_brackets.pop_back(); } if (c == '{') open_brackets.push_back(c); if (c == '}') { - assert(open_brackets.back() == '{'); + if (open_brackets.empty() || open_brackets.back() != '{') { + raise << "unbalanced '}'\n" << end(); + continue; + } open_brackets.pop_back(); } result << c; diff --git a/html/020run.cc.html b/html/020run.cc.html index 61706723..31a8e9ad 100644 --- a/html/020run.cc.html +++ b/html/020run.cc.html @@ -186,8 +186,8 @@ load_file_or_directory(&qu //? START_TRACING_UNTIL_END_OF_SCOPE if (argc > 1) { // skip argv[0] - argv++; - argc--; + ++argv; + --argc; // ignore argv past '--'; that's commandline args for 'main' while (argc > 0) { if (string(*argv) == "--") break; @@ -200,6 +200,7 @@ load_file_or_directory(&qu transform_all(); //? DUMP(""); //? exit(0); +if (Trace_errors) return 1; save_snapshots(); //: Step 3: if we aren't running tests, locate a recipe called 'main' and @@ -213,7 +214,6 @@ save_snapshots(); trace(9990, "run") << "=== Starting to run" << end(); assert(Num_calls_to_transform_all == 1); run_main(argc, argv); - if (Trace_main) delete Trace_stream, Trace_stream = NULL; teardown(); } :(code) @@ -255,6 +255,7 @@ save_snapshots(); fout << Trace_stream->readable_contents(""); fout.close(); } + if (Trace_stream) delete Trace_stream, Trace_stream = NULL; } :(before "End One-time Setup") atexit(cleanup_main); @@ -267,7 +268,7 @@ atexit(cleanup_main);} ifstream fin(filename.c_str()); if (!fin) { - raise << "no such file '" << filename << "'\n" << end(); + cerr << "no such file '" << filename << "'\n" << end(); // don't raise, just warn. just in case it's just a name for a scenario to run. return; } trace(9990, "load") << "=== " << filename << end(); diff --git a/html/032array.cc.html b/html/032array.cc.html index 78c802e6..921247a6 100644 --- a/html/032array.cc.html +++ b/html/032array.cc.html @@ -85,7 +85,7 @@ put(Recipe_ordinal,// Update CREATE_ARRAY product in Run int base_address = product.value; int array_length = to_integer(product.type->right->right->name); - // initialize array size, so that size_of will work + // initialize array length, so that size_of will work trace(9999, "mem") << "storing " << array_length << " in location " << base_address << end(); put(Memory, base_address, array_length); // in array elements int size = size_of(product); // in locations diff --git a/html/034address.cc.html b/html/034address.cc.html index 12aa417c..d4ef5474 100644 --- a/html/034address.cc.html +++ b/html/034address.cc.html @@ -304,7 +304,7 @@ put(Recipe_ordinal,int size = ingredients.at(0).at(0); if (SIZE(ingredients) > 1) { // array allocation - trace(9999, "mem") << "array size is " << ingredients.at(1).at(0) << end(); + trace(9999, "mem") << "array length is " << ingredients.at(1).at(0) << end(); size = /*space for length*/1 + size*ingredients.at(1).at(0); } int result = allocate(size); @@ -320,10 +320,10 @@ put(Recipe_ordinal,:(code) int allocate(int size) { // include space for refcount - size++; + ++size; trace(9999, "mem") << "allocating size " << size << end(); //? Total_alloc += size; -//? Num_alloc++; +//? ++Num_alloc; // Allocate Special-cases // compute the region of memory to return // really crappy at the moment @@ -385,8 +385,8 @@ def main [ 3:number/raw <- subtract 2:address:number/raw, 1:address:array:number/raw ] +run: {1: ("address" "array" "number"), "raw": ()} <- new {number: "type"}, {5: "literal"} -+mem: array size is 5 -# don't forget the extra location for array size, and the second extra location for the refcount ++mem: array length is 5 +# don't forget the extra location for array length, and the second extra location for the refcount +mem: storing 7 in location 3 :(scenario new_empty_array) @@ -396,8 +396,8 @@ def main [ 3:number/raw <- subtract 2:address:number/raw, 1:address:array:number/raw ] +run: {1: ("address" "array" "number"), "raw": ()} <- new {number: "type"}, {0: "literal"} -+mem: array size is 0 -# one location for array size, and one for the refcount ++mem: array length is 0 +# one location for array length, and one for the refcount +mem: storing 2 in location 3 //: If a routine runs out of its initial allocation, it should allocate more. diff --git a/html/036refcount.cc.html b/html/036refcount.cc.html index c1f923ae..6613fdb8 100644 --- a/html/036refcount.cc.html +++ b/html/036refcount.cc.html @@ -428,12 +428,13 @@ Transform.push_back(:(before "End Decrement Refcounts(canonized_x)") if (is_mu_container(canonized_x) || is_mu_exclusive_container(canonized_x)) { - trace(9999, "mem") << "need to read old value to figure out what refcounts to decrement" << end(); + trace(9999, "mem") << "need to read old value of '" << to_string(canonized_x) << "' to figure out what refcounts to decrement" << end(); // read from canonized_x but without canonizing again // todo: inline without running canonize all over again reagent/*copy*/ tmp = canonized_x; tmp.properties.push_back(pair<string, string_tree*>("raw", NULL)); vector<double> data = read_memory(tmp); + trace(9999, "mem") << "done reading old value of '" << to_string(canonized_x) << "'" << end(); const container_metadata& metadata = get(Container_metadata, canonized_x.type); for (map<set<tag_condition_info>, set<address_element_info> >::const_iterator p = metadata.address.begin(); p != metadata.address.end(); ++p) { if (!all_match(data, p->first)) continue; diff --git a/html/037abandon.cc.html b/html/037abandon.cc.html index 1339c4f0..89ea879c 100644 --- a/html/037abandon.cc.html +++ b/html/037abandon.cc.html @@ -63,7 +63,7 @@ map<int, void abandon(int address, const type_tree* payload_type, int payload_size) { trace(9999, "abandon") << "updating refcounts inside " << address << ": " << to_string(payload_type) << end(); //? Total_free += size; -//? Num_free++; +//? ++Num_free; //? cerr << "abandon: " << size << '\n'; // decrement any contained refcounts if (payload_type->name == "array") { @@ -73,7 +73,7 @@ map<int, (element.type->name != "array"); int element_size = size_of(element); for (int i = 0; i < array_length; ++i) { - element.set_value(address + /*skip refcount*/1 + /*skip array length*/1 + i*element_size); + element.set_value(address + /*skip refcount and length*/2 + i*element_size); decrement_any_refcounts(element); } } diff --git a/html/038new_text.cc.html b/html/038new_text.cc.html index 35d3a2c9..f9565ba4 100644 --- a/html/038new_text.cc.html +++ b/html/038new_text.cc.html @@ -69,23 +69,24 @@ def main [ // allocate an array just large enough for it int string_length = unicode_length(contents); //? Total_alloc += string_length+1; -//? Num_alloc++; - ensure_space(string_length+1); // don't forget the extra location for array size - // initialize string - int result = Current_routine->alloc; - // initialize refcount - put(Memory, Current_routine->alloc++, 0); - // store length - put(Memory, Current_routine->alloc++, string_length); +//? ++Num_alloc; + int result = allocate(string_length+/*array length*/1); + trace(9999, "mem") << "storing string refcount 0 in location " << result << end(); + put(Memory, result, 0); + int curr_address = result+/*skip refcount*/1; + trace(9999, "mem") << "storing string length " << string_length << " in location " << curr_address << end(); + put(Memory, curr_address, string_length); + ++curr_address; // skip length int curr = 0; const char* raw_contents = contents.c_str(); for (int i = 0; i < string_length; ++i) { uint32_t curr_character; assert(curr < SIZE(contents)); tb_utf8_char_to_unicode(&curr_character, &raw_contents[curr]); - put(Memory, Current_routine->alloc, curr_character); + trace(9999, "mem") << "storing string character " << curr_character << " in location " << curr_address << end(); + put(Memory, curr_address, curr_character); curr += tb_utf8_char_length(raw_contents[curr]); - ++Current_routine->alloc; + ++curr_address; } // mu strings are not null-terminated in memory return result; @@ -133,13 +134,13 @@ def main [ //: Allocate more to routine when initializing a literal string :(scenario new_string_overflow) -% Initial_memory_per_routine = 2; +% Initial_memory_per_routine = 3; def main [ 1:address:number/raw <- new number:type - 2:address:array:character/raw <- new [a] # not enough room in initial page, if you take the array size into account + 2:address:array:character/raw <- new [a] # not enough room in initial page, if you take the refcount and array length into account ] -+new: routine allocated memory from 1000 to 1002 -+new: routine allocated memory from 1002 to 1004 ++new: routine allocated memory from 1000 to 1003 ++new: routine allocated memory from 1003 to 1006 //: helpers :(code) @@ -157,7 +158,7 @@ def main [ string read_mu_string(int address) { if (address == 0) return ""; - address++; // skip refcount + ++address; // skip refcount int size = get_or_insert(Memory, address); if (size == 0) return ""; ostringstream tmp; diff --git a/html/039location_array.cc.html b/html/039location_array.cc.html index 2b2e23cf..cb1e0e08 100644 --- a/html/039location_array.cc.html +++ b/html/039location_array.cc.html @@ -56,7 +56,7 @@ put(Recipe_ordinal,:(before "End Primitive Recipe Implementations") case TO_LOCATION_ARRAY: { int array_size = SIZE(ingredients.at(0)); - int allocation_size = array_size + /*refcount*/1 + /*length*/1; + int allocation_size = array_size + /*refcount and length*/2; ensure_space(allocation_size); const int result = Current_routine->alloc; products.resize(1); diff --git a/html/042name.cc.html b/html/042name.cc.html index adb432be..e52ba220 100644 --- a/html/042name.cc.html +++ b/html/042name.cc.html @@ -86,41 +86,45 @@ Name = Name_snapshot; // End transform_names(inst) Special-cases // map names to addresses for (int in = 0; in < SIZE(inst.ingredients); ++in) { - if (is_disqualified(inst.ingredients.at(in), inst, caller.name)) continue; - if (is_numeric_location(inst.ingredients.at(in))) numeric_locations_used = true; - if (is_named_location(inst.ingredients.at(in))) names_used = true; - if (is_integer(inst.ingredients.at(in).name)) continue; - if (!already_transformed(inst.ingredients.at(in), names)) { - raise << maybe(caller.name) << "use before set: '" << inst.ingredients.at(in).name << "'\n" << end(); + reagent& ingredient = inst.ingredients.at(in); + // Begin transform_names Ingredient Special-cases(ingredient, inst, caller) + if (is_disqualified(ingredient, inst, caller.name)) continue; + if (is_numeric_location(ingredient)) numeric_locations_used = true; + if (is_named_location(ingredient)) names_used = true; + if (is_integer(ingredient.name)) continue; + if (!already_transformed(ingredient, names)) { + raise << maybe(caller.name) << "use before set: '" << ingredient.name << "'\n" << end(); return; } - int v = lookup_name(inst.ingredients.at(in), r); + int v = lookup_name(ingredient, r); if (v >= 0) { - inst.ingredients.at(in).set_value(v); - // Done Placing Ingredient(inst, in, caller) + ingredient.set_value(v); + // Done Placing Ingredient(ingredient, inst, caller) } else { - raise << maybe(caller.name) << "can't find a place to store '" << inst.ingredients.at(in).name << "'\n" << end(); + raise << maybe(caller.name) << "can't find a place to store '" << ingredient.name << "'\n" << end(); return; } } for (int out = 0; out < SIZE(inst.products); ++out) { - if (is_disqualified(inst.products.at(out), inst, caller.name)) continue; - if (is_numeric_location(inst.products.at(out))) numeric_locations_used = true; - if (is_named_location(inst.products.at(out))) names_used = true; - if (is_integer(inst.products.at(out).name)) continue; - if (names.find(inst.products.at(out).name) == names.end()) { - trace(9993, "name") << "assign " << inst.products.at(out).name << " " << curr_idx << end(); - names[inst.products.at(out).name] = curr_idx; - curr_idx += size_of(inst.products.at(out)); + reagent& product = inst.products.at(out); + // Begin transform_names Product Special-cases(product, inst, caller) + if (is_disqualified(product, inst, caller.name)) continue; + if (is_numeric_location(product)) numeric_locations_used = true; + if (is_named_location(product)) names_used = true; + if (is_integer(product.name)) continue; + if (names.find(product.name) == names.end()) { + trace(9993, "name") << "assign " << product.name << " " << curr_idx << end(); + names[product.name] = curr_idx; + curr_idx += size_of(product); } - int v = lookup_name(inst.products.at(out), r); + int v = lookup_name(product, r); if (v >= 0) { - inst.products.at(out).set_value(v); - // Done Placing Product(inst, out, caller) + product.set_value(v); + // Done Placing Product(product, inst, caller) } else { - raise << maybe(caller.name) << "can't find a place to store '" << inst.products.at(out).name << "'\n" << end(); + raise << maybe(caller.name) << "can't find a place to store '" << product.name << "'\n" << end(); return; } } @@ -131,7 +135,6 @@ Name = Name_snapshot; bool is_disqualified(/*mutable*/ reagent& x, const instruction& inst, const string& recipe_name) { if (!x.type) { - // End Null-type is_disqualified Exceptions raise << maybe(recipe_name) << "missing type for '" << x.original_string << "' in '" << inst.original_string << "'\n" << end(); return true; } diff --git a/html/043space.cc.html b/html/043space.cc.html index e04b8cfc..0ecfbcc1 100644 --- a/html/043space.cc.html +++ b/html/043space.cc.html @@ -198,7 +198,7 @@ def main [ y:number <- copy 3 ] # allocate space for x and y, as well as the chaining slot at 0 -+mem: array size is 3 ++mem: array length is 3 :(before "End is_disqualified Cases") if (x.name == "number-of-locals") @@ -275,14 +275,15 @@ try_reclaim_locals(); for (int i = /*leave default space for last*/1; i < SIZE(exiting_recipe.steps); ++i) { const instruction& inst = exiting_recipe.steps.at(i); for (int i = 0; i < SIZE(inst.products); ++i) { + const reagent& product = inst.products.at(i); // local variables only - if (has_property(inst.products.at(i), "lookup")) continue; - if (has_property(inst.products.at(i), "raw")) continue; // tests often want to check such locations after they run - if (escaping(inst.products.at(i))) continue; + if (has_property(product, "lookup")) continue; + if (has_property(product, "raw")) continue; // tests often want to check such locations after they run + if (escaping(product)) continue; // End Checks For Reclaiming Locals - trace(9999, "mem") << "clearing " << inst.products.at(i).original_string << end(); - zeros.resize(size_of(inst.products.at(i))); - write_memory(inst.products.at(i), zeros); + trace(9999, "mem") << "clearing " << product.original_string << end(); + zeros.resize(size_of(product)); + write_memory(product, zeros); } } trace(9999, "mem") << "automatically abandoning " << current_call().default_space << end(); @@ -300,8 +301,8 @@ try_reclaim_locals(); if (current_step_index() >= SIZE(Current_routine->steps())) return false; for (long long i = 0; i < SIZE(current_instruction().ingredients); ++i) { if (r == current_instruction().ingredients.at(i)) { - if (caller_uses_product(i)) - return true; + if (caller_uses_product(i)) + return true; } } return false; diff --git a/html/044space_surround.cc.html b/html/044space_surround.cc.html index 3c564c0b..66db51b3 100644 --- a/html/044space_surround.cc.html +++ b/html/044space_surround.cc.html @@ -55,11 +55,11 @@ def main [ ] def dummy [ # just for the /names: property above ] -# chain space: 10 + /*skip refcount*/1 + /*skip length*/1 +# chain space: 10 + (refcount and length) 2 +mem: storing 20 in location 12 -# store to default space: 10 + /*skip refcount*/1 + /*skip length*/1 + /*index*/1 +# store to default space: 10 + (skip refcount and length) 2 + (index) 1 +mem: storing 32 in location 13 -# store to chained space: /*contents of location 12*/20 + /*skip refcount*/1 + /*skip length*/1 + /*index*/1 +# store to chained space: (contents of location 12) 20 + (refcount and length) 2 + (index) 1 +mem: storing 33 in location 23 :(before "End Checks For Reclaiming Locals") diff --git a/html/046global.cc.html b/html/046global.cc.html index c84b909b..1d68aa01 100644 --- a/html/046global.cc.html +++ b/html/046global.cc.html @@ -57,9 +57,9 @@ def main [ 1:number <- copy 23 1:number/space:global <- copy 24 ] -# store to default space: 10 + /*skip refcount*/1 + /*skip length*/1 + /*index*/1 +# store to default space: 10 + (skip refcount and length) 2 + (index) 1 +mem: storing 23 in location 13 -# store to chained space: /*contents of location 12*/20 + /*skip refcount*/1 + /*skip length*/1 + /*index*/1 +# store to chained space: (contents of location 12) 20 + (refcount and length) 2 + (index) 1 +mem: storing 24 in location 23 //: to support it, create another special variable called global space diff --git a/html/050scenario.cc.html b/html/050scenario.cc.html index a660cefb..1ec3e9b2 100644 --- a/html/050scenario.cc.html +++ b/html/050scenario.cc.html @@ -213,9 +213,9 @@ Hide_missing_default_space_errors = false(tmp.front()); if (!Hide_errors && trace_count("error") > 0) Passed = false; + // End Mu Test Teardown if (!Passed) ++Num_failures; - // End Mu Test Teardown if (not_already_inside_test && Trace_stream) { teardown(); if (Save_trace) { @@ -236,10 +236,10 @@ Hide_missing_default_space_errors = false// Special Scenario Variable Names(r) // End Special Scenario Variable Names(r) } -:(before "Done Placing Ingredient(inst, in, caller)") -maybe_make_raw(inst.ingredients.at(in), caller); -:(before "Done Placing Product(inst, out, caller)") -maybe_make_raw(inst.products.at(out), caller); +:(before "Done Placing Ingredient(ingredient, inst, caller)") +maybe_make_raw(ingredient, caller); +:(before "Done Placing Product(product, inst, caller)") +maybe_make_raw(product, caller); :(code) void maybe_make_raw(reagent& r, const recipe& caller) { if (!is_special_name(r.name)) return; diff --git a/html/056shape_shifting_recipe.cc.html b/html/056shape_shifting_recipe.cc.html index 98c0d5a3..2302862c 100644 --- a/html/056shape_shifting_recipe.cc.html +++ b/html/056shape_shifting_recipe.cc.html @@ -231,7 +231,7 @@ recipe_ordinal best_shape_shifting_variant(if (!type) return 0; int result = 0; if (!type->name.empty() && !is_type_ingredient_name(type->name)) - result++; + ++result; result += number_of_concrete_type_names(type->left); result += number_of_concrete_type_names(type->right); return result; diff --git a/html/071recipe.cc.html b/html/071recipe.cc.html index 13241cf5..0ae26bfe 100644 --- a/html/071recipe.cc.html +++ b/html/071recipe.cc.html @@ -68,13 +68,59 @@ put(Type_ordinal, type_ordinal recipe = put(Type_ordinal, "recipe", Next_type_ordinal++); get_or_insert(Type, recipe).name = "recipe"; -:(before "End Null-type is_disqualified Exceptions") -if (!x.type && contains_key(Recipe_ordinal, x.name)) { +:(after "Begin transform_names Ingredient Special-cases(ingredient, inst, caller)") +if (is_recipe_literal(ingredient, caller)) { + initialize_recipe_literal(ingredient); + continue; +} +:(after "Begin transform_names Product Special-cases(product, inst, caller)") +if (is_recipe_literal(product, caller)) { + initialize_recipe_literal(product); + continue; +} +:(code) +bool is_recipe_literal(const reagent& x, const recipe& caller) { + if (x.type) return false; + if (!contains_key(Recipe_ordinal, x.name)) return false; + if (contains_reagent_with_type(caller, x.name)) { + raise << maybe(caller.name) << "you can't use '" << x.name << "' as a recipe literal when it's also a variable\n" << end(); + return false; + } + return true; +} +void initialize_recipe_literal(reagent& x) { x.type = new type_tree("recipe-literal"); x.set_value(get(Recipe_ordinal, x.name)); +} +bool contains_reagent_with_type(const recipe& caller, const string& name) { + for (int i = 0; i < SIZE(caller.steps); ++i) { + const instruction& inst = caller.steps.at(i); + for (int i = 0; i < SIZE(inst.ingredients); ++i) + if (is_matching_non_recipe_literal(inst.ingredients.at(i), name)) return true; + for (int i = 0; i < SIZE(inst.products); ++i) + if (is_matching_non_recipe_literal(inst.products.at(i), name)) return true; + } + return false; +} +bool is_matching_non_recipe_literal(const reagent& x, const string& name) { + if (x.name != name) return false; + if (!x.type) return false; + if (x.type->value == get(Type_ordinal, "recipe-literal")) return false; return true; } +//: It's confusing to use variable names that are also recipe names. Always +//: assume variable types override recipe literals. +:(scenario error_on_recipe_literal_used_as_a_variable) +% Hide_errors = true; +def main [ + local-scope + a:boolean <- equal break 0 + break:boolean <- copy 0 +] ++error: main: you can't use 'break' as a recipe literal when it's also a variable ++error: main: missing type for 'break' in 'a:boolean <- equal break, 0' + :(before "End Primitive Recipe Declarations") CALL, :(before "End Primitive Recipe Numbers") diff --git a/html/072scheduler.cc.html b/html/072scheduler.cc.html index 55f7ba88..78ac7437 100644 --- a/html/072scheduler.cc.html +++ b/html/072scheduler.cc.html @@ -21,8 +21,9 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color .Comment { color: #9090ff; } .Delimiter { color: #800080; } .Special { color: #c00000; } -.Identifier { color: #fcb165; } +.CommentedCode { color: #6c6c6c; } .Normal { color: #eeeeee; background-color: #080808; padding-bottom: 1px; } +.Identifier { color: #fcb165; } --> @@ -95,6 +96,7 @@ Routines.clear(); assert(Current_routine); assert(Current_routine->state == RUNNING); trace(9990, "schedule") << current_routine_label() << end(); +//? cerr << "schedule: " << current_routine_label() << '\n'; run_current_routine(Scheduling_interval); // Scheduler State Transitions if (Current_routine->completed()) @@ -104,6 +106,7 @@ Routines.clear(); // Scheduler Cleanup // End Scheduler Cleanup } + // End Run Routine } bool all_routines_done() { @@ -128,8 +131,12 @@ Routines.clear(); } string current_routine_label() { + return routine_label(Current_routine); +} + +string routine_label(routine* r) { ostringstream result; - const call_stack& calls = Current_routine->calls; + const call_stack& calls = r->calls; for (call_stack::const_iterator p = calls.begin(); p != calls.end(); ++p) { if (p != calls.begin()) result << '/'; result << get(Recipe, p->running_recipe).name; @@ -172,7 +179,7 @@ Current_routine = NULL;1; :(before "End routine Constructor") id = Next_routine_id; -Next_routine_id++; +++Next_routine_id; //: routines save the routine that spawned them :(before "End routine Fields") diff --git a/html/073wait.cc.html b/html/073wait.cc.html index 49bdd7d3..847eafa3 100644 --- a/html/073wait.cc.html +++ b/html/073wait.cc.html @@ -21,6 +21,7 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color .Special { color: #c00000; } .Identifier { color: #fcb165; } .Normal { color: #eeeeee; background-color: #080808; padding-bottom: 1px; } +.CommentedCode { color: #6c6c6c; } --> @@ -68,6 +69,11 @@ waiting_on_location = old_value_of_waiting_location = 0false; raise << Current_scenario->name << ": deadlock!\n" << end(); } +:(before "End Run Routine") +if (any_routines_waiting()) { + raise << "deadlock!\n" << end(); + dump_waiting_routines(); +} :(before "End Test Teardown") if (Passed && any_routines_with_error()) { Passed = false; @@ -81,6 +87,12 @@ waiting_on_location = old_value_of_waiting_location = 0} return false; } +void dump_waiting_routines() { + for (int i = 0; i < SIZE(Routines); ++i) { + if (Routines.at(i)->state == WAITING) + cerr << i << ": " << routine_label(Routines.at(i)) << '\n'; + } +} //: primitive recipe to put routines in that state @@ -258,26 +270,112 @@ def main [ ] +mem: storing 11 in location 21 -//: also allow waiting on a routine to stop running +//: also allow waiting on a routine to block +//: (just for tests; use wait_for_routine below wherever possible) + +:(scenario wait_for_routine_to_block) +def f1 [ + 1:number/routine <- start-running f2 + wait-for-routine-to-block 1:number/routine + # now wait for f2 to run and modify location 10 before using its value + 11:number <- copy 10:number +] +def f2 [ + 10:number <- copy 34 +] ++schedule: f1 ++run: waiting for routine 2 to block ++schedule: f2 ++schedule: waking up blocked routine 1 ++schedule: f1 +# if we got the synchronization wrong we'd be storing 0 in location 11 ++mem: storing 34 in location 11 + +:(before "End routine Fields") +// only if state == WAITING +int waiting_on_routine_to_block; +:(before "End routine Constructor") +waiting_on_routine_to_block = 0; + +:(before "End Primitive Recipe Declarations") +WAIT_FOR_ROUTINE_TO_BLOCK, +:(before "End Primitive Recipe Numbers") +put(Recipe_ordinal, "wait-for-routine-to-block", WAIT_FOR_ROUTINE_TO_BLOCK); +:(before "End Primitive Recipe Checks") +case WAIT_FOR_ROUTINE_TO_BLOCK: { + if (SIZE(inst.ingredients) != 1) { + raise << maybe(get(Recipe, r).name) << "'wait-for-routine-to-block' requires exactly one ingredient, but got '" << inst.original_string << "'\n" << end(); + break; + } + if (!is_mu_number(inst.ingredients.at(0))) { + raise << maybe(get(Recipe, r).name) << "first ingredient of 'wait-for-routine-to-block' should be a routine id generated by 'start-running', but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); + break; + } + break; +} +:(before "End Primitive Recipe Implementations") +case WAIT_FOR_ROUTINE_TO_BLOCK: { + if (ingredients.at(0).at(0) == Current_routine->id) { + raise << maybe(current_recipe_name()) << "routine can't wait for itself! '" << to_original_string(current_instruction()) << "'\n" << end(); + break; + } + Current_routine->state = WAITING; + Current_routine->waiting_on_routine_to_block = ingredients.at(0).at(0); + trace(9998, "run") << "waiting for routine " << ingredients.at(0).at(0) << " to block" << end(); +//? cerr << Current_routine->id << ": waiting for routine " << ingredients.at(0).at(0) << " to block\n"; + break; +} + +:(before "End Scheduler State Transitions") +// Wake up any routines waiting for other routines to stop running. +for (int i = 0; i < SIZE(Routines); ++i) { + if (Routines.at(i)->state != WAITING) continue; + routine* waiter = Routines.at(i); + if (!waiter->waiting_on_routine_to_block) continue; + int id = waiter->waiting_on_routine_to_block; + assert(id != waiter->id); // routine can't wait on itself + for (int j = 0; j < SIZE(Routines); ++j) { + const routine* waitee = Routines.at(j); + if (waitee->id == id && waitee->state != RUNNING) { + // routine is WAITING or COMPLETED or DISCONTINUED + trace(9999, "schedule") << "waking up blocked routine " << waiter->id << end(); +//? cerr << id << " is now unblocked (" << waitee->state << "); waking up waiting routine " << waiter->id << '\n'; + waiter->state = RUNNING; + waiter->waiting_on_routine_to_block = 0; + } + } +} + +//: allow waiting on a routine to complete :(scenario wait_for_routine) def f1 [ - 1:number <- copy 0 - 12:number/routine <- start-running f2 - wait-for-routine 12:number/routine - # now wait for f2 to run and modify location 1 before using its value - 3:number <- copy 1:number + # add a few routines to run + 1:number/routine <- start-running f2 + 2:number/routine <- start-running f3 + wait-for-routine 1:number/routine + # now wait for f2 to *complete* and modify location 13 before using its value + 20:number <- copy 13:number ] def f2 [ - 1:number <- copy 34 + 10:number <- copy 0 # just padding + switch # simulate a block; routine f1 shouldn't restart at this point + 13:number <- copy 34 +] +def f3 [ + # padding routine just to help simulate the block in f2 using 'switch' + 11:number <- copy 0 + 12:number <- copy 0 ] +schedule: f1 +run: waiting for routine 2 +schedule: f2 ++schedule: f3 ++schedule: f2 +schedule: waking up routine 1 +schedule: f1 -# if we got the synchronization wrong we'd be storing 0 in location 3 -+mem: storing 34 in location 3 +# if we got the synchronization wrong we'd be storing 0 in location 20 ++mem: storing 34 in location 20 :(before "End routine Fields") // only if state == WAITING @@ -310,27 +408,34 @@ put(Recipe_ordinal,->state = WAITING; Current_routine->waiting_on_routine = ingredients.at(0).at(0); trace(9998, "run") << "waiting for routine " << ingredients.at(0).at(0) << end(); +//? cerr << Current_routine->id << ": waiting for routine " << ingredients.at(0).at(0) << '\n'; break; } :(before "End Scheduler State Transitions") -// Wake up any routines waiting for other routines to go to sleep. +// Wake up any routines waiting for other routines to complete. // Important: this must come after the scheduler loop above giving routines // waiting for locations to change a chance to wake up. for (int i = 0; i < SIZE(Routines); ++i) { if (Routines.at(i)->state != WAITING) continue; - if (!Routines.at(i)->waiting_on_routine) continue; - int id = Routines.at(i)->waiting_on_routine; - assert(id != Routines.at(i)->id); // routine can't wait on itself + routine* waiter = Routines.at(i); + if (!waiter->waiting_on_routine) continue; + int id = waiter->waiting_on_routine; + assert(id != waiter->id); // routine can't wait on itself for (int j = 0; j < SIZE(Routines); ++j) { - if (Routines.at(j)->id == id && Routines.at(j)->state != RUNNING) { - trace(9999, "schedule") << "waking up routine " << Routines.at(i)->id << end(); - Routines.at(i)->state = RUNNING; - Routines.at(i)->waiting_on_routine = 0; + const routine* waitee = Routines.at(j); + if (waitee->id == id && waitee->state != RUNNING && waitee->state != WAITING) { + // routine is COMPLETED or DISCONTINUED + trace(9999, "schedule") << "waking up routine " << waiter->id << end(); +//? cerr << id << " is now done (" << waitee->state << "); waking up waiting routine " << waiter->id << '\n'; + waiter->state = RUNNING; + waiter->waiting_on_routine = 0; } } } +//: yield voluntarily to let some other routine run + :(before "End Primitive Recipe Declarations") SWITCH, :(before "End Primitive Recipe Numbers") @@ -339,17 +444,18 @@ put(Recipe_ordinal,case SWITCH: { break; } +//: pick another RUNNING routine at random and wait for it to get a chance +//: there might be a better implementation than this :(before "End Primitive Recipe Implementations") case SWITCH: { int id = some_other_running_routine(); if (id) { assert(id != Current_routine->id); Current_routine->state = WAITING; - Current_routine->waiting_on_routine = id; + Current_routine->waiting_on_routine_to_block = id; } break; } - :(code) int some_other_running_routine() { for (int i = 0; i < SIZE(Routines); ++i) { diff --git a/html/075channel.mu.html b/html/075channel.mu.html index 54fda434..6f34b054 100644 --- a/html/075channel.mu.html +++ b/html/075channel.mu.html @@ -454,28 +454,28 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color F buffer-lines-blocks-until-newline: channel should be empty after init] # buffer stdin into buffered-stdin, try to read from buffered-stdin buffer-routine:number <- start-running buffer-lines, source, buffered-stdin - wait-for-routine buffer-routine + wait-for-routine-to-block buffer-routine empty? <- channel-empty? buffered-chan assert empty?:boolean, [ F buffer-lines-blocks-until-newline: channel should be empty after buffer-lines bring-up] # write 'a' sink <- write sink, 97/a restart buffer-routine - wait-for-routine buffer-routine + wait-for-routine-to-block buffer-routine empty? <- channel-empty? buffered-chan assert empty?:boolean, [ F buffer-lines-blocks-until-newline: channel should be empty after writing 'a'] # write 'b' sink <- write sink, 98/b restart buffer-routine - wait-for-routine buffer-routine + wait-for-routine-to-block buffer-routine empty? <- channel-empty? buffered-chan assert empty?:boolean, [ F buffer-lines-blocks-until-newline: channel should be empty after writing 'b'] # write newline sink <- write sink, 10/newline restart buffer-routine - wait-for-routine buffer-routine + wait-for-routine-to-block buffer-routine empty? <- channel-empty? buffered-chan data-emitted?:boolean <- not empty? assert data-emitted?, [ diff --git a/html/080display.cc.html b/html/080display.cc.html index fc7d0e22..44613b49 100644 --- a/html/080display.cc.html +++ b/html/080display.cc.html @@ -256,7 +256,7 @@ put(Recipe_ordinal,int h=tb_height(); int height = (h >= 0) ? h : 0; if (Display_row < height-1) { - Display_row++; + ++Display_row; tb_set_cursor(Display_column, Display_row); if (Autodisplay) tb_present(); } @@ -274,7 +274,7 @@ put(Recipe_ordinal,:(before "End Primitive Recipe Implementations") case MOVE_CURSOR_UP_ON_DISPLAY: { if (Display_row > 0) { - Display_row--; + --Display_row; tb_set_cursor(Display_column, Display_row); if (Autodisplay) tb_present(); } @@ -294,7 +294,7 @@ put(Recipe_ordinal,int w=tb_width(); int width = (w >= 0) ? w : 0; if (Display_column < width-1) { - Display_column++; + ++Display_column; tb_set_cursor(Display_column, Display_row); if (Autodisplay) tb_present(); } @@ -312,7 +312,7 @@ put(Recipe_ordinal,:(before "End Primitive Recipe Implementations") case MOVE_CURSOR_LEFT_ON_DISPLAY: { if (Display_column > 0) { - Display_column--; + --Display_column; tb_set_cursor(Display_column, Display_row); if (Autodisplay) tb_present(); } @@ -326,7 +326,7 @@ put(Recipe_ordinal,} :(code) void move_cursor_to_start_of_next_line_on_display() { - if (Display_row < tb_height()-1) Display_row++; + if (Display_row < tb_height()-1) ++Display_row; else Display_row = 0; Display_column = 0; tb_set_cursor(Display_column, Display_row); diff --git a/html/082scenario_screen.cc.html b/html/082scenario_screen.cc.html index d66b7407..5501e57f 100644 --- a/html/082scenario_screen.cc.html +++ b/html/082scenario_screen.cc.html @@ -161,10 +161,13 @@ def main [ $error: 0 :(scenarios run_mu_scenario) +//: It's easier to implement assume-screen and other similar scenario-only +//: primitives if they always write to a fixed location. So we'll assign a +//: single fixed location for the per-scenario screen, keyboard, file system, +//: etc. Carve space for these fixed locations out of the reserved-for-test +//: locations. + :(before "End Globals") -// Scenarios may not define default-space, so they should fit within the -// initial area of memory reserved for tests. We'll put the predefined -// variables available to them at the end of that region. const int Max_variables_in_scenarios = Reserved_for_tests-100; int Next_predefined_global_for_scenarios = Max_variables_in_scenarios; :(before "End Setup") @@ -192,9 +195,17 @@ Name[r]["screen"] = SCREEN// `screen:address:screen <- new-fake-screen width, height` if (curr.name == "assume-screen") { curr.name = "new-fake-screen"; - assert(curr.products.empty()); - curr.products.push_back(reagent("screen:address:screen/raw")); // only allowed in scenario blocks - curr.products.at(0).set_value(SCREEN); + if (!curr.products.empty()) { + raise << result.name << ": 'assume-screen' has no products\n" << end(); + } + else if (!starts_with(result.name, "scenario_")) { + raise << result.name << ": 'assume-screen' can't be called here, only in scenarios\n" << end(); + } + else { + assert(curr.products.empty()); + curr.products.push_back(reagent("screen:address:screen/raw")); + curr.products.at(0).set_value(SCREEN); + } } :(scenario assume_screen_shows_up_in_errors) @@ -211,6 +222,14 @@ SCREEN_SHOULD_CONTAIN, put(Recipe_ordinal, "screen-should-contain", SCREEN_SHOULD_CONTAIN); :(before "End Primitive Recipe Checks") case SCREEN_SHOULD_CONTAIN: { + if (SIZE(inst.ingredients) != 1) { + raise << maybe(get(Recipe, r).name) << "'screen-should-contain' requires exactly one ingredient, but got '" << inst.original_string << "'\n" << end(); + break; + } + if (!is_literal_string(inst.ingredients.at(0))) { + raise << maybe(get(Recipe, r).name) << "first ingredient of 'screen-should-contain' should be a literal string, but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); + break; + } break; } :(before "End Primitive Recipe Implementations") @@ -227,6 +246,18 @@ SCREEN_SHOULD_CONTAIN_IN_COLOR, put(Recipe_ordinal, "screen-should-contain-in-color", SCREEN_SHOULD_CONTAIN_IN_COLOR); :(before "End Primitive Recipe Checks") case SCREEN_SHOULD_CONTAIN_IN_COLOR: { + if (SIZE(inst.ingredients) != 2) { + raise << maybe(get(Recipe, r).name) << "'screen-should-contain-in-color' requires exactly two ingredients, but got '" << inst.original_string << "'\n" << end(); + break; + } + if (!is_mu_number(inst.ingredients.at(0))) { + raise << maybe(get(Recipe, r).name) << "first ingredient of 'screen-should-contain-in-color' should be a number (color code), but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); + break; + } + if (!is_literal_string(inst.ingredients.at(1))) { + raise << maybe(get(Recipe, r).name) << "second ingredient of 'screen-should-contain-in-color' should be a literal string, but got '" << inst.ingredients.at(1).original_string << "'\n" << end(); + break; + } break; } :(before "End Primitive Recipe Implementations") @@ -269,7 +300,11 @@ put(Recipe_ordinal,for (int row = 0; row < screen_height; ++row) { cursor.skip_whitespace_and_comments(); if (cursor.at_end()) break; - assert(cursor.get() == '.'); + if (cursor.get() != '.') { + raise << Current_scenario->name << ": each row of the expected screen should start with a '.'\n" << end(); + Passed = false; + return; + } for (int column = 0; column < screen_width; ++column, addr+= /*size of screen-cell*/2) { const int cell_color_offset = 1; uint32_t curr = cursor.get(); @@ -321,10 +356,17 @@ put(Recipe_ordinal,if (!Scenario_testing_scenario) Passed = false; return; } - assert(cursor.get() == '.'); + if (cursor.get() != '.') { + raise << Current_scenario->name << ": row " << row << " of the expected screen is too long\n" << end(); + Passed = false; + return; + } } cursor.skip_whitespace_and_comments(); - assert(cursor.at_end()); + if (!cursor.at_end()) { + raise << Current_scenario->name << ": expected screen has too many rows\n" << end(); + Passed = false; + } } raw_string_stream::raw_string_stream(const string& backing) :index(0), max(SIZE(backing)), buf(backing.c_str()) {} diff --git a/html/085scenario_console.cc.html b/html/085scenario_console.cc.html index f2e79b3c..f4c60570 100644 --- a/html/085scenario_console.cc.html +++ b/html/085scenario_console.cc.html @@ -91,70 +91,67 @@ put(Recipe_ordinal,(in, r); int num_events = count_events(r); // initialize the events like in new-fake-console - int size = /*space for refcount*/1 + /*space for length*/1 + num_events*size_of_event(); - ensure_space(size); - int event_data_address = Current_routine->alloc; + int size = /*space for refcount and length*/2 + num_events*size_of_event(); + int event_data_address = allocate(size); // store length - put(Memory, Current_routine->alloc+/*skip refcount*/1, num_events); - Current_routine->alloc += /*skip refcount and length*/2; + put(Memory, event_data_address+/*skip refcount*/1, num_events); + int curr_address = event_data_address + /*skip refcount and length*/2; for (int i = 0; i < SIZE(r.steps); ++i) { - const instruction& curr = r.steps.at(i); - if (curr.name == "left-click") { + const instruction& inst = r.steps.at(i); + if (inst.name == "left-click") { trace(9999, "mem") << "storing 'left-click' event starting at " << Current_routine->alloc << end(); - put(Memory, Current_routine->alloc, /*tag for 'touch-event' variant of 'event' exclusive-container*/2); - put(Memory, Current_routine->alloc+1+/*offset of 'type' in 'mouse-event'*/0, TB_KEY_MOUSE_LEFT); - put(Memory, Current_routine->alloc+1+/*offset of 'row' in 'mouse-event'*/1, to_integer(curr.ingredients.at(0).name)); - put(Memory, Current_routine->alloc+1+/*offset of 'column' in 'mouse-event'*/2, to_integer(curr.ingredients.at(1).name)); - Current_routine->alloc += size_of_event(); + 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(); } - else if (curr.name == "press") { - trace(9999, "mem") << "storing 'press' event starting at " << Current_routine->alloc << end(); - string key = curr.ingredients.at(0).name; + else if (inst.name == "press") { + trace(9999, "mem") << "storing 'press' event starting at " << curr_address << end(); + string key = inst.ingredients.at(0).name; if (is_integer(key)) - put(Memory, Current_routine->alloc+1, to_integer(key)); + put(Memory, curr_address+1, to_integer(key)); else if (contains_key(Key, key)) - put(Memory, Current_routine->alloc+1, Key[key]); + put(Memory, curr_address+1, Key[key]); else raise << "assume-console: can't press '" << key << "'\n" << end(); - if (get_or_insert(Memory, Current_routine->alloc+1) < 256) + if (get_or_insert(Memory, curr_address+1) < 256) // these keys are in ascii - put(Memory, Current_routine->alloc, /*tag for 'text' variant of 'event' exclusive-container*/0); + put(Memory, curr_address, /*tag for 'text' variant of 'event' exclusive-container*/0); else { // distinguish from unicode - put(Memory, Current_routine->alloc, /*tag for 'keycode' variant of 'event' exclusive-container*/1); + put(Memory, curr_address, /*tag for 'keycode' variant of 'event' exclusive-container*/1); } - Current_routine->alloc += size_of_event(); + curr_address += size_of_event(); } // End Event Handlers else { // keyboard input - assert(curr.name == "type"); - trace(9999, "mem") << "storing 'type' event starting at " << Current_routine->alloc << end(); - const string& contents = curr.ingredients.at(0).name; + assert(inst.name == "type"); + trace(9999, "mem") << "storing 'type' event starting at " << curr_address << end(); + const string& contents = inst.ingredients.at(0).name; const char* raw_contents = contents.c_str(); int num_keyboard_events = unicode_length(contents); int curr = 0; for (int i = 0; i < num_keyboard_events; ++i) { - trace(9999, "mem") << "storing 'text' tag at " << Current_routine->alloc << end(); - put(Memory, Current_routine->alloc, /*tag for 'text' variant of 'event' exclusive-container*/0); + trace(9999, "mem") << "storing 'text' tag at " << curr_address << end(); + put(Memory, curr_address, /*tag for 'text' variant of 'event' exclusive-container*/0); uint32_t curr_character; assert(curr < SIZE(contents)); tb_utf8_char_to_unicode(&curr_character, &raw_contents[curr]); - trace(9999, "mem") << "storing character " << curr_character << " at " << Current_routine->alloc+1 << end(); - put(Memory, Current_routine->alloc+/*skip exclusive container tag*/1, curr_character); + trace(9999, "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); curr += tb_utf8_char_length(raw_contents[curr]); - Current_routine->alloc += size_of_event(); + curr_address += size_of_event(); } } } - assert(Current_routine->alloc == event_data_address+size); + assert(curr_address == event_data_address+size); // wrap the array of events in a console object - ensure_space(size_of_console()); - put(Memory, CONSOLE, Current_routine->alloc); - trace(9999, "mem") << "storing console in " << Current_routine->alloc << end(); - Current_routine->alloc += size_of_console(); - int console_address = get_or_insert(Memory, CONSOLE); - trace(9999, "mem") << "storing console data in " << console_address+2 << end(); + int console_address = allocate(size_of_console()); + trace(9999, "mem") << "storing console in " << console_address << end(); + put(Memory, CONSOLE, console_address); + trace(9999, "mem") << "storing console data in " << console_address+/*skip refcount*/1+/*offset of 'data' in container 'events'*/1 << end(); put(Memory, console_address+/*skip refcount*/1+/*offset of 'data' in container 'events'*/1, event_data_address); // increment refcount for event data put(Memory, event_data_address, 1); @@ -291,8 +288,8 @@ put(Recipe_ordinal,} int console_address = get_or_insert(Memory, CONSOLE); int console_data = get_or_insert(Memory, console_address+1); - int size = get_or_insert(Memory, console_data); // array size - for (int i = 0, curr = console_data+1; i < size; ++i, curr+=size_of_event()) { + int length = get_or_insert(Memory, console_data); // array length + 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; for (int n = 0; n < size_of_event(); ++n) @@ -309,7 +306,7 @@ put(Recipe_ordinal,if (curr.name == "type") result += unicode_length(curr.ingredients.at(0).name); else - result++; + ++result; } return result; } diff --git a/html/087file.cc.html b/html/087file.cc.html index d8af7ae0..052774d9 100644 --- a/html/087file.cc.html +++ b/html/087file.cc.html @@ -52,11 +52,18 @@ put(Recipe_ordinal,(get(Recipe, r).name) << "'$open-file-for-reading' requires exactly one ingredient, but got '" << inst.original_string << "'\n" << end(); break; } - string filename; if (!is_mu_string(inst.ingredients.at(0))) { raise << maybe(get(Recipe, r).name) << "first ingredient of '$open-file-for-reading' should be a string, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end(); break; } + if (SIZE(inst.products) != 1) { + raise << maybe(get(Recipe, r).name) << "'$open-file-for-reading' requires exactly one product, but got '" << inst.original_string << "'\n" << end(); + break; + } + if (!is_mu_number(inst.products.at(0))) { + raise << maybe(get(Recipe, r).name) << "first product of '$open-file-for-reading' should be a number (file handle), but got '" << to_string(inst.products.at(0)) << "'\n" << end(); + break; + } break; } :(before "End Primitive Recipe Implementations") @@ -80,11 +87,18 @@ put(Recipe_ordinal,(get(Recipe, r).name) << "'$open-file-for-writing' requires exactly one ingredient, but got '" << inst.original_string << "'\n" << end(); break; } - string filename; if (!is_mu_string(inst.ingredients.at(0))) { raise << maybe(get(Recipe, r).name) << "first ingredient of '$open-file-for-writing' should be a string, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end(); break; } + if (SIZE(inst.products) != 1) { + raise << maybe(get(Recipe, r).name) << "'$open-file-for-writing' requires exactly one product, but got '" << inst.original_string << "'\n" << end(); + break; + } + if (!is_mu_number(inst.products.at(0))) { + raise << maybe(get(Recipe, r).name) << "first product of '$open-file-for-writing' should be a number (file handle), but got '" << to_string(inst.products.at(0)) << "'\n" << end(); + break; + } break; } :(before "End Primitive Recipe Implementations") @@ -107,11 +121,22 @@ put(Recipe_ordinal,(get(Recipe, r).name) << "'$read-from-file' requires exactly one ingredient, but got '" << inst.original_string << "'\n" << end(); break; } - string filename; if (!is_mu_number(inst.ingredients.at(0))) { raise << maybe(get(Recipe, r).name) << "first ingredient of '$read-from-file' should be a number, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end(); break; } + if (SIZE(inst.products) != 2) { + raise << maybe(get(Recipe, r).name) << "'$read-from-file' requires exactly two products, but got '" << inst.original_string << "'\n" << end(); + break; + } + if (!is_mu_character(inst.products.at(0))) { + raise << maybe(get(Recipe, r).name) << "first product of '$read-from-file' should be a character, but got '" << to_string(inst.products.at(0)) << "'\n" << end(); + break; + } + if (!is_mu_boolean(inst.products.at(1))) { + raise << maybe(get(Recipe, r).name) << "second product of '$read-from-file' should be a boolean, but got '" << to_string(inst.products.at(1)) << "'\n" << end(); + break; + } break; } :(before "End Primitive Recipe Implementations") @@ -122,9 +147,10 @@ put(Recipe_ordinal,(current_recipe_name()) << "can't read from null file in '" << to_string(current_instruction()) << "'\n" << end(); break; } - products.resize(1); + products.resize(2); if (feof(f)) { products.at(0).push_back(0); + products.at(1).push_back(1); // eof break; } if (ferror(f)) { @@ -132,12 +158,18 @@ put(Recipe_ordinal,break; } char c = getc(f); // todo: unicode + if (c == EOF) { + products.at(0).push_back(0); + products.at(1).push_back(1); // eof + break; + } if (ferror(f)) { raise << maybe(current_recipe_name()) << "couldn't read from file in '" << to_string(current_instruction()) << "'\n" << end(); raise << " errno: " << errno << '\n' << end(); break; } products.at(0).push_back(c); + products.at(1).push_back(0); // not eof break; } :(before "End Includes") @@ -153,7 +185,6 @@ put(Recipe_ordinal,(get(Recipe, r).name) << "'$write-to-file' requires exactly two ingredients, but got '" << inst.original_string << "'\n" << end(); break; } - string filename; if (!is_mu_number(inst.ingredients.at(0))) { raise << maybe(get(Recipe, r).name) << "first ingredient of '$write-to-file' should be a number, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end(); break; @@ -162,6 +193,10 @@ put(Recipe_ordinal,(get(Recipe, r).name) << "second ingredient of '$write-to-file' should be a character, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end(); break; } + if (!inst.products.empty()) { + raise << maybe(get(Recipe, r).name) << "'$write-to-file' writes to no products, but got '" << inst.original_string << "'\n" << end(); + break; + } break; } :(before "End Primitive Recipe Implementations") @@ -198,11 +233,18 @@ put(Recipe_ordinal,(get(Recipe, r).name) << "'$close-file' requires exactly one ingredient, but got '" << inst.original_string << "'\n" << end(); break; } - string filename; if (!is_mu_number(inst.ingredients.at(0))) { raise << maybe(get(Recipe, r).name) << "first ingredient of '$close-file' should be a number, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end(); break; } + if (SIZE(inst.products) != 1) { + raise << maybe(get(Recipe, r).name) << "'$close-file' requires exactly one product, but got '" << inst.original_string << "'\n" << end(); + break; + } + if (inst.products.at(0).name != inst.ingredients.at(0).name) { + raise << maybe(get(Recipe, r).name) << "'$close-file' requires its product to be the same as its ingredient, but got '" << inst.original_string << "'\n" << end(); + break; + } break; } :(before "End Primitive Recipe Implementations") diff --git a/html/088file.mu.html b/html/088file.mu.html index 6a194227..e86201ed 100644 --- a/html/088file.mu.html +++ b/html/088file.mu.html @@ -31,7 +31,7 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color
-# Wrappers around file-system primitives that take a 'filesystem' object and
+# Wrappers around file system primitives that take a 'filesystem' object and
 # are thus easier to test.
 
 container filesystem [
@@ -48,7 +48,7 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color
   load-ingredients
   {
     break-if fs
-    # real file-system
+    # real file system
     file:number <- $open-file-for-reading filename
     assert file, [file not found]
     contents:address:source:character, sink:address:sink:character <- new-channel 30
@@ -63,6 +63,7 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color
     done?:boolean <- greater-or-equal i, len
     break-if done?
     tmp:file-mapping <- index *data, i
+    i <- add i, 1
     curr-filename:address:array:character <- get tmp, name:offset
     found?:boolean <- equal filename, curr-filename
     loop-unless found?
@@ -78,13 +79,13 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color
   local-scope
   load-ingredients
   {
-    c:character <- $read-from-file file
-    break-unless c
+    c:character, eof?:boolean <- $read-from-file file
+    break-if eof?
     sink <- write sink, c
     loop
   }
   sink <- close sink
-  $close-file file
+  file <- $close-file file
 ]
 
 def transmit-from-text contents:address:array:character, sink:address:sink:character -> sink:address:sink:character [
@@ -106,12 +107,21 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color
 def start-writing fs:address:filesystem, filename:address:array:character -> sink:address:sink:character, routine-id:number [
   local-scope
   load-ingredients
-  file:number <- $open-file-for-writing filename
   source:address:source:character, sink:address:sink:character <- new-channel 30
-  routine-id <- start-running transmit-to-file file, source
+  {
+    break-if fs
+    # real file system
+    file:number <- $open-file-for-writing filename
+    assert file, [no such file]
+    routine-id <- start-running transmit-to-file file, source
+    reply
+  }
+  # fake file system
+  # beware: doesn't support multiple concurrent writes yet
+  routine-id <- start-running transmit-to-fake-file fs, filename, source
 ]
 
-def transmit-to-file file:number, source:address:source:character -> file:number, source:address:source:character [
+def transmit-to-file file:number, source:address:source:character -> source:address:source:character [
   local-scope
   load-ingredients
   {
@@ -120,7 +130,52 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color
     $write-to-file file, c
     loop
   }
-  $close-file file
+  file <- $close-file file
+]
+
+def transmit-to-fake-file fs:address:filesystem, filename:address:array:character, source:address:source:character -> fs:address:filesystem, source:address:source:character [
+  local-scope
+  load-ingredients
+  # compute new file contents
+  buf:address:buffer <- new-buffer 30
+  {
+    c:character, done?:boolean, source <- read source
+    break-if done?
+    buf <- append buf, c
+    loop
+  }
+  contents:address:array:character <- buffer-to-array buf
+  new-file-mapping:file-mapping <- merge filename, contents
+  # write to filesystem
+  curr-filename:address:array:character <- copy 0
+  data:address:array:file-mapping <- get *fs, data:offset
+  # replace file contents if it already exists
+  i:number <- copy 0
+  len:number <- length *data
+  {
+    done?:boolean <- greater-or-equal i, len
+    break-if done?
+    tmp:file-mapping <- index *data, i
+    curr-filename <- get tmp, name:offset
+    found?:boolean <- equal filename, curr-filename
+    loop-unless found?
+    put-index *data, i, new-file-mapping
+    reply
+  }
+  # if file didn't already exist, make room for it
+  new-len:number <- add len, 1
+  new-data:address:array:file-mapping <- new file-mapping:type, new-len
+  put *fs, data:offset, new-data
+  # copy over old files
+  i:number <- copy 0
+  {
+    done?:boolean <- greater-or-equal i, len
+    break-if done?
+    tmp:file-mapping <- index *data, i
+    put-index *new-data, i, tmp
+  }
+  # write new file
+  put-index *new-data, len, new-file-mapping
 ]
 
diff --git a/html/089scenario_filesystem.cc.html b/html/089scenario_filesystem.cc.html index 6dc1faa3..0f9b51b6 100644 --- a/html/089scenario_filesystem.cc.html +++ b/html/089scenario_filesystem.cc.html @@ -222,21 +222,34 @@ string munge_filesystem_contents(void construct_filesystem_object(const map<string, string>& contents) { int filesystem_data_address = allocate(SIZE(contents)*2 + /*array length*/1); - int curr = filesystem_data_address + /*skip refcount*/1 + /*skip array length*/1; + int curr = filesystem_data_address + /*skip refcount and length*/2; for (map<string, string>::const_iterator p = contents.begin(); p != contents.end(); ++p) { put(Memory, curr, new_mu_string(p->first)); - curr++; + trace(9999, "mem") << "storing file name " << get(Memory, curr) << " in location " << curr << end(); + put(Memory, get(Memory, curr), 1); + trace(9999, "mem") << "storing refcount 1 in location " << get(Memory, curr) << end(); + ++curr; put(Memory, curr, new_mu_string(p->second)); - curr++; + trace(9999, "mem") << "storing file contents " << get(Memory, curr) << " in location " << curr << end(); + put(Memory, get(Memory, curr), 1); + trace(9999, "mem") << "storing refcount 1 in location " << get(Memory, curr) << end(); + ++curr; } - put(Memory, filesystem_data_address+/*skip refcount*/1, SIZE(contents)); // size of array + curr = filesystem_data_address+/*skip refcount*/1; + put(Memory, curr, SIZE(contents)); // size of array + trace(9999, "mem") << "storing filesystem size " << get(Memory, curr) << " in location " << curr << end(); put(Memory, filesystem_data_address, 1); // initialize refcount + trace(9999, "mem") << "storing refcount 1 in location " << filesystem_data_address << end(); // wrap the filesystem data in a filesystem object int filesystem_address = allocate(size_of_filesystem()); - put(Memory, filesystem_address+/*skip refcount*/1, filesystem_data_address); + curr = filesystem_address+/*skip refcount*/1; + put(Memory, curr, filesystem_data_address); + trace(9999, "mem") << "storing filesystem data address " << filesystem_data_address << " in location " << curr << end(); put(Memory, filesystem_address, 1); // initialize refcount + trace(9999, "mem") << "storing refcount 1 in location " << filesystem_address << end(); // save in product put(Memory, FILESYSTEM, filesystem_address); + trace(9999, "mem") << "storing filesystem address " << filesystem_address << " in location " << FILESYSTEM << end(); } int size_of_filesystem() { diff --git a/html/090scenario_filesystem_test.mu.html b/html/090scenario_filesystem_test.mu.html index 1188f1dd..dc20709d 100644 --- a/html/090scenario_filesystem_test.mu.html +++ b/html/090scenario_filesystem_test.mu.html @@ -14,9 +14,12 @@ pre { white-space: pre-wrap; font-family: monospace; color: #eeeeee; background- body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color: #080808; } * { font-size: 12pt; font-size: 1em; } .muScenario { color: #00af00; } +.Delimiter { color: #800080; } +.muRecipe { color: #ff8700; } .Comment { color: #9090ff; } .Constant { color: #00a0a0; } .Special { color: #c00000; } +.muControl { color: #c0a020; } --> @@ -51,6 +54,77 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color 5 <- 1 # eof ] ] + +scenario write-to-fake-file [ + local-scope + assume-filesystem [ + ] + sink:address:sink:character, writer:number/routine <- start-writing filesystem:address:filesystem, [a] + sink <- write sink, 120/x + sink <- write sink, 121/y + close sink + wait-for-routine writer + contents-read-back:address:array:character <- slurp filesystem, [a] + 10:boolean/raw <- equal contents-read-back, [xy] + memory-should-contain [ + 10 <- 1 # file contents read back exactly match what was written + ] +] + +scenario write-to-fake-file-that-exists [ + local-scope + assume-filesystem [ + [a] <- [] + ] + sink:address:sink:character, writer:number/routine <- start-writing filesystem:address:filesystem, [a] + sink <- write sink, 120/x + sink <- write sink, 121/y + close sink + wait-for-routine writer + contents-read-back:address:array:character <- slurp filesystem, [a] + 10:boolean/raw <- equal contents-read-back, [xy] + memory-should-contain [ + 10 <- 1 # file contents read back exactly match what was written + ] +] + +scenario write-to-existing-file-preserves-other-files [ + local-scope + assume-filesystem [ + [a] <- [] + [b] <- [ + |bcd| + ] + ] + sink:address:sink:character, writer:number/routine <- start-writing filesystem:address:filesystem, [a] + sink <- write sink, 120/x + sink <- write sink, 121/y + close sink + wait-for-routine writer + contents-read-back:address:array:character <- slurp filesystem, [a] + 10:boolean/raw <- equal contents-read-back, [xy] + other-file-contents:address:array:character <- slurp filesystem, [b] + 11:boolean/raw <- equal other-file-contents, [bcd +] + memory-should-contain [ + 10 <- 1 # file contents read back exactly match what was written + 11 <- 1 # other files also continue to persist unchanged + ] +] + +def slurp fs:address:filesystem, filename:address:array:character -> contents:address:array:character [ + local-scope + load-ingredients + source:address:source:character <- start-reading fs, filename + buf:address:buffer <- new-buffer 30/capacity + { + c:character, done?:boolean, source <- read source + break-if done? + buf <- append buf, c + loop + } + contents <- buffer-to-array buf +] diff --git a/html/chessboard.mu.html b/html/chessboard.mu.html index 0b583444..95eea356 100644 --- a/html/chessboard.mu.html +++ b/html/chessboard.mu.html @@ -404,7 +404,7 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color source:address:source:character, sink:address:sink:character <- new-channel 2/capacity read-move-routine:number/routine <- start-running read-move, source, screen:address:screen # 'read-move' is waiting for input - wait-for-routine read-move-routine + wait-for-routine-to-block read-move-routine read-move-state:number <- routine-state read-move-routine waiting?:boolean <- equal read-move-state, 3/waiting assert waiting?, [ @@ -413,7 +413,7 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color sink <- write sink, 97/a restart read-move-routine # 'read-move' still waiting for input - wait-for-routine read-move-routine + wait-for-routine-to-block read-move-routine read-move-state <- routine-state read-move-routine waiting? <- equal read-move-state, 3/waiting assert waiting?, [ @@ -422,7 +422,7 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color sink <- write sink, 50/'2' restart read-move-routine # 'read-move' still waiting for input - wait-for-routine read-move-routine + wait-for-routine-to-block read-move-routine read-move-state <- routine-state read-move-routine waiting? <- equal read-move-state, 3/waiting assert waiting?, [ @@ -431,7 +431,7 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color sink <- write sink, 45/'-' restart read-move-routine # 'read-move' still waiting for input - wait-for-routine read-move-routine + wait-for-routine-to-block read-move-routine read-move-state <- routine-state read-move-routine waiting? <- equal read-move-state, 3/waiting assert waiting?, [ @@ -440,7 +440,7 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color sink <- write sink, 97/a restart read-move-routine # 'read-move' still waiting for input - wait-for-routine read-move-routine + wait-for-routine-to-block read-move-routine read-move-state <- routine-state read-move-routine waiting? <- equal read-move-state, 3/waiting assert waiting?, [ @@ -449,7 +449,7 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color sink <- write sink, 52/'4' restart read-move-routine # 'read-move' still waiting for input - wait-for-routine read-move-routine + wait-for-routine-to-block read-move-routine read-move-state <- routine-state read-move-routine waiting? <- equal read-move-state, 3/waiting assert waiting?, [ @@ -458,7 +458,7 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color sink <- write sink, 10 # newline restart read-move-routine # 'read-move' now completes - wait-for-routine read-move-routine + wait-for-routine-to-block read-move-routine read-move-state <- routine-state read-move-routine completed?:boolean <- equal read-move-state, 1/completed assert completed?, [ @@ -477,7 +477,7 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color source:address:source:character, sink:address:sink:character <- new-channel 2/capacity read-move-routine:number <- start-running read-move, source, screen:address:screen # 'read-move' is waiting for input - wait-for-routine read-move-routine + wait-for-routine-to-block read-move-routine read-move-state:number <- routine-state read-move-routine waiting?:boolean <- equal read-move-state, 3/waiting assert waiting?, [ @@ -486,7 +486,7 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color sink <- write sink, 113/q restart read-move-routine # 'read-move' completes - wait-for-routine read-move-routine + wait-for-routine-to-block read-move-routine read-move-state <- routine-state read-move-routine completed?:boolean <- equal read-move-state, 1/completed assert completed?, [ @@ -505,14 +505,14 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color source:address:source:character, sink:address:sink:character <- new-channel 2/capacity read-move-routine:number <- start-running read-move, source, screen:address:screen # 'read-move' is waiting for input - wait-for-routine read-move-routine + wait-for-routine-to-block read-move-routine read-move-state:number <- routine-state read-move-routine waiting?:boolean <- equal read-move-state, 3/waiting assert waiting?, [ F read-move-file: routine failed to pause after coming up (before any keys were pressed)] sink <- write sink, 50/'2' restart read-move-routine - wait-for-routine read-move-routine + wait-for-routine-to-block read-move-routine ] screen-should-contain [ .file too low: 2 . @@ -527,7 +527,7 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color source:address:source:character, sink:address:sink:character <- new-channel 2/capacity read-move-routine:number <- start-running read-move, source, screen:address:screen # 'read-move' is waiting for input - wait-for-routine read-move-routine + wait-for-routine-to-block read-move-routine read-move-state:number <- routine-state read-move-routine waiting?:boolean <- equal read-move-state, 3/waiting assert waiting?, [ @@ -535,7 +535,7 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color sink <- write sink, 97/a sink <- write sink, 97/a restart read-move-routine - wait-for-routine read-move-routine + wait-for-routine-to-block read-move-routine ] screen-should-contain [ .rank too high: a . @@ -550,7 +550,7 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color source:address:source:character, sink:address:sink:character <- new-channel 2/capacity read-move-routine:number <- start-running read-move, source, screen:address:screen # 'read-move' is waiting for input - wait-for-routine read-move-routine + wait-for-routine-to-block read-move-routine read-move-state:number <- routine-state read-move-routine waiting?:boolean <- equal read-move-state, 3/waiting assert waiting?, [ @@ -558,7 +558,7 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color sink <- write sink, 10/newline sink <- write sink, 97/a restart read-move-routine - wait-for-routine read-move-routine + wait-for-routine-to-block read-move-routine ] screen-should-contain [ .that's not enough . diff --git a/html/filesystem.mu.html b/html/filesystem.mu.html index 8fb32b8a..d29130e8 100644 --- a/html/filesystem.mu.html +++ b/html/filesystem.mu.html @@ -42,8 +42,6 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color { c:character, done?:boolean, source-file <- read source-file break-if done? - eof?:boolean <- equal c, -1 - break-if eof? sink-file <- write sink-file, c loop } -- cgit 1.4.1-2-gfad0