diff options
author | Kartik Agaram <vc@akkartik.com> | 2018-06-15 22:12:03 -0700 |
---|---|---|
committer | Kartik Agaram <vc@akkartik.com> | 2018-06-15 22:12:03 -0700 |
commit | 0edd9b9fc60440213e4df926ea511419ee291f1e (patch) | |
tree | 84b22f7afdeb9110ad7105c5fc070dacff178502 | |
parent | 3f34ac9369978b396d00a4fd02c9fb06b8eea621 (diff) | |
download | mu-0edd9b9fc60440213e4df926ea511419ee291f1e.tar.gz |
4257 - abortive attempt at safe fat pointers
I've been working on this slowly over several weeks, but it's too hard to support 0 as the null value for addresses. I constantly have to add exceptions for scalar value corresponding to an address type (now occupying 2 locations). The final straw is the test for 'reload': x:num <- reload text 'reload' returns an address. But there's no way to know that for arbitrary instructions. New plan: let's put this off for a bit and first create support for literals. Then use 'null' instead of '0' for addresses everywhere. Then it'll be easy to just change what 'null' means.
38 files changed, 757 insertions, 381 deletions
diff --git a/020run.cc b/020run.cc index 3739ad2c..0bba6bb2 100644 --- a/020run.cc +++ b/020run.cc @@ -87,6 +87,12 @@ void run_current_routine() { // Primitive Recipe Implementations case COPY: { copy(ingredients.begin(), ingredients.end(), inserter(products, products.begin())); + for (int i = 0; i < SIZE(current_instruction().products); ++i) { + if (is_mu_scalar(current_instruction().products.at(i)) && is_mu_address(current_instruction().ingredients.at(i))) + products.at(i).erase(products.at(i).begin()); // ignore alloc id + if (is_mu_address(current_instruction().products.at(i)) && is_mu_scalar(current_instruction().ingredients.at(i))) + products.at(i).insert(products.at(i).begin(), /*alloc id*/0); + } break; } // End Primitive Recipe Implementations @@ -196,7 +202,7 @@ if (argc > 1) { } transform_all(); //? cerr << to_original_string(get(Type_ordinal, "editor")) << '\n'; -//? cerr << to_original_string(get(Recipe, get(Recipe_ordinal, "event-loop"))) << '\n'; +//? cerr << to_original_string(get(Recipe, get(Recipe_ordinal, "handle-keyboard-event"))) << '\n'; //? DUMP(""); //? exit(0); if (trace_contains_errors()) { @@ -341,8 +347,9 @@ void write_memory(reagent/*copy*/ x, const vector<double>& data) { } :(code) -int size_of(const reagent& r) { +int size_of(reagent/*copy*/ r) { if (!r.type) return 0; + // Begin size_of(reagent r) Special-cases // End size_of(reagent r) Special-cases return size_of(r.type); } @@ -351,6 +358,7 @@ int size_of(const type_tree* type) { if (type->atom) { if (type->value == -1) return 1; // error value, but we'll raise it elsewhere if (type->value == 0) return 1; +//? if (type->value == Address_type_ordinal) return 2; // address and alloc id // End size_of(type) Atom Special-cases } else { @@ -358,7 +366,7 @@ int size_of(const type_tree* type) { raise << "invalid type " << to_string(type) << '\n' << end(); return 0; } - if (type->left->value == Address_type_ordinal) return 1; + if (type->left->value == Address_type_ordinal) return 2; // address and alloc id // End size_of(type) Non-atom Special-cases } // End size_of(type) Special-cases diff --git a/021check_instruction.cc b/021check_instruction.cc index d7628008..4bba31e5 100644 --- a/021check_instruction.cc +++ b/021check_instruction.cc @@ -112,8 +112,9 @@ def main [ :(code) // types_match with some leniency -bool types_coercible(const reagent& to, const reagent& from) { - if (types_match(to, from)) return true; +bool types_coercible(reagent/*copy*/ to, reagent/*copy*/ from) { + // Begin types_coercible(reagent to, reagent from) + if (types_match_sub(to, from)) return true; if (is_mu_address(from) && is_real_mu_number(to)) return true; if (is_mu_boolean(from) && is_real_mu_number(to)) return true; if (is_real_mu_number(from) && is_mu_character(to)) return true; @@ -121,10 +122,11 @@ bool types_coercible(const reagent& to, const reagent& from) { return false; } -bool types_match(const reagent& to, const reagent& from) { +bool types_match_sub(const reagent& to, const reagent& from) { // to sidestep type-checking, use /unsafe in the source. // this will be highlighted in red inside vim. just for setting up some tests. if (is_unsafe(from)) return true; + if (is_literal(from)) { if (is_mu_array(to)) return false; // End Matching Types For Literal(to) @@ -134,12 +136,16 @@ bool types_match(const reagent& to, const reagent& from) { if (is_mu_boolean(to)) return from.name == "0" || from.name == "1"; return size_of(to) == 1; // literals are always scalars } - return types_strictly_match(to, from); + return types_strictly_match_sub(to, from); +} +// variant for others to call +bool types_match(reagent/*copy*/ to, reagent/*copy*/ from) { + // Begin types_match(reagent to, reagent from) + return types_match_sub(to, from); } //: copy arguments for later layers -bool types_strictly_match(reagent/*copy*/ to, reagent/*copy*/ from) { - // End Preprocess types_strictly_match(reagent to, reagent from) +bool types_strictly_match_sub(const reagent& to, const reagent& from) { if (to.type == NULL) return false; // error if (is_literal(from) && to.type->value == Number_type_ordinal) return true; // to sidestep type-checking, use /unsafe in the source. @@ -150,6 +156,11 @@ bool types_strictly_match(reagent/*copy*/ to, reagent/*copy*/ from) { if (!to.type) return !from.type; return types_strictly_match(to.type, from.type); } +// variant for others to call +bool types_strictly_match(reagent/*copy*/ to, reagent/*copy*/ from) { + // Begin types_strictly_match(reagent to, reagent from) + return types_strictly_match_sub(to, from); +} bool types_strictly_match(const type_tree* to, const type_tree* from) { if (from == to) return true; @@ -268,7 +279,7 @@ bool is_mu_scalar(reagent/*copy*/ r) { } bool is_mu_scalar(const type_tree* type) { if (!type) return false; - if (is_mu_address(type)) return true; + if (is_mu_address(type)) return false; if (!type->atom) return false; if (is_literal(type)) return type->name != "literal-string"; diff --git a/022arithmetic.cc b/022arithmetic.cc index 530541aa..50a54578 100644 --- a/022arithmetic.cc +++ b/022arithmetic.cc @@ -95,18 +95,25 @@ case SUBTRACT: { } break; } +:(code) +bool is_raw(const reagent& r) { + return has_property(r, "raw"); +} + :(before "End Primitive Recipe Implementations") case SUBTRACT: { - double result = ingredients.at(0).at(0); + double result = scalar_ingredient(ingredients, 0); for (int i = 1; i < SIZE(ingredients); ++i) - result -= ingredients.at(i).at(0); + result -= scalar_ingredient(ingredients, i); products.resize(1); products.at(0).push_back(result); break; } :(code) -bool is_raw(const reagent& r) { - return has_property(r, "raw"); +double scalar_ingredient(const vector<vector<double> >& ingredients, int i) { + if (is_mu_address(current_instruction().ingredients.at(i))) + return ingredients.at(i).at(1); // skip alloc id + return ingredients.at(i).at(0); } :(scenario subtract_literal) diff --git a/023boolean.cc b/023boolean.cc index 3d51fd7a..976cbff9 100644 --- a/023boolean.cc +++ b/023boolean.cc @@ -26,7 +26,7 @@ case AND: { case AND: { bool result = true; for (int i = 0; i < SIZE(ingredients); ++i) - result = result && ingredients.at(i).at(0); + result = result && scalar_ingredient(ingredients, i); products.resize(1); products.at(0).push_back(result); break; @@ -84,7 +84,7 @@ case OR: { case OR: { bool result = false; for (int i = 0; i < SIZE(ingredients); ++i) - result = result || ingredients.at(i).at(0); + result = result || scalar_ingredient(ingredients, i); products.resize(1); products.at(0).push_back(result); break; @@ -127,8 +127,8 @@ case NOT: { break; } for (int i = 0; i < SIZE(inst.ingredients); ++i) { - if (!is_mu_scalar(inst.ingredients.at(i))) { - raise << maybe(get(Recipe, r).name) << "'not' requires boolean ingredients, but got '" << inst.ingredients.at(i).original_string << "'\n" << end(); + if (!is_mu_scalar(inst.ingredients.at(i)) && !is_mu_address(inst.ingredients.at(i))) { + raise << maybe(get(Recipe, r).name) << "'not' requires ingredients that can be interpreted as boolean, but got '" << inst.ingredients.at(i).original_string << "'\n" << end(); goto finish_checking_instruction; } } @@ -145,7 +145,7 @@ case NOT: { case NOT: { products.resize(SIZE(ingredients)); for (int i = 0; i < SIZE(ingredients); ++i) { - products.at(i).push_back(!ingredients.at(i).at(0)); + products.at(i).push_back(!scalar_ingredient(ingredients, i)); } break; } diff --git a/024jump.cc b/024jump.cc index 37011290..b7011c09 100644 --- a/024jump.cc +++ b/024jump.cc @@ -72,7 +72,7 @@ case JUMP_IF: { raise << maybe(get(Recipe, r).name) << "'" << to_original_string(inst) << "' should get exactly two ingredients\n" << end(); break; } - if (!is_mu_scalar(inst.ingredients.at(0))) { + if (!is_mu_address(inst.ingredients.at(0)) && !is_mu_scalar(inst.ingredients.at(0))) { raise << maybe(get(Recipe, r).name) << "'" << to_original_string(inst) << "' requires a boolean for its first ingredient, but '" << inst.ingredients.at(0).name << "' has type '" << names_to_string_without_quotes(inst.ingredients.at(0).type) << "'\n" << end(); break; } @@ -90,7 +90,7 @@ case JUMP_IF: { :(before "End Primitive Recipe Implementations") case JUMP_IF: { assert(current_instruction().ingredients.at(1).initialized); - if (!ingredients.at(0).at(0)) { + if (!scalar_ingredient(ingredients, 0)) { trace(9998, "run") << "jump-if fell through" << end(); break; } @@ -109,7 +109,7 @@ def main [ ] +run: jump-if {999: "literal"}, {1: "offset"} +run: jumping to instruction 2 --run: {1: "number"} <- copy {1: "literal"} +-run: {123: "number"} <- copy {1: "literal"} -mem: storing 1 in location 123 :(scenario jump_if_fallthrough) @@ -122,6 +122,17 @@ def main [ +run: {123: "number"} <- copy {1: "literal"} +mem: storing 1 in location 123 +:(scenario jump_if_on_address) +def main [ + 10:&:num <- copy 999/unsafe + jump-if 10:&:number, 1:offset + 123:num <- copy 1 +] ++run: jump-if {10: ("address" "number")}, {1: "offset"} ++run: jumping to instruction 3 +-run: {123: "number"} <- copy {1: "literal"} +-mem: storing 1 in location 123 + :(before "End Primitive Recipe Declarations") JUMP_UNLESS, :(before "End Primitive Recipe Numbers") @@ -132,7 +143,7 @@ case JUMP_UNLESS: { raise << maybe(get(Recipe, r).name) << "'" << to_original_string(inst) << "' should get exactly two ingredients\n" << end(); break; } - if (!is_mu_scalar(inst.ingredients.at(0))) { + if (!is_mu_address(inst.ingredients.at(0)) && !is_mu_scalar(inst.ingredients.at(0))) { raise << maybe(get(Recipe, r).name) << "'" << to_original_string(inst) << "' requires a boolean for its first ingredient, but '" << inst.ingredients.at(0).name << "' has type '" << names_to_string_without_quotes(inst.ingredients.at(0).type) << "'\n" << end(); break; } @@ -150,7 +161,7 @@ case JUMP_UNLESS: { :(before "End Primitive Recipe Implementations") case JUMP_UNLESS: { assert(current_instruction().ingredients.at(1).initialized); - if (ingredients.at(0).at(0)) { + if (scalar_ingredient(ingredients, 0)) { trace(9998, "run") << "jump-unless fell through" << end(); break; } diff --git a/025compare.cc b/025compare.cc index 92878208..c82f3578 100644 --- a/025compare.cc +++ b/025compare.cc @@ -29,6 +29,8 @@ case EQUAL: { } :(before "End Primitive Recipe Implementations") case EQUAL: { + // todo: keep the address exception from slowing down the common case + drop_alloc_ids_if_comparing_address_to_literal_0(ingredients); vector<double>& exemplar = ingredients.at(0); bool result = true; for (int i = /*skip exemplar*/1; i < SIZE(ingredients); ++i) { @@ -41,6 +43,23 @@ case EQUAL: { products.at(0).push_back(result); break; } +:(code) +void drop_alloc_ids_if_comparing_address_to_literal_0(vector<vector<double> >& ingredients) { + bool any_ingredient_is_null = false; + bool any_ingredient_is_address = false; + for (int i = 0; i < SIZE(current_instruction().ingredients); ++i) { + if (current_instruction().ingredients.at(i).name == "0") + any_ingredient_is_null = true; + if (is_mu_address(current_instruction().ingredients.at(i))) + any_ingredient_is_address = true; + } + if (any_ingredient_is_null && any_ingredient_is_address) { + for (int i = 0; i < SIZE(ingredients); ++i) { + if (is_mu_address(current_instruction().ingredients.at(i))) + ingredients.at(i).erase(ingredients.at(i).begin()); + } + } +} :(scenario equal) def main [ @@ -74,6 +93,42 @@ def main [ ] +mem: storing 0 in location 1 +:(scenario equal_address_null) +def main [ + 1:&:num <- copy 0 + 10:bool <- equal 1:&:num, 0 +] ++mem: storing 1 in location 10 + +:(scenario equal_address_null_2) +def main [ + 1:&:num <- copy 0 + 10:bool <- equal 0, 1:&:num +] ++mem: storing 1 in location 10 + +:(scenario equal_address_null_3) +def main [ + 1:&:num <- new num:type + 10:bool <- equal 1:&:num, 0 +] ++mem: storing 0 in location 10 + +:(scenario equal_address_null_multiple) +def main [ + 1:&:num <- copy 0 + 10:bool <- equal 0, 1:&:num, 0 +] ++mem: storing 1 in location 10 + +:(scenario equal_address_null_multiple_2) +def main [ + 1:&:num <- copy 0 + 3:&:num <- copy 0 + 10:bool <- equal 0, 1:&:num, 0, 3:&:num +] ++mem: storing 1 in location 10 + :(before "End Primitive Recipe Declarations") NOT_EQUAL, :(before "End Primitive Recipe Numbers") @@ -101,6 +156,8 @@ case NOT_EQUAL: { } :(before "End Primitive Recipe Implementations") case NOT_EQUAL: { + // todo: keep the address exception from slowing down the common case + drop_alloc_ids_if_comparing_address_to_literal_0(ingredients); vector<double>& exemplar = ingredients.at(0); products.resize(1); bool equal_ingredients = equal(ingredients.at(1).begin(), ingredients.at(1).end(), exemplar.begin()); diff --git a/027call_ingredient.cc b/027call_ingredient.cc index 46fafe7e..00e44ea3 100644 --- a/027call_ingredient.cc +++ b/027call_ingredient.cc @@ -68,6 +68,9 @@ case NEXT_INGREDIENT: { } products.push_back( current_call().ingredient_atoms.at(current_call().next_ingredient_to_process)); + if (is_mu_scalar(current_call().ingredients.at(current_call().next_ingredient_to_process)) + && is_mu_address(current_instruction().products.at(0))) + products.at(0).insert(products.at(0).begin(), /*alloc id*/0); assert(SIZE(products) == 1); products.resize(2); // push a new vector products.at(1).push_back(1); ++current_call().next_ingredient_to_process; @@ -94,6 +97,18 @@ def f [ ] +error: f: no ingredient to save in '11:num' +:(scenario pass_null_ingredient_for_address) +def main [ + f 0 +] +def f [ + 1:address:num <- next-ingredient +] ++mem: storing 0 in location 2 +$error: 0 + +//: another primitive: 'rewind-ingredients' to rescan ingredients from the start + :(scenario rewind_ingredients) def main [ f 2 @@ -124,6 +139,8 @@ case REWIND_INGREDIENTS: { break; } +//: another primitive: 'ingredient' for random access + :(scenario ingredient) def main [ f 1, 2 diff --git a/028call_return.cc b/028call_return.cc index c8c1bca6..aa5cb584 100644 --- a/028call_return.cc +++ b/028call_return.cc @@ -52,6 +52,10 @@ case RETURN: { trace(9998, "run") << "result " << i << " is " << to_string(ingredients.at(i)) << end(); // make return products available to caller copy(ingredients.begin(), ingredients.end(), inserter(products, products.begin())); + for (int i = 0; i < SIZE(current_instruction().products); ++i) { + if (is_mu_address(current_instruction().products.at(i)) && scalar(ingredients.at(i))) + products.at(i).insert(products.at(i).begin(), /*alloc id*/0); + } // End Return break; // continue to process rest of *caller* instruction } diff --git a/030container.cc b/030container.cc index f4aaafd4..b722e711 100644 --- a/030container.cc +++ b/030container.cc @@ -7,14 +7,16 @@ get_or_insert(Type, point); // initialize get(Type, point).kind = CONTAINER; get(Type, point).name = "point"; get(Type, point).elements.push_back(reagent("x:number")); +get(Type, point).elements.back().set_value(0); get(Type, point).elements.push_back(reagent("y:number")); +get(Type, point).elements.back().set_value(1); //: Containers can be copied around with a single instruction just like //: numbers, no matter how large they are. //: Tests in this layer often explicitly set up memory before reading it as a -//: container. Don't do this in general. I'm tagging such cases with /unsafe; -//: they'll be exceptions to later checks. +//: container. Don't do this in general. I'm tagging exceptions with /unsafe to +//: skip later checks. :(scenario copy_multiple_locations) def main [ 1:num <- copy 34 @@ -40,7 +42,9 @@ get_or_insert(Type, point_number); // initialize get(Type, point_number).kind = CONTAINER; get(Type, point_number).name = "point-number"; get(Type, point_number).elements.push_back(reagent("xy:point")); +get(Type, point_number).elements.back().set_value(0); get(Type, point_number).elements.push_back(reagent("z:number")); +get(Type, point_number).elements.back().set_value(1); :(scenario copy_handles_nested_container_elements) def main [ @@ -127,6 +131,10 @@ def main [ //: 'get' takes a 'base' container and an 'offset' into it and returns the //: appropriate element of the container value. +//: The offset is different from the distance (in memory locations) an element +//: is at from the start. This is because elements can occupy multiple memory +//: locations in the container. + :(scenario get) def main [ 12:num <- copy 34 @@ -195,9 +203,7 @@ case GET: { // Update GET base_type in Run int offset = ingredients.at(1).at(0); if (offset < 0 || offset >= SIZE(get(Type, base_type->value).elements)) break; // copied from Check above - int src = base_address; - for (int i = 0; i < offset; ++i) - src += size_of(element_type(base.type, i)); + int src = element_location(base_address, offset, base.type); trace(9998, "run") << "address to copy is " << src << end(); //: use base.type rather than base_type because later layers will introduce compound types reagent/*copy*/ element = element_type(base.type, offset); @@ -222,6 +228,12 @@ const reagent element_type(const type_tree* type, int offset_value) { // End element_type Special-cases return element; } +int element_location(int base_address, int offset, const type_tree* type) { + int result = base_address; + for (int i = 0; i < offset; ++i) + result += size_of(element_type(type, i)); + return result; +} :(scenario get_handles_nested_container_elements) def main [ @@ -352,14 +364,17 @@ case PUT: { // Update PUT base_type in Run int offset = ingredients.at(1).at(0); if (offset < 0 || offset >= SIZE(get(Type, base_type->value).elements)) break; // copied from Check above - int address = base_address; - for (int i = 0; i < offset; ++i) - address += size_of(element_type(base.type, i)); + int address = element_location(base_address, offset, base.type); trace(9998, "run") << "address to copy to is " << address << end(); // optimization: directly write the element rather than updating 'product' // and writing the entire container // Write Memory in PUT in Run write_products = false; + if (is_mu_address(element_type(base.type, offset)) && is_literal(current_instruction().ingredients.at(2)) && current_instruction().ingredients.at(2).name == "0") { + trace("mem") << "storing 0 in location " << address << end(); + put(Memory, address, /*alloc id*/0); + ++address; + } for (int i = 0; i < SIZE(ingredients.at(2)); ++i) { trace("mem") << "storing " << no_scientific(ingredients.at(2).at(i)) << " in location " << address+i << end(); put(Memory, address+i, ingredients.at(2).at(i)); @@ -377,6 +392,23 @@ def main [ ] +error: main: product of 'put' must be first ingredient '1:point', but got '3:point' +:(scenario put_null_address) +container foo [ + x:num + y:&:num + z:num +] +def main [ + 1:num <- copy 34 + 2:num <- copy 0 # alloc id + 3:num <- copy 1000 # pretend address + 4:num <- copy 36 + put 1:foo, y:offset, 0 +] ++run: put {1: "foo"}, {y: "offset"}, {0: "literal"} ++mem: storing 0 in location 2 ++mem: storing 0 in location 3 + //:: Allow containers to be defined in Mu code. :(scenarios load) @@ -490,6 +522,7 @@ void insert_container(const string& command, kind_of_type kind, istream& in) { break; } info.elements.push_back(reagent(element)); + info.elements.back().set_value(SIZE(info.elements)-1); expand_type_abbreviations(info.elements.back().type); // todo: use abbreviation before declaration replace_unknown_types_with_unique_ordinals(info.elements.back().type, info); trace(9993, "parse") << " element: " << to_string(info.elements.back()) << end(); diff --git a/032array.cc b/032array.cc index 3bde42fb..d893b529 100644 --- a/032array.cc +++ b/032array.cc @@ -11,7 +11,7 @@ def main [ # create an array occupying locations 1 (for the size) and 2-4 (for the elements) 1:array:num:3 <- create-array ] -+run: creating array of size 4 ++run: creating array from 4 locations :(before "End Primitive Recipe Declarations") CREATE_ARRAY, @@ -60,7 +60,7 @@ case CREATE_ARRAY: { trace("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 - trace(9998, "run") << "creating array of size " << size << end(); + trace(9998, "run") << "creating array from " << size << " locations" << end(); // initialize array for (int i = 1; i <= size_of(product); ++i) put(Memory, base_address+i, 0); @@ -208,19 +208,23 @@ def main [ 2:num <- copy 14 3:num <- copy 15 4:num <- copy 16 - 5:num <- index 1:array:num:3, 0/index # the index must be a non-negative whole number + 10:num <- index 1:array:num:3, 0/index # the index must be a non-negative whole number ] -+mem: storing 14 in location 5 ++mem: storing 14 in location 10 :(scenario index_compound_element) def main [ {1: (array (address number) 3)} <- create-array - 2:num <- copy 14 - 3:num <- copy 15 - 4:num <- copy 16 - 5:address:num <- index {1: (array (address number) 3)}, 0 + # skip alloc id + 3:num <- copy 14 + # skip alloc id + 5:num <- copy 15 + # skip alloc id + 7:num <- copy 16 + 10:address:num <- index {1: (array (address number) 3)}, 0 ] -+mem: storing 14 in location 5 +# skip alloc id ++mem: storing 14 in location 11 :(scenario index_direct_offset) def main [ @@ -228,10 +232,10 @@ def main [ 2:num <- copy 14 3:num <- copy 15 4:num <- copy 16 - 5:num <- copy 0 - 6:num <- index 1:array:num, 5:num + 10:num <- copy 0 + 20:num <- index 1:array:num, 10:num ] -+mem: storing 14 in location 6 ++mem: storing 14 in location 20 :(before "End Primitive Recipe Declarations") INDEX, @@ -346,38 +350,38 @@ def main [ 2:num <- copy 14 3:num <- copy 15 4:num <- copy 16 - 5:num <- index 1:array:num:3, 1.5 # non-whole number + 10:num <- index 1:array:num:3, 1.5 # non-whole number ] # fraction is truncated away -+mem: storing 15 in location 5 ++mem: storing 15 in location 10 :(scenario index_out_of_bounds) % Hide_errors = true; def main [ - 1:array:num:3 <- create-array - 2:num <- copy 14 - 3:num <- copy 15 - 4:num <- copy 16 - 5:num <- copy 14 - 6:num <- copy 15 - 7:num <- copy 16 - index 1:array:num:3, 4 # less than size of array in locations, but larger than its length in elements + 2:array:point:3 <- create-array + 3:num <- copy 14 + 4:num <- copy 15 + 5:num <- copy 16 + 6:num <- copy 17 + 7:num <- copy 18 + 8:num <- copy 19 + index 1:array:point:3/skip-alloc-id, 4 # less than size of array in locations, but larger than its length in elements ] -+error: main: invalid index 4 in 'index 1:array:num:3, 4' ++error: main: invalid index 4 in 'index 1:array:point:3/skip-alloc-id, 4' :(scenario index_out_of_bounds_2) % Hide_errors = true; def main [ - 1:array:point:3 <- create-array - 2:num <- copy 14 - 3:num <- copy 15 - 4:num <- copy 16 - 5:num <- copy 14 - 6:num <- copy 15 - 7:num <- copy 16 - index 1:array:point, -1 + 2:array:point:3 <- create-array + 3:num <- copy 14 + 4:num <- copy 15 + 5:num <- copy 16 + 6:num <- copy 14 + 7:num <- copy 15 + 8:num <- copy 16 + index 1:array:point/skip-alloc-id, -1 ] -+error: main: invalid index -1 in 'index 1:array:point, -1' ++error: main: invalid index -1 in 'index 1:array:point/skip-alloc-id, -1' :(scenario index_product_type_mismatch) % Hide_errors = true; diff --git a/033exclusive_container.cc b/033exclusive_container.cc index 44161d7c..e4fa6398 100644 --- a/033exclusive_container.cc +++ b/033exclusive_container.cc @@ -12,12 +12,14 @@ get_or_insert(Type, tmp); // initialize get(Type, tmp).kind = EXCLUSIVE_CONTAINER; get(Type, tmp).name = "number-or-point"; get(Type, tmp).elements.push_back(reagent("i:number")); +get(Type, tmp).elements.back().set_value(0); get(Type, tmp).elements.push_back(reagent("p:point")); +get(Type, tmp).elements.back().set_value(1); } -//: Tests in this layer often explicitly set up memory before reading it as a -//: container. Don't do this in general. I'm tagging such cases with /unsafe; -//: they'll be exceptions to later checks. +//: Tests in this layer often explicitly set up memory before reading it as an +//: array. Don't do this in general. I'm tagging exceptions with /raw to keep +//: checks in future layers from flagging them. :(scenario copy_exclusive_container) # Copying exclusive containers copies all their contents and an extra location for the tag. def main [ diff --git a/034address.cc b/034address.cc index bce51b2e..94c930bd 100644 --- a/034address.cc +++ b/034address.cc @@ -18,6 +18,37 @@ //: write to the payload of an ingredient rather than its value, simply add //: the /lookup property to it. Modern computers provide efficient support for //: addresses and lookups, making this a realistic feature. +//: +//: To create addresses and allocate memory exclusively for their use, use +//: 'new'. Memory is a finite resource so if the computer can't satisfy your +//: request, 'new' may return a 0 (null) address. +//: +//: Computers these days have lots of memory so in practice we can often +//: assume we'll never run out. If you start running out however, say in a +//: long-running program, you'll need to switch mental gears and start +//: husbanding our memory more carefully. The most important tool to avoid +//: wasting memory is to 'abandon' an address when you don't need it anymore. +//: That frees up the memory allocated to it to be reused in future calls to +//: 'new'. + +//: Since memory can be reused multiple times, it can happen that you have a +//: stale copy to an address that has since been abandoned and reused. Using +//: the stale address is almost never safe, but it can be very hard to track +//: down such copies because any errors caused by them may occur even millions +//: of instructions after the copy or abandon instruction. To help track down +//: such issues, Mu tracks an 'alloc id' for each allocation it makes. The +//: first call to 'new' has an alloc id of 1, the second gets 2, and so on. +//: The alloc id is never reused. +:(before "End Globals") +long long Next_alloc_id = 0; +:(before "End Reset") +Next_alloc_id = 0; + +//: The 'new' instruction records alloc ids both in the memory being allocated +//: and *also* in the address. The 'abandon' instruction clears alloc ids in +//: both places as well. Tracking alloc ids in this manner allows us to raise +//: errors about stale addresses much earlier: 'lookup' operations always +//: compare alloc ids between the address and its payload. //: todo: give 'new' a custodian ingredient. Following malloc/free is a temporary hack. @@ -26,28 +57,30 @@ # should get back different results def main [ 1:address:num/raw <- new number:type - 2:address:num/raw <- new number:type - 3:bool/raw <- equal 1:address:num/raw, 2:address:num/raw + 3:address:num/raw <- new number:type + 5:bool/raw <- equal 1:address:num/raw, 3:address:num/raw ] ++mem: storing 1000 in location 2 +mem: storing 0 in location 3 :(scenario new_array) # call 'new' with a second ingredient to allocate an array of some type rather than a single copy def main [ 1:address:array:num/raw <- new number:type, 5 - 2:address:num/raw <- new number:type - 3:num/raw <- subtract 2:address:num/raw, 1:address:array:num/raw + 3:address:num/raw <- new number:type + 5:num/raw <- subtract 3:address:num/raw, 1:address:array:num/raw ] +run: {1: ("address" "array" "number"), "raw": ()} <- new {number: "type"}, {5: "literal"} +mem: array length is 5 ++mem: storing 1000 in location 2 # don't forget the extra location for array length -+mem: storing 6 in location 3 ++mem: storing 7 in location 5 :(scenario dilated_reagent_with_new) def main [ 1:address:address:num <- new {(address number): type} ] -+new: size of '(address number)' is 1 ++new: size of '(address number)' is 2 //: 'new' takes a weird 'type' as its first ingredient; don't error on it :(before "End Mu Types Initialization") @@ -151,6 +184,13 @@ def main [ ] $error: 0 +:(scenario equal_result_of_new_with_null) +def main [ + 1:&:num <- new num:type + 10:bool <- equal 1:&:num, 0 +] ++mem: storing 0 in location 10 + //: To implement 'new', a Mu transform turns all 'new' instructions into //: 'allocate' instructions that precompute the amount of memory they want to //: allocate. @@ -221,15 +261,18 @@ case ALLOCATE: { int result = allocate(size); if (SIZE(current_instruction().ingredients) > 1) { // initialize array length - trace("mem") << "storing " << ingredients.at(1).at(0) << " in location " << result << end(); - put(Memory, result, ingredients.at(1).at(0)); + trace("mem") << "storing array length " << ingredients.at(1).at(0) << " in location " << result+/*skip alloc id*/1 << end(); + put(Memory, result+/*skip alloc id*/1, ingredients.at(1).at(0)); } products.resize(1); + products.at(0).push_back(0); products.at(0).push_back(result); break; } :(code) int allocate(int size) { + // include space for alloc id + ++size; trace("mem") << "allocating size " << size << end(); //? Total_alloc += size; //? ++Num_alloc; @@ -290,41 +333,41 @@ def main [ :(scenario new_size) def main [ 11:address:num/raw <- new number:type - 12:address:num/raw <- new number:type - 13:num/raw <- subtract 12:address:num/raw, 11:address:num/raw + 13:address:num/raw <- new number:type + 15:num/raw <- subtract 13:address:num/raw, 11:address:num/raw ] -# size of number -+mem: storing 1 in location 13 +# size of number + alloc id ++mem: storing 2 in location 15 :(scenario new_array_size) def main [ 1:address:array:num/raw <- new number:type, 5 - 2:address:num/raw <- new number:type - 3:num/raw <- subtract 2:address:num/raw, 1:address:array:num/raw + 3:address:num/raw <- new number:type + 5:num/raw <- subtract 3:address:num/raw, 1:address:array:num/raw ] # 5 locations for array contents + array length -+mem: storing 6 in location 3 ++mem: storing 7 in location 5 :(scenario new_empty_array) def main [ 1:address:array:num/raw <- new number:type, 0 - 2:address:num/raw <- new number:type - 3:num/raw <- subtract 2:address:num/raw, 1:address:array:num/raw + 3:address:num/raw <- new number:type + 5:num/raw <- subtract 3:address:num/raw, 1:address:array:num/raw ] +run: {1: ("address" "array" "number"), "raw": ()} <- new {number: "type"}, {0: "literal"} +mem: array length is 0 # one location for array length -+mem: storing 1 in location 3 ++mem: storing 2 in location 5 //: If a routine runs out of its initial allocation, it should allocate more. :(scenario new_overflow) -% Initial_memory_per_routine = 2; // barely enough room for point allocation below +% Initial_memory_per_routine = 3; // barely enough room for point allocation below def main [ 1:address:num/raw <- new number:type 2:address:point/raw <- new point:type # not enough room in initial page ] -+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 :(scenario new_without_ingredient) % Hide_errors = true; diff --git a/035lookup.cc b/035lookup.cc index a2647f5d..9708ce5b 100644 --- a/035lookup.cc +++ b/035lookup.cc @@ -6,7 +6,8 @@ :(scenario copy_indirect) def main [ 1:address:num <- copy 10/unsafe - 10:num <- copy 34 + # skip alloc id + 11:num <- copy 34 # This loads location 1 as an address and looks up *that* location. 2:num <- copy 1:address:num/lookup ] @@ -22,7 +23,7 @@ def main [ 1:address:num <- copy 10/unsafe 1:address:num/lookup <- copy 34 ] -+mem: storing 34 in location 10 ++mem: storing 34 in location 11 :(before "End Preprocess write_memory(x, data)") canonize(x); @@ -35,7 +36,7 @@ def main [ 1:address:num/lookup <- copy 34 ] -mem: storing 34 in location 0 -+error: can't write to location 0 in '1:address:num/lookup <- copy 34' ++error: main: tried to lookup 0 in '1:address:num/lookup <- copy 34' //: attempts to /lookup address 0 always loudly fail :(scenario lookup_0_fails) @@ -82,7 +83,7 @@ void lookup_memory(reagent& x) { } void lookup_memory_core(reagent& x, bool check_for_null) { - double address = x.value; + double address = x.value + /*skip alloc id in address*/1; double new_value = get_or_insert(Memory, address); trace("mem") << "location " << address << " contains " << no_scientific(new_value) << end(); if (check_for_null && new_value == 0) { @@ -94,12 +95,18 @@ void lookup_memory_core(reagent& x, bool check_for_null) { raise << "tried to lookup 0\n" << end(); } } - x.set_value(new_value); + x.set_value(new_value+/*skip alloc id in payload*/1); drop_from_type(x, "address"); drop_one_lookup(x); } -:(before "End Preprocess types_strictly_match(reagent to, reagent from)") +:(after "Begin types_coercible(reagent to, reagent from)") +if (!canonize_type(to)) return false; +if (!canonize_type(from)) return false; +:(after "Begin types_match(reagent to, reagent from)") +if (!canonize_type(to)) return false; +if (!canonize_type(from)) return false; +:(after "Begin types_strictly_match(reagent to, reagent from)") if (!canonize_type(to)) return false; if (!canonize_type(from)) return false; @@ -157,30 +164,33 @@ void drop_one_lookup(reagent& r) { :(scenario get_indirect) def main [ 1:address:point <- copy 10/unsafe - 10:num <- copy 34 - 11:num <- copy 35 - 2:num <- get 1:address:point/lookup, 0:offset + # skip alloc id + 11:num <- copy 34 + 12:num <- copy 35 + 20:num <- get 1:address:point/lookup, 0:offset ] -+mem: storing 34 in location 2 ++mem: storing 34 in location 20 :(scenario get_indirect2) def main [ 1:address:point <- copy 10/unsafe - 10:num <- copy 34 - 11:num <- copy 35 - 2:address:num <- copy 20/unsafe - 2:address:num/lookup <- get 1:address:point/lookup, 0:offset + # skip alloc id + 11:num <- copy 94 + 12:num <- copy 95 + 20:address:num <- copy 30/unsafe + 20:address:num/lookup <- get 1:address:point/lookup, 0:offset ] -+mem: storing 34 in location 20 ++mem: storing 94 in location 31 :(scenario include_nonlookup_properties) def main [ 1:address:point <- copy 10/unsafe - 10:num <- copy 34 - 11:num <- copy 35 - 2:num <- get 1:address:point/lookup/foo, 0:offset + # skip alloc id + 11:num <- copy 34 + 12:num <- copy 35 + 20:num <- get 1:address:point/lookup/foo, 0:offset ] -+mem: storing 34 in location 2 ++mem: storing 34 in location 20 :(after "Update GET base in Check") if (!canonize_type(base)) break; @@ -192,11 +202,12 @@ canonize(base); :(scenario put_indirect) def main [ 1:address:point <- copy 10/unsafe - 10:num <- copy 34 - 11:num <- copy 35 + # skip alloc id + 11:num <- copy 34 + 12:num <- copy 35 1:address:point/lookup <- put 1:address:point/lookup, 0:offset, 36 ] -+mem: storing 36 in location 10 ++mem: storing 36 in location 11 :(after "Update PUT base in Check") if (!canonize_type(base)) break; @@ -241,7 +252,7 @@ def main [ 11:num <- copy 14 12:num <- copy 15 13:num <- copy 16 - 1:address:array:num <- copy 10/unsafe + 1:address:array:num <- copy 9/unsafe/skip-alloc-id 2:array:num <- copy 1:address:array:num/lookup ] +mem: storing 3 in location 2 @@ -254,7 +265,7 @@ def main [ 1:address:array:num:3 <- copy 1000/unsafe # pretend allocation 1:address:array:num:3/lookup <- create-array ] -+mem: storing 3 in location 1000 ++mem: storing 3 in location 1001 :(after "Update CREATE_ARRAY product in Check") if (!canonize_type(product)) break; @@ -267,7 +278,7 @@ def main [ 11:num <- copy 14 12:num <- copy 15 13:num <- copy 16 - 1:address:array:num <- copy 10/unsafe + 1:address:array:num <- copy 9/unsafe/skip-alloc-id 2:num <- index 1:address:array:num/lookup, 1 ] +mem: storing 15 in location 2 @@ -290,7 +301,7 @@ def main [ 11:num <- copy 14 12:num <- copy 15 13:num <- copy 16 - 1:address:array:num <- copy 10/unsafe + 1:address:array:num <- copy 9/unsafe/skip-alloc-id 1:address:array:num/lookup <- put-index 1:address:array:num/lookup, 1, 34 ] +mem: storing 34 in location 12 @@ -301,7 +312,7 @@ def main [ 2:num <- copy 14 3:num <- copy 15 4:num <- copy 16 - 5:address:num <- copy 10/unsafe + 5:address:num <- copy 9/unsafe/skip-alloc-id 10:num <- copy 1 1:array:num:3 <- put-index 1:array:num:3, 5:address:num/lookup, 34 ] @@ -314,7 +325,7 @@ def main [ 11:num <- copy 14 12:num <- copy 15 13:num <- copy 16 - 1:address:array:num <- copy 10/unsafe + 1:address:array:num <- copy 9/unsafe/skip-alloc-id 1:address:array:num <- put-index 1:address:array:num/lookup, 1, 34 ] +error: main: product of 'put-index' must be first ingredient '1:address:array:num/lookup', but got '1:address:array:num' @@ -337,7 +348,7 @@ def main [ *5:address:num <- copy 34 6:num <- copy *5:address:num ] -+run: creating array of size 4 ++run: creating array from 7 locations +mem: storing 34 in location 6 :(before "Update PUT_INDEX base in Check") @@ -358,7 +369,7 @@ def main [ 11:num <- copy 14 12:num <- copy 15 13:num <- copy 16 - 1:address:array:num <- copy 10/unsafe + 1:address:array:num <- copy 9/unsafe/skip-alloc-id 2:num <- length 1:address:array:num/lookup ] +mem: storing 3 in location 2 @@ -370,8 +381,8 @@ canonize(array); :(scenario maybe_convert_indirect) def main [ - 10:number-or-point <- merge 0/number, 34 - 1:address:number-or-point <- copy 10/unsafe + 11:number-or-point <- merge 0/number, 34 + 1:address:number-or-point <- copy 10/unsafe/skip-alloc-id 2:num, 3:bool <- maybe-convert 1:address:number-or-point/lookup, i:variant ] +mem: storing 1 in location 3 @@ -379,23 +390,23 @@ def main [ :(scenario maybe_convert_indirect_2) def main [ - 10:number-or-point <- merge 0/number, 34 - 1:address:number-or-point <- copy 10/unsafe - 2:address:num <- copy 20/unsafe - 2:address:num/lookup, 3:bool <- maybe-convert 1:address:number-or-point/lookup, i:variant + 11:number-or-point <- merge 0/number, 34 + 1:address:number-or-point <- copy 10/unsafe/skip-alloc-id + 3:address:num <- copy 20/unsafe + 3:address:num/lookup, 5:bool <- maybe-convert 1:address:number-or-point/lookup, i:variant ] -+mem: storing 1 in location 3 -+mem: storing 34 in location 20 ++mem: storing 1 in location 5 ++mem: storing 34 in location 21 :(scenario maybe_convert_indirect_3) def main [ - 10:number-or-point <- merge 0/number, 34 - 1:address:number-or-point <- copy 10/unsafe - 2:address:bool <- copy 20/unsafe - 3:num, 2:address:bool/lookup <- maybe-convert 1:address:number-or-point/lookup, i:variant + 11:number-or-point <- merge 0/number, 34 + 1:address:number-or-point <- copy 10/unsafe/skip-alloc-id + 3:address:bool <- copy 20/unsafe + 5:num, 3:address:bool/lookup <- maybe-convert 1:address:number-or-point/lookup, i:variant ] -+mem: storing 1 in location 20 -+mem: storing 34 in location 3 ++mem: storing 1 in location 21 ++mem: storing 34 in location 5 :(before "Update MAYBE_CONVERT base in Check") if (!canonize_type(base)) break; @@ -416,8 +427,9 @@ def main [ 1:address:number-or-point <- copy 10/unsafe 1:address:number-or-point/lookup <- merge 0/number, 34 ] -+mem: storing 0 in location 10 -+mem: storing 34 in location 11 +# skip alloc id ++mem: storing 0 in location 11 ++mem: storing 34 in location 12 :(before "Update size_mismatch Check for MERGE(x) canonize(x); @@ -427,7 +439,7 @@ canonize(x); :(scenario lookup_abbreviation) def main [ 1:address:number <- copy 10/unsafe - 10:number <- copy 34 + 11:number <- copy 34 3:number <- copy *1:address:number ] +parse: ingredient: {1: ("address" "number"), "lookup": ()} diff --git a/037abandon.cc b/037abandon.cc index 5a4adbd1..1c2bc395 100644 --- a/037abandon.cc +++ b/037abandon.cc @@ -3,14 +3,14 @@ :(scenario new_reclaim) def main [ 1:address:num <- new number:type - 2:num <- copy 1:address:num # because 1 will get reset during abandon below + 3:num <- copy 1:address:num # because 1 will get reset during abandon below abandon 1:address:num - 3:address:num <- new number:type # must be same size as abandoned memory to reuse - 4:num <- copy 3:address:num - 5:bool <- equal 2:num, 4:num + 4:address:num <- new number:type # must be same size as abandoned memory to reuse + 6:num <- copy 4:address:num + 7:bool <- equal 3:num, 6:num ] # both allocations should have returned the same address -+mem: storing 1 in location 5 ++mem: storing 1 in location 7 //: When abandoning addresses we'll save them to a 'free list', segregated by size. @@ -39,7 +39,7 @@ case ABANDON: { for (int i = 0; i < SIZE(current_instruction().ingredients); ++i) { reagent/*copy*/ ingredient = current_instruction().ingredients.at(i); canonize(ingredient); - abandon(get_or_insert(Memory, ingredient.value), payload_size(ingredient)); + abandon(get_or_insert(Memory, ingredient.value+/*skip alloc id*/1), payload_size(ingredient)); } break; } @@ -50,7 +50,7 @@ void abandon(int address, int payload_size) { for (int curr = address; curr < address+payload_size; ++curr) put(Memory, curr, 0); // append existing free list to address - trace("abandon") << "saving " << address << " in free-list of size " << payload_size << end(); + trace("mem") << "saving " << address << " in free-list of size " << payload_size << end(); put(Memory, address, get_or_insert(Current_routine->free_list, payload_size)); put(Current_routine->free_list, payload_size, address); } @@ -58,7 +58,7 @@ void abandon(int address, int payload_size) { int payload_size(reagent/*copy*/ x) { x.properties.push_back(pair<string, string_tree*>("lookup", NULL)); lookup_memory_core(x, /*check_for_null*/false); - return size_of(x); + return size_of(x) + /*alloc id*/1; } :(after "Allocate Special-cases") @@ -80,23 +80,23 @@ if (get_or_insert(Current_routine->free_list, size)) { :(scenario new_differing_size_no_reclaim) def main [ 1:address:num <- new number:type - 2:num <- copy 1:address:num + 3:num <- copy 1:address:num abandon 1:address:num - 3:address:array:num <- new number:type, 2 # different size - 4:num <- copy 3:address:array:num - 5:bool <- equal 2:num, 4:num + 4:address:array:num <- new number:type, 2 # different size + 6:num <- copy 4:address:array:num + 7:bool <- equal 3:num, 6:num ] # no reuse -+mem: storing 0 in location 5 ++mem: storing 0 in location 7 :(scenario new_reclaim_array) def main [ 1:address:array:num <- new number:type, 2 - 2:num <- copy 1:address:array:num + 3:num <- copy 1:address:array:num abandon 1:address:array:num - 3:address:array:num <- new number:type, 2 # same size - 4:num <- copy 3:address:array:num - 5:bool <- equal 2:num, 4:num + 4:address:array:num <- new number:type, 2 # same size + 6:num <- copy 4:address:array:num + 7:bool <- equal 3:num, 6:num ] # both calls to new returned identical addresses -+mem: storing 1 in location 5 ++mem: storing 1 in location 7 diff --git a/038new_text.cc b/038new_text.cc index 4b666f1c..b2a5db75 100644 --- a/038new_text.cc +++ b/038new_text.cc @@ -4,23 +4,25 @@ :(before "End Mu Types Initialization") put(Type_abbreviations, "text", new_type_tree("address:array:character")); -:(scenario new_string) +:(scenario new_text) def main [ 1:text <- new [abc def] - 2:char <- index *1:text, 5 + # location 2 is part of 1:text + 3:char <- index *1:text, 5 ] # number code for 'e' -+mem: storing 101 in location 2 ++mem: storing 101 in location 3 -:(scenario new_string_handles_unicode) +:(scenario new_text_handles_unicode) def main [ 1:text <- new [a«c] - 2:num <- length *1:text - 3:char <- index *1:text, 1 + # location 2 is part of 1:text + 3:num <- length *1:text + 4:char <- index *1:text, 1 ] -+mem: storing 3 in location 2 ++mem: storing 3 in location 3 # unicode for '«' -+mem: storing 171 in location 3 ++mem: storing 171 in location 4 :(before "End NEW Check Special-cases") if (is_literal_text(inst.ingredients.at(0))) break; @@ -29,6 +31,7 @@ if (inst.name == "new" && !inst.ingredients.empty() && is_literal_text(inst.ingr :(after "case NEW" following "Primitive Recipe Implementations") if (is_literal_text(current_instruction().ingredients.at(0))) { products.resize(1); + products.at(0).push_back(/*alloc id*/0); products.at(0).push_back(new_mu_text(current_instruction().ingredients.at(0).name)); trace("mem") << "new string alloc: " << products.at(0).at(0) << end(); break; @@ -40,8 +43,9 @@ int new_mu_text(const string& contents) { int string_length = unicode_length(contents); //? Total_alloc += string_length+1; //? ++Num_alloc; - int result = allocate(string_length+/*array length*/1); + int result = allocate(/*array length*/1 + string_length); int curr_address = result; + ++curr_address; // skip alloc id trace("mem") << "storing string length " << string_length << " in location " << curr_address << end(); put(Memory, curr_address, string_length); ++curr_address; // skip length @@ -62,16 +66,16 @@ int new_mu_text(const string& contents) { //: a new kind of typo -:(scenario string_literal_without_instruction) +:(scenario literal_text_without_instruction) % Hide_errors = true; def main [ [abc] ] +error: main: instruction '[abc]' has no recipe in '[abc]' -//: stash recognizes strings +//: stash recognizes texts -:(scenario stash_string) +:(scenario stash_text) def main [ 1:text <- new [abc] stash [foo:], 1:text @@ -80,30 +84,29 @@ def main [ :(before "End inspect Special-cases(r, data)") if (is_mu_text(r)) { - assert(scalar(data)); - return read_mu_text(data.at(0)); + return read_mu_text(data.at(/*skip alloc id*/1)); } :(before "End $print Special-cases") else if (is_mu_text(current_instruction().ingredients.at(i))) { - cout << read_mu_text(ingredients.at(i).at(0)); + cout << read_mu_text(ingredients.at(i).at(/*skip alloc id*/1)); } -:(scenario unicode_string) +:(scenario unicode_text) def main [ 1:text <- new [♠] stash [foo:], 1:text ] +app: foo: ♠ -:(scenario stash_space_after_string) +:(scenario stash_space_after_text) def main [ 1:text <- new [abc] stash 1:text, [foo] ] +app: abc foo -:(scenario stash_string_as_array) +:(scenario stash_text_as_array) def main [ 1:text <- new [abc] stash *1:text @@ -114,15 +117,16 @@ def main [ :(before "End Preprocess is_mu_text(reagent x)") if (!canonize_type(x)) return false; -//: Allocate more to routine when initializing a literal string -:(scenario new_string_overflow) -% Initial_memory_per_routine = 2; +//: Allocate more to routine when initializing a literal text +:(scenario new_text_overflow) +% Initial_memory_per_routine = 3; def main [ 1:address:num/raw <- new number:type - 2:text/raw <- new [a] # not enough room in initial page, if you take the array length into account + # location 2 is part of 1:address + 3:text/raw <- new [a] # not enough room in initial page, if you take the 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) @@ -140,9 +144,9 @@ int unicode_length(const string& s) { string read_mu_text(int address) { if (address == 0) return ""; - int length = get_or_insert(Memory, address); + int length = get_or_insert(Memory, address+/*alloc id*/1); if (length == 0) return ""; - return read_mu_characters(address+1, length); + return read_mu_characters(address+/*alloc id*/1+/*length*/1, length); } string read_mu_characters(int start, int length) { @@ -156,13 +160,21 @@ string read_mu_characters(int start, int length) { //: assert: perform sanity checks at runtime -:(scenario assert) +:(scenario assert_literal) % Hide_errors = true; // '%' lines insert arbitrary C code into tests before calling 'run' with the lines below. Must be immediately after :(scenario) line. def main [ assert 0, [this is an assert in Mu] ] +error: this is an assert in Mu +:(scenario assert) +% Hide_errors = true; // '%' lines insert arbitrary C code into tests before calling 'run' with the lines below. Must be immediately after :(scenario) line. +def main [ + 1:text <- new [this is an assert in Mu] + assert 0, 1:text +] ++error: this is an assert in Mu + :(before "End Primitive Recipe Declarations") ASSERT, :(before "End Primitive Recipe Numbers") @@ -173,7 +185,7 @@ case ASSERT: { raise << maybe(get(Recipe, r).name) << "'assert' takes exactly two ingredients rather than '" << to_original_string(inst) << "'\n" << end(); break; } - if (!is_mu_scalar(inst.ingredients.at(0))) { + if (!is_mu_address(inst.ingredients.at(0)) && !is_mu_scalar(inst.ingredients.at(0))) { raise << maybe(get(Recipe, r).name) << "'assert' requires a boolean for its first ingredient, but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); break; } @@ -185,11 +197,11 @@ case ASSERT: { } :(before "End Primitive Recipe Implementations") case ASSERT: { - if (!ingredients.at(0).at(0)) { + if (!scalar_ingredient(ingredients, 0)) { if (is_literal_text(current_instruction().ingredients.at(1))) raise << current_instruction().ingredients.at(1).name << '\n' << end(); else - raise << read_mu_text(ingredients.at(1).at(0)) << '\n' << end(); + raise << read_mu_text(ingredients.at(1).at(/*skip alloc id*/1)) << '\n' << end(); if (!Hide_errors) exit(1); } break; diff --git a/042name.cc b/042name.cc index f183962c..e1f35136 100644 --- a/042name.cc +++ b/042name.cc @@ -6,8 +6,8 @@ def main [ x:num <- copy 0 ] -+name: assign x 1 -+mem: storing 0 in location 1 ++name: assign x 2 ++mem: storing 0 in location 2 :(scenarios transform) :(scenario transform_names_fails_on_use_before_define) @@ -42,7 +42,7 @@ void transform_names(const recipe_ordinal r) { map<string, int>& names = Name[r]; // store the indices 'used' so far in the map int& curr_idx = names[""]; - ++curr_idx; // avoid using index 0, benign skip in some other cases + curr_idx = 2; // reserve indices 0 and 1 for the chaining slot in a later layer for (int i = 0; i < SIZE(caller.steps); ++i) { instruction& inst = caller.steps.at(i); // End transform_names(inst) Special-cases @@ -135,13 +135,21 @@ bool is_compound_type_starting_with(const type_tree* type, const string& expecte return type->left->value == get(Type_ordinal, expected_name); } -int find_element_name(const type_ordinal t, const string& name, const string& recipe_name) { +int find_element_offset(const type_ordinal t, const string& name, const string& recipe_name) { const type_info& container = get(Type, t); for (int i = 0; i < SIZE(container.elements); ++i) if (container.elements.at(i).name == name) return i; raise << maybe(recipe_name) << "unknown element '" << name << "' in container '" << get(Type, t).name << "'\n" << end(); return -1; } +int find_element_location(int base_address, const string& name, const type_tree* type, const string& recipe_name) { + int offset = find_element_offset(get_base_type(type)->value, name, recipe_name); + if (offset == -1) return offset; + int result = base_address; + for (int i = 0; i < offset; ++i) + result += size_of(element_type(type, i)); + return result; +} bool is_numeric_location(const reagent& x) { if (is_literal(x)) return false; @@ -170,26 +178,26 @@ def main [ x:point <- merge 34, 35 y:num <- copy 3 ] -+name: assign x 1 ++name: assign x 2 # skip location 2 because x occupies two locations -+name: assign y 3 ++name: assign y 4 :(scenario transform_names_supports_static_arrays) def main [ x:@:num:3 <- create-array y:num <- copy 3 ] -+name: assign x 1 ++name: assign x 2 # skip locations 2, 3, 4 because x occupies four locations -+name: assign y 5 ++name: assign y 6 :(scenario transform_names_passes_dummy) # _ is just a dummy result that never gets consumed def main [ _, x:num <- copy 0, 1 ] -+name: assign x 1 --name: assign _ 1 ++name: assign x 2 +-name: assign _ 2 //: an escape hatch to suppress name conversion that we'll use later :(scenarios run) @@ -198,7 +206,7 @@ def main [ def main [ x:num/raw <- copy 0 ] --name: assign x 1 +-name: assign x 2 +error: can't write to location 0 in 'x:num/raw <- copy 0' :(scenarios transform) @@ -266,7 +274,7 @@ if (inst.name == "get" || inst.name == "get-location" || inst.name == "put") { // since first non-address in base type must be a container, we don't have to canonize type_ordinal base_type = skip_addresses(inst.ingredients.at(0).type); if (contains_key(Type, base_type)) { // otherwise we'll raise an error elsewhere - inst.ingredients.at(1).set_value(find_element_name(base_type, inst.ingredients.at(1).name, get(Recipe, r).name)); + inst.ingredients.at(1).set_value(find_element_offset(base_type, inst.ingredients.at(1).name, get(Recipe, r).name)); trace(9993, "name") << "element " << inst.ingredients.at(1).name << " of type " << get(Type, base_type).name << " is at offset " << no_scientific(inst.ingredients.at(1).value) << end(); } } @@ -286,8 +294,8 @@ def main [ a:point <- copy 0/unsafe b:num <- copy 0/unsafe ] -+name: assign a 1 -+name: assign b 3 ++name: assign a 2 ++name: assign b 4 //:: Support variant names for exclusive containers in 'maybe-convert'. @@ -316,7 +324,7 @@ if (inst.name == "maybe-convert") { // since first non-address in base type must be an exclusive container, we don't have to canonize type_ordinal base_type = skip_addresses(inst.ingredients.at(0).type); if (contains_key(Type, base_type)) { // otherwise we'll raise an error elsewhere - inst.ingredients.at(1).set_value(find_element_name(base_type, inst.ingredients.at(1).name, get(Recipe, r).name)); + inst.ingredients.at(1).set_value(find_element_offset(base_type, inst.ingredients.at(1).name, get(Recipe, r).name)); trace(9993, "name") << "variant " << inst.ingredients.at(1).name << " of type " << get(Type, base_type).name << " has tag " << no_scientific(inst.ingredients.at(1).value) << end(); } } diff --git a/043space.cc b/043space.cc index f290a0b9..e62e9285 100644 --- a/043space.cc +++ b/043space.cc @@ -13,25 +13,31 @@ put(Type_abbreviations, "space", new_type_tree("address:array:location")); :(scenario set_default_space) -# if default-space is 10, and if an array of 5 locals lies from location 12 to 16 (inclusive), -# then local 0 is really location 12, local 1 is really location 13, and so on. +# if default-space is 10, then: +# 10: alloc id +# 11: array size +# 12: local 0 (space for the chaining slot; described later; often unused) +# 13: local 0 (space for the chaining slot; described later; often unused) +# 14: local 2 (assuming it is a scalar) +# 15: local 3 +# ..and so on def main [ # pretend address:array:location; in practice we'll use 'new' - 10:num <- copy 5 # length - default-space:space <- copy 10/unsafe - 1:num <- copy 23 + 11:num <- copy 5 # length + default-space:space <- copy 10/unsafe/skip-alloc-id + 2:num <- copy 23 ] -+mem: storing 23 in location 12 ++mem: storing 23 in location 14 :(scenario lookup_sidesteps_default_space) def main [ # pretend pointer from outside - 2000:num <- copy 34 + 2001:num <- copy 34 # pretend address:array:location; in practice we'll use 'new' - 1000:num <- copy 5 # length + 1001:num <- copy 5 # length # actual start of this recipe - default-space:space <- copy 1000/unsafe - 1:&:num <- copy 2000/unsafe # even local variables always contain raw addresses + default-space:space <- copy 1000/unsafe/skip-alloc-id + 1:&:num <- copy 2000/unsafe/skip-alloc-id # even local variables always contain raw addresses 8:num/raw <- copy *1:&:num ] +mem: storing 34 in location 8 @@ -43,8 +49,9 @@ def main [ def main [ default-space:num, x:num <- copy 0, 1 ] -+name: assign x 1 ++name: assign x 2 -name: assign default-space 1 +-name: assign default-space 2 :(before "End is_disqualified Special-cases") if (x.name == "default-space") @@ -74,7 +81,7 @@ void absolutize(reagent& x) { //: hook replaced in a later layer int space_base(const reagent& x) { - return current_call().default_space ? current_call().default_space : 0; + return current_call().default_space ? (current_call().default_space + /*skip alloc id*/1) : 0; } int address(int offset, int base) { @@ -95,9 +102,13 @@ int address(int offset, int base) { :(after "Begin Preprocess write_memory(x, data)") if (x.name == "default-space") { - if (!scalar(data) || !is_mu_space(x)) + if (!is_mu_space(x)) { raise << maybe(current_recipe_name()) << "'default-space' should be of type address:array:location, but is " << to_string(x.type) << '\n' << end(); - current_call().default_space = data.at(0); + return; + } + double space_location = data.at(/*skip alloc id*/1); + trace("mem") << "storing " << no_scientific(space_location) << " to default_space" << end(); + current_call().default_space = space_location; return; } :(code) @@ -115,11 +126,13 @@ def main [ default-space:space <- copy 10/unsafe 1:space/raw <- copy default-space:space ] -+mem: storing 10 in location 1 +# skip alloc id ++mem: storing 10 in location 2 :(after "Begin Preprocess read_memory(x)") if (x.name == "default-space") { vector<double> result; + result.push_back(/*alloc id*/0); result.push_back(current_call().default_space); return result; } @@ -129,13 +142,13 @@ if (x.name == "default-space") { :(scenario lookup_sidesteps_default_space_in_get) def main [ # pretend pointer to container from outside - 2000:num <- copy 34 - 2001:num <- copy 35 + 2001:num <- copy 34 + 2002:num <- copy 35 # pretend address:array:location; in practice we'll use 'new' - 1000:num <- copy 5 # length + 1001:num <- copy 5 # length # actual start of this recipe - default-space:space <- copy 1000/unsafe - 1:&:point <- copy 2000/unsafe + default-space:space <- copy 1000/unsafe/skip-alloc-id + 1:&:point <- copy 2000/unsafe/skip-alloc-id 9:num/raw <- get *1:&:point, 1:offset ] +mem: storing 35 in location 9 @@ -148,14 +161,14 @@ element.properties.push_back(pair<string, string_tree*>("raw", NULL)); :(scenario lookup_sidesteps_default_space_in_index) def main [ # pretend pointer to array from outside - 2000:num <- copy 2 # length - 2001:num <- copy 34 - 2002:num <- copy 35 + 2001:num <- copy 2 # length + 2002:num <- copy 34 + 2003:num <- copy 35 # pretend address:array:location; in practice we'll use 'new' - 1000:num <- copy 5 # length + 1001:num <- copy 5 # length # actual start of this recipe - default-space:space <- copy 1000/unsafe - 1:&:@:num <- copy 2000/unsafe + default-space:space <- copy 1000/unsafe/skip-alloc-id + 1:&:@:num <- copy 2000/unsafe/skip-alloc-id 9:num/raw <- index *1:&:@:num, 1 ] +mem: storing 35 in location 9 @@ -172,8 +185,8 @@ def main [ x:num <- copy 0 y:num <- copy 3 ] -# allocate space for x and y, as well as the chaining slot at 0 -+mem: array length is 3 +# allocate space for x and y, as well as the chaining slot at indices 0 and 1 ++mem: array length is 4 :(before "End is_disqualified Special-cases") if (x.name == "number-of-locals") diff --git a/044space_surround.cc b/044space_surround.cc index 310672be..8efda681 100644 --- a/044space_surround.cc +++ b/044space_surround.cc @@ -8,24 +8,24 @@ # location 1 in space 1 refers to the space surrounding the default space, here 20. def main [ # pretend address:array:location; in practice we'll use 'new' - 10:num <- copy 5 # length + 11:num <- copy 5 # length # pretend address:array:location; in practice we'll use 'new" - 20:num <- copy 5 # length + 21:num <- copy 5 # length # actual start of this recipe - default-space:space <- copy 10/unsafe + default-space:space <- copy 10/unsafe/skip-alloc-id #: later layers will explain the /names: property - 0:space/names:dummy <- copy 20/unsafe - 1:num <- copy 32 - 1:num/space:1 <- copy 33 + 0:space/names:dummy <- copy 20/unsafe/skip-alloc-id + 2:num <- copy 32 + 2:num/space:1 <- copy 33 ] def dummy [ # just for the /names: property above ] -# chain space: 10 + (length) 1 -+mem: storing 20 in location 11 -# store to default space: 10 + (skip length) 1 + (index) 1 -+mem: storing 32 in location 12 -# store to chained space: (contents of location 12) 20 + (length) 1 + (index) 1 -+mem: storing 33 in location 22 +# write chained space: 10 + (alloc id for default-space) 1 + (length) 1 + (alloc id for chained space) 1 ++mem: storing 20 in location 13 +# store to inside default space: 10 + (alloc id) 1 + (length) 1 + (index) 2 ++mem: storing 32 in location 14 +# store to inside chained space: (contents of location 12) 20 + (alloc id) 1 + (length) 1 + (index) 2 ++mem: storing 33 in location 24 //: If you think of a space as a collection of variables with a common //: lifetime, surrounding allows managing shorter lifetimes inside a longer @@ -33,14 +33,17 @@ def dummy [ # just for the /names: property above :(replace{} "int space_base(const reagent& x)") int space_base(const reagent& x) { - int base = current_call().default_space ? current_call().default_space : 0; + int base = current_call().default_space ? (current_call().default_space+/*skip alloc id*/1) : 0; return space_base(x, space_index(x), base); } int space_base(const reagent& x, int space_index, int base) { + trace("space") << "default_space is at location " << base << " with " << space_index << " chained spaces to go" << end(); if (space_index == 0) return base; - return space_base(x, space_index-1, get_or_insert(Memory, base+/*skip length*/1)); + double chained_space_address = base+/*skip length*/1+/*skip alloc id of chaining slot*/1; + double chained_space_base = get_or_insert(Memory, chained_space_address) + /*skip alloc id of chained space*/1; + return space_base(x, space_index-1, chained_space_base); } int space_index(const reagent& x) { diff --git a/045closure_name.cc b/045closure_name.cc index e478337d..f5c8d2aa 100644 --- a/045closure_name.cc +++ b/045closure_name.cc @@ -9,9 +9,9 @@ :(scenario closure) def main [ default-space:space <- new location:type, 30 - 1:space/names:new-counter <- new-counter - 2:num/raw <- increment-counter 1:space/names:new-counter - 3:num/raw <- increment-counter 1:space/names:new-counter + 2:space/names:new-counter <- new-counter + 10:num/raw <- increment-counter 2:space/names:new-counter + 11:num/raw <- increment-counter 2:space/names:new-counter ] def new-counter [ default-space:space <- new location:type, 30 @@ -27,7 +27,7 @@ def increment-counter [ return y:num/space:1 ] +name: lexically surrounding space for recipe increment-counter comes from new-counter -+mem: storing 5 in location 3 ++mem: storing 5 in location 11 //: To make this work, compute the recipe that provides names for the //: surrounding space of each recipe. diff --git a/046check_type_by_name.cc b/046check_type_by_name.cc index df5a2a6d..8a023024 100644 --- a/046check_type_by_name.cc +++ b/046check_type_by_name.cc @@ -87,27 +87,27 @@ void check_type(set<reagent>& known, const reagent& x, const recipe& caller) { :(scenario transform_fills_in_missing_types) def main [ - x:num <- copy 1 + x:num <- copy 10 y:num <- add x, 1 ] -# x is in location 1, y in location 2 -+mem: storing 2 in location 2 +# x is in location 2, y in location 3 ++mem: storing 11 in location 3 :(scenario transform_fills_in_missing_types_in_product) def main [ - x:num <- copy 1 - x <- copy 2 + x:num <- copy 10 + x <- copy 11 ] -# x is in location 1 -+mem: storing 2 in location 1 +# x is in location 2 ++mem: storing 11 in location 2 :(scenario transform_fills_in_missing_types_in_product_and_ingredient) def main [ - x:num <- copy 1 + x:num <- copy 10 x <- add x, 1 ] # x is in location 1 -+mem: storing 2 in location 1 ++mem: storing 11 in location 2 :(scenario transform_fills_in_missing_label_type) def main [ diff --git a/050scenario.cc b/050scenario.cc index e84a18e9..c4f2541b 100644 --- a/050scenario.cc +++ b/050scenario.cc @@ -79,6 +79,7 @@ Scenario_names = Scenario_names_snapshot; :(before "End Command Handlers") else if (command == "scenario") { scenario result = parse_scenario(in); +//? result.name.clear(); // disable running scenarios if (!result.name.empty()) Scenarios.push_back(result); } diff --git a/053recipe_header.cc b/053recipe_header.cc index 057234f9..b948ce61 100644 --- a/053recipe_header.cc +++ b/053recipe_header.cc @@ -207,6 +207,9 @@ case NEXT_INGREDIENT_WITHOUT_TYPECHECKING: { if (current_call().next_ingredient_to_process < SIZE(current_call().ingredient_atoms)) { products.push_back( current_call().ingredient_atoms.at(current_call().next_ingredient_to_process)); + if (is_mu_scalar(current_call().ingredients.at(current_call().next_ingredient_to_process)) + && is_mu_address(current_instruction().products.at(0))) + products.at(0).insert(products.at(0).begin(), /*alloc id*/0); assert(SIZE(products) == 1); products.resize(2); // push a new vector products.at(1).push_back(1); ++current_call().next_ingredient_to_process; diff --git a/055shape_shifting_container.cc b/055shape_shifting_container.cc index 0e7409d8..3bf1fe80 100644 --- a/055shape_shifting_container.cc +++ b/055shape_shifting_container.cc @@ -295,12 +295,12 @@ container foo:_a:_b [ y:_b ] def main [ - 1:text <- new [abc] - {2: (foo number (address array character))} <- merge 34/x, 1:text/y - 3:text <- get {2: (foo number (address array character))}, y:offset - 4:bool <- equal 1:text, 3:text + 10:text <- new [abc] + {20: (foo number (address array character))} <- merge 34/x, 10:text/y + 30:text <- get {20: (foo number (address array character))}, y:offset + 40:bool <- equal 10:text, 30:text ] -+mem: storing 1 in location 4 ++mem: storing 1 in location 40 :(before "End element_type Special-cases") replace_type_ingredients(element, type, info, " while computing element type of container"); @@ -346,8 +346,8 @@ exclusive-container foo:_a [ ] def main [ 1:text <- new [abc] - 2:foo:point <- merge 0/variant, 34/xx, 35/xy - 10:point, 20:bool <- maybe-convert 2:foo:point, 0/variant + 3:foo:point <- merge 0/variant, 34/xx, 35/xy + 10:point, 20:bool <- maybe-convert 3:foo:point, 0/variant ] +mem: storing 1 in location 20 +mem: storing 35 in location 11 diff --git a/058to_text.cc b/058to_text.cc index 8c86e36c..9cb14e14 100644 --- a/058to_text.cc +++ b/058to_text.cc @@ -18,6 +18,7 @@ case TO_TEXT: { :(before "End Primitive Recipe Implementations") case TO_TEXT: { products.resize(1); + products.at(0).push_back(/*alloc id*/0); products.at(0).push_back(new_mu_text(inspect(current_instruction().ingredients.at(0), ingredients.at(0)))); break; } diff --git a/060rewrite_literal_string.cc b/060rewrite_literal_string.cc index f4ed9b4c..95e38924 100644 --- a/060rewrite_literal_string.cc +++ b/060rewrite_literal_string.cc @@ -19,6 +19,7 @@ Transform.push_back(rewrite_literal_string_to_text); // idempotent set<string> recipes_taking_literal_strings; :(code) void initialize_transform_rewrite_literal_string_to_text() { + recipes_taking_literal_strings.insert("assert"); recipes_taking_literal_strings.insert("$print"); recipes_taking_literal_strings.insert("$dump-trace"); recipes_taking_literal_strings.insert("$system"); diff --git a/065duplex_list.mu b/065duplex_list.mu index 037cb923..b299fda7 100644 --- a/065duplex_list.mu +++ b/065duplex_list.mu @@ -393,12 +393,17 @@ def remove-between start:&:duplex-list:_elem, end:&:duplex-list:_elem/contained- # start->next = end *next <- put *next, prev:offset, 0 *start <- put *start, next:offset, end - return-unless end + { + break-if end + stash [spliced:] next + return + } # end->prev->next = 0 # end->prev = start prev:&:duplex-list:_elem <- get *end, prev:offset assert prev, [malformed duplex list - 2] *prev <- put *prev, next:offset, 0 + stash [spliced:] next *end <- put *end, prev:offset, start ] @@ -431,6 +436,9 @@ scenario remove-range [ 12 <- 15 20 <- 0 ] + trace-should-contain [ + app: spliced: 16 <-> 17 <-> 18 + ] ] scenario remove-range-to-final [ @@ -466,6 +474,49 @@ scenario remove-range-to-final [ 12 <- 18 20 <- 0 # no more elements ] + trace-should-contain [ + app: spliced: 15 <-> 16 <-> 17 + ] +] + +scenario remove-range-to-penultimate [ + local-scope + # construct a duplex list with six elements [13, 14, 15, 16, 17, 18] + list:&:duplex-list:num <- push 18, 0 + list <- push 17, list + list <- push 16, list + list <- push 15, list + list <- push 14, list + list <- push 13, list + run [ + # delete 15 and 16 + # start pointer: to the second element + list2:&:duplex-list:num <- next list + # end pointer: to the last (sixth) element + end:&:duplex-list:num <- next list2 + end <- next end + end <- next end + remove-between list2, end + # now check the list + 10:num/raw <- get *list, value:offset + list <- next list + 11:num/raw <- get *list, value:offset + list <- next list + 12:num/raw <- get *list, value:offset + list <- next list + 13:num/raw <- get *list, value:offset + 20:&:duplex-list:num/raw <- next list + ] + memory-should-contain [ + 10 <- 13 + 11 <- 14 + 12 <- 17 + 13 <- 18 + 20 <- 0 # no more elements + ] + trace-should-contain [ + app: spliced: 15 <-> 16 + ] ] scenario remove-range-empty [ diff --git a/069hash.cc b/069hash.cc index 4400c1e8..1d2f706e 100644 --- a/069hash.cc +++ b/069hash.cc @@ -62,7 +62,7 @@ size_t hash_mu_address(size_t h, reagent& r) { } size_t hash_mu_text(size_t h, const reagent& r) { - string input = read_mu_text(get_or_insert(Memory, r.value)); + string input = read_mu_text(get_or_insert(Memory, r.value+/*skip alloc id*/1)); for (int i = 0; i < SIZE(input); ++i) { h = hash_iter(h, static_cast<size_t>(input.at(i))); //? cerr << i << ": " << h << '\n'; @@ -319,11 +319,11 @@ def main [ :(scenario hash_matches_old_version) def main [ 1:text <- new [abc] - 2:num <- hash 1:text - 3:num <- hash_old 1:text - 4:bool <- equal 2:num, 3:num + 3:num <- hash 1:text + 4:num <- hash_old 1:text + 5:bool <- equal 3:num, 4:num ] -+mem: storing 1 in location 4 ++mem: storing 1 in location 5 :(before "End Primitive Recipe Declarations") HASH_OLD, @@ -343,7 +343,7 @@ case HASH_OLD: { } :(before "End Primitive Recipe Implementations") case HASH_OLD: { - string input = read_mu_text(ingredients.at(0).at(0)); + string input = read_mu_text(ingredients.at(0).at(/*skip alloc id*/1)); size_t h = 0 ; for (int i = 0; i < SIZE(input); ++i) { diff --git a/074wait.cc b/074wait.cc index eb17c8aa..47bbb0cc 100644 --- a/074wait.cc +++ b/074wait.cc @@ -262,22 +262,23 @@ def main [ :(scenario get_location_indirect) # 'get-location' can read from container address def main [ - 1:num <- copy 10 - 10:num <- copy 34 - 11:num <- copy 35 + 1:&:point <- copy 10/unsafe + # skip alloc id + 11:num <- copy 34 + 12:num <- copy 35 4:location <- get-location 1:&:point/lookup, 0:offset ] -+mem: storing 10 in location 4 ++mem: storing 11 in location 4 :(scenario get_location_indirect_2) def main [ - 1:num <- copy 10 - 10:num <- copy 34 - 11:num <- copy 35 + 1:&:point <- copy 10/unsafe + 11:num <- copy 34 + 12:num <- copy 35 4:&:num <- copy 20/unsafe 4:&:location/lookup <- get-location 1:&:point/lookup, 0:offset ] -+mem: storing 10 in location 20 ++mem: storing 11 in location 21 //: allow waiting on a routine to complete diff --git a/082scenario_screen.cc b/082scenario_screen.cc index 31cbfcc9..dc015fed 100644 --- a/082scenario_screen.cc +++ b/082scenario_screen.cc @@ -145,8 +145,14 @@ assert(Next_predefined_global_for_scenarios < Reserved_for_tests); :(before "End Globals") // Scenario Globals. -extern const int SCREEN = Next_predefined_global_for_scenarios++; +extern const int SCREEN = next_predefined_global_for_scenarios(/*size_of(address:screen)*/2); // End Scenario Globals. +:(code) +int next_predefined_global_for_scenarios(int size) { + int result = Next_predefined_global_for_scenarios; + Next_predefined_global_for_scenarios += size; + return result; +} //: give 'screen' a fixed location in scenarios :(before "End Special Scenario Variable Names(r)") @@ -250,19 +256,27 @@ struct raw_string_stream { :(code) void check_screen(const string& expected_contents, const int color) { - int screen_location = get_or_insert(Memory, SCREEN); - int data_offset = find_element_name(get(Type_ordinal, "screen"), "data", ""); - assert(data_offset >= 0); - int screen_data_location = screen_location+data_offset; // type: address:array:character - int screen_data_start = get_or_insert(Memory, screen_data_location); // type: array:character - int width_offset = find_element_name(get(Type_ordinal, "screen"), "num-columns", ""); - int screen_width = get_or_insert(Memory, screen_location+width_offset); - int height_offset = find_element_name(get(Type_ordinal, "screen"), "num-rows", ""); - int screen_height = get_or_insert(Memory, screen_location+height_offset); + int screen_location = get_or_insert(Memory, SCREEN+/*skip address alloc id*/1) + /*skip payload alloc id*/1; + reagent screen("x:screen"); // just to ensure screen.type is reclaimed + int screen_data_location = find_element_location(screen_location, "data", screen.type, "check_screen"); // type: address:array:character + assert(screen_data_location >= 0); +//? cerr << "screen data is at location " << screen_data_location << '\n'; + int screen_data_start = get_or_insert(Memory, screen_data_location+/*skip address alloc id*/1) + /*skip payload alloc id*/1; // type: array:character +//? cerr << "screen data start is at " << screen_data_start << '\n'; + int screen_width_location = find_element_location(screen_location, "num-columns", screen.type, "check_screen"); +//? cerr << "screen width is at location " << screen_width_location << '\n'; + int screen_width = get_or_insert(Memory, screen_width_location); +//? cerr << "screen width: " << screen_width << '\n'; + int screen_height_location = find_element_location(screen_location, "num-rows", screen.type, "check_screen"); +//? cerr << "screen height is at location " << screen_height_location << '\n'; + int screen_height = get_or_insert(Memory, screen_height_location); +//? cerr << "screen height: " << screen_height << '\n'; + int top_index_location= find_element_location(screen_location, "top-idx", screen.type, "check_screen"); +//? cerr << "top of screen is at location " << top_index_location << '\n'; + int top_index = get_or_insert(Memory, top_index_location); +//? cerr << "top of screen is index " << top_index << '\n'; raw_string_stream cursor(expected_contents); // todo: too-long expected_contents should fail - int top_index_offset = find_element_name(get(Type_ordinal, "screen"), "top-idx", ""); - int top_index = get_or_insert(Memory, screen_location+top_index_offset); for (int i=0, row=top_index/screen_width; i < screen_height; ++i, row=(row+1)%screen_height) { cursor.skip_whitespace_and_comments(); if (cursor.at_end()) break; @@ -385,18 +399,25 @@ case _DUMP_SCREEN: { :(code) void dump_screen() { - int screen_location = get_or_insert(Memory, SCREEN); - int width_offset = find_element_name(get(Type_ordinal, "screen"), "num-columns", ""); - int screen_width = get_or_insert(Memory, screen_location+width_offset); - int height_offset = find_element_name(get(Type_ordinal, "screen"), "num-rows", ""); - int screen_height = get_or_insert(Memory, screen_location+height_offset); - int data_offset = find_element_name(get(Type_ordinal, "screen"), "data", ""); - assert(data_offset >= 0); - int screen_data_location = screen_location+data_offset; // type: address:array:character - int screen_data_start = get_or_insert(Memory, screen_data_location); // type: array:character - assert(get_or_insert(Memory, screen_data_start) == screen_width*screen_height); - int top_index_offset = find_element_name(get(Type_ordinal, "screen"), "top-idx", ""); - int top_index = get_or_insert(Memory, screen_location+top_index_offset); + int screen_location = get_or_insert(Memory, SCREEN+/*skip address alloc id*/1) + /*skip payload alloc id*/1; + reagent screen("x:screen"); // just to ensure screen.type is reclaimed + int screen_data_location = find_element_location(screen_location, "data", screen.type, "check_screen"); // type: address:array:character + assert(screen_data_location >= 0); +//? cerr << "screen data is at location " << screen_data_location << '\n'; + int screen_data_start = get_or_insert(Memory, screen_data_location+/*skip address alloc id*/1) + /*skip payload alloc id*/1; // type: array:character +//? cerr << "screen data start is at " << screen_data_start << '\n'; + int screen_width_location = find_element_location(screen_location, "num-columns", screen.type, "check_screen"); +//? cerr << "screen width is at location " << screen_width_location << '\n'; + int screen_width = get_or_insert(Memory, screen_width_location); +//? cerr << "screen width: " << screen_width << '\n'; + int screen_height_location = find_element_location(screen_location, "num-rows", screen.type, "check_screen"); +//? cerr << "screen height is at location " << screen_height_location << '\n'; + int screen_height = get_or_insert(Memory, screen_height_location); +//? cerr << "screen height: " << screen_height << '\n'; + int top_index_location= find_element_location(screen_location, "top-idx", screen.type, "check_screen"); +//? cerr << "top of screen is at location " << top_index_location << '\n'; + int top_index = get_or_insert(Memory, top_index_location); +//? cerr << "top of screen is index " << top_index << '\n'; for (int i=0, row=top_index/screen_width; i < screen_height; ++i, row=(row+1)%screen_height) { cerr << '.'; int curr = screen_data_start+/*length*/1+row*screen_width* /*size of screen-cell*/2; diff --git a/085scenario_console.cc b/085scenario_console.cc index 2c3ab4bc..31aa4fe7 100644 --- a/085scenario_console.cc +++ b/085scenario_console.cc @@ -34,7 +34,7 @@ scenario keyboard-in-scenario [ ] :(before "End Scenario Globals") -extern const int CONSOLE = Next_predefined_global_for_scenarios++; +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; @@ -61,8 +61,8 @@ case ASSUME_CONSOLE: { int size = /*length*/1 + num_events*size_of_event(); int event_data_address = allocate(size); // store length - put(Memory, event_data_address, num_events); - int curr_address = event_data_address + /*skip length*/1; + put(Memory, event_data_address+/*skip alloc id*/1, num_events); + int curr_address = event_data_address + /*skip alloc id*/1 + /*skip length*/1; for (int i = 0; i < SIZE(r.steps); ++i) { const instruction& inst = r.steps.at(i); if (inst.name == "left-click") { @@ -113,13 +113,13 @@ case ASSUME_CONSOLE: { } } } - assert(curr_address == event_data_address+size); + 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("mem") << "storing console in " << console_address << end(); - put(Memory, CONSOLE, console_address); + put(Memory, CONSOLE+/*skip alloc id*/1, console_address); trace("mem") << "storing console data in " << console_address+/*offset of 'data' in container 'events'*/1 << end(); - put(Memory, console_address+/*offset of 'data' in container 'events'*/1, event_data_address); + put(Memory, console_address+/*skip alloc id*/1+/*offset of 'data' in container 'events'*/1+/*skip alloc id of 'data'*/1, event_data_address); break; } diff --git a/087file.cc b/087file.cc index 44da9b02..9fd056db 100644 --- a/087file.cc +++ b/087file.cc @@ -35,7 +35,7 @@ case _OPEN_FILE_FOR_READING: { } :(before "End Primitive Recipe Implementations") case _OPEN_FILE_FOR_READING: { - string filename = read_mu_text(ingredients.at(0).at(0)); + string filename = read_mu_text(ingredients.at(0).at(/*skip alloc id*/1)); assert(sizeof(long long int) >= sizeof(FILE*)); FILE* f = fopen(filename.c_str(), "r"); long long int result = reinterpret_cast<long long int>(f); @@ -70,7 +70,7 @@ case _OPEN_FILE_FOR_WRITING: { } :(before "End Primitive Recipe Implementations") case _OPEN_FILE_FOR_WRITING: { - string filename = read_mu_text(ingredients.at(0).at(0)); + string filename = read_mu_text(ingredients.at(0).at(/*skip alloc id*/1)); assert(sizeof(long long int) >= sizeof(FILE*)); long long int result = reinterpret_cast<long long int>(fopen(filename.c_str(), "w")); products.resize(1); diff --git a/089scenario_filesystem.cc b/089scenario_filesystem.cc index f14534ac..bacb61be 100644 --- a/089scenario_filesystem.cc +++ b/089scenario_filesystem.cc @@ -71,7 +71,7 @@ scenario escaping-file-contents [ ] :(before "End Globals") -extern const int RESOURCES = Next_predefined_global_for_scenarios++; +extern const int RESOURCES = next_predefined_global_for_scenarios(/*size_of(address:resources)*/2); //: give 'resources' a fixed location in scenarios :(before "End Special Scenario Variable Names(r)") Name[r]["resources"] = RESOURCES; @@ -203,26 +203,28 @@ string munge_resources_contents(const string& data, const string& filename, cons } void construct_resources_object(const map<string, string>& contents) { - int resources_data_address = allocate(SIZE(contents)*2 + /*array length*/1); - int curr = resources_data_address + /*skip length*/1; + int resources_data_address = allocate(SIZE(contents) * /*size of resource*/4 + /*array length*/1); + int curr = resources_data_address + /*skip alloc id*/1 + /*skip array length*/1; for (map<string, string>::const_iterator p = contents.begin(); p != contents.end(); ++p) { + ++curr; // skip alloc id of resource.name put(Memory, curr, new_mu_text(p->first)); trace("mem") << "storing file name " << get(Memory, curr) << " in location " << curr << end(); ++curr; + ++curr; // skip alloc id of resource.contents put(Memory, curr, new_mu_text(p->second)); trace("mem") << "storing file contents " << get(Memory, curr) << " in location " << curr << end(); ++curr; } - curr = resources_data_address; - put(Memory, curr, SIZE(contents)); // size of array + curr = resources_data_address + /*skip alloc id of resources.data*/1; + put(Memory, curr, SIZE(contents)); // array length trace("mem") << "storing resources size " << get(Memory, curr) << " in location " << curr << end(); // wrap the resources data in a 'resources' object int resources_address = allocate(size_of_resources()); - curr = resources_address+/*offset of 'data' element*/1; + curr = resources_address+/*alloc id*/1+/*offset of 'data' element*/1+/*skip alloc id of 'data' element*/1; put(Memory, curr, resources_data_address); trace("mem") << "storing resources data address " << resources_data_address << " in location " << curr << end(); // save in product - put(Memory, RESOURCES, resources_address); + put(Memory, RESOURCES+/*skip alloc id*/1, resources_address); trace("mem") << "storing resources address " << resources_address << " in location " << RESOURCES << end(); } diff --git a/091socket.cc b/091socket.cc index 7b6ca5b1..a0f3b948 100644 --- a/091socket.cc +++ b/091socket.cc @@ -40,7 +40,7 @@ case _OPEN_CLIENT_SOCKET: { } :(before "End Primitive Recipe Implementations") case _OPEN_CLIENT_SOCKET: { - string host = read_mu_text(ingredients.at(0).at(0)); + string host = read_mu_text(ingredients.at(0).at(/*skip alloc id*/1)); int port = ingredients.at(1).at(0); socket_t* client = client_socket(host, port); products.resize(1); diff --git a/101run_sandboxed.cc b/101run_sandboxed.cc index a0b827e9..6d866b60 100644 --- a/101run_sandboxed.cc +++ b/101run_sandboxed.cc @@ -3,20 +3,23 @@ :(scenario run_interactive_code) def main [ - 1:num <- copy 0 - 2:text <- new [1:num/raw <- copy 34] - run-sandboxed 2:text - 3:num <- copy 1:num + 1:num <- copy 0 # reserve space for the sandbox + 10:text <- new [1:num/raw <- copy 34] +#? $print 10:num [|] 11:num [: ] 1000:num [|] *10:text [ (] 10:text [)] 10/newline + run-sandboxed 10:text + 20:num <- copy 1:num ] -+mem: storing 34 in location 3 +#? ? ++mem: storing 34 in location 20 :(scenario run_interactive_empty) def main [ - 1:text <- copy 0/unsafe - 2:text <- run-sandboxed 1:text + 10:text <- copy 0/unsafe + 20:text <- run-sandboxed 10:text ] # result is null -+mem: storing 0 in location 2 ++mem: storing 0 in location 20 ++mem: storing 0 in location 21 //: As the name suggests, 'run-sandboxed' will prevent certain operations that //: regular Mu code can perform. @@ -52,12 +55,16 @@ case RUN_SANDBOXED: { } :(before "End Primitive Recipe Implementations") case RUN_SANDBOXED: { - bool new_code_pushed_to_stack = run_interactive(ingredients.at(0).at(0)); + bool new_code_pushed_to_stack = run_interactive(ingredients.at(0).at(/*skip alloc id*/1)); if (!new_code_pushed_to_stack) { products.resize(5); + products.at(0).push_back(/*alloc id*/0); products.at(0).push_back(0); + products.at(1).push_back(/*alloc id*/0); products.at(1).push_back(trace_error_contents()); + products.at(2).push_back(/*alloc id*/0); products.at(2).push_back(0); + products.at(3).push_back(/*alloc id*/0); products.at(3).push_back(trace_app_contents()); products.at(4).push_back(1); // completed run_code_end(); @@ -90,6 +97,7 @@ string Save_trace_file; // all errors. // returns true if successfully called (no errors found during load and transform) bool run_interactive(int address) { +//? cerr << "run_interactive: " << address << '\n'; assert(contains_key(Recipe_ordinal, "interactive") && get(Recipe_ordinal, "interactive") != 0); // try to sandbox the run as best you can // todo: test this @@ -98,6 +106,7 @@ bool run_interactive(int address) { Memory.erase(i); } string command = trim(strip_comments(read_mu_text(address))); +//? cerr << "command: " << command << '\n'; Name[get(Recipe_ordinal, "interactive")].clear(); run_code_begin(/*should_stash_snapshots*/true); if (command.empty()) return false; @@ -213,15 +222,20 @@ load(string( "]\n" + "recipe sandbox [\n" + "local-scope\n" + +//? "$print [aaa] 10/newline\n" + "screen:&:screen <- new-fake-screen 30, 5\n" + "routine-id:num <- start-running interactive, screen\n" + "limit-time routine-id, 100000/instructions\n" + "wait-for-routine routine-id\n" + +//? "$print [bbb] 10/newline\n" + "instructions-run:num <- number-of-instructions routine-id\n" + "stash instructions-run [instructions run]\n" + "sandbox-state:num <- routine-state routine-id\n" + "completed?:bool <- equal sandbox-state, 1/completed\n" + +//? "$print [completed: ] completed? 10/newline\n" + "output:text <- $most-recent-products\n" + +//? "$print [zzz] 10/newline\n" + +//? "$print output\n" + "errors:text <- save-errors\n" + "stashes:text <- save-app-trace\n" + "$cleanup-run-sandboxed\n" + @@ -281,6 +295,7 @@ case _MOST_RECENT_PRODUCTS: { :(before "End Primitive Recipe Implementations") case _MOST_RECENT_PRODUCTS: { products.resize(1); + products.at(0).push_back(/*alloc id*/0); products.at(0).push_back(new_mu_text(Most_recent_products)); break; } @@ -296,6 +311,7 @@ case SAVE_ERRORS: { :(before "End Primitive Recipe Implementations") case SAVE_ERRORS: { products.resize(1); + products.at(0).push_back(/*alloc id*/0); products.at(0).push_back(trace_error_contents()); break; } @@ -311,6 +327,7 @@ case SAVE_APP_TRACE: { :(before "End Primitive Recipe Implementations") case SAVE_APP_TRACE: { products.resize(1); + products.at(0).push_back(/*alloc id*/0); products.at(0).push_back(trace_app_contents()); break; } @@ -332,64 +349,64 @@ case _CLEANUP_RUN_SANDBOXED: { :(scenario "run_interactive_converts_result_to_text") def main [ # try to interactively add 2 and 2 - 1:text <- new [add 2, 2] - 2:text <- run-sandboxed 1:text - 10:@:char <- copy *2:text + 10:text <- new [add 2, 2] + 20:text <- run-sandboxed 10:text + 30:@:char <- copy *20:text ] # first letter in the output should be '4' in unicode -+mem: storing 52 in location 11 ++mem: storing 52 in location 31 :(scenario "run_interactive_ignores_products_in_nested_functions") def main [ - 1:text <- new [foo] - 2:text <- run-sandboxed 1:text - 10:@:char <- copy *2:text + 10:text <- new [foo] + 20:text <- run-sandboxed 10:text + 30:@:char <- copy *20:text ] def foo [ - 20:num <- copy 1234 + 40:num <- copy 1234 { break reply 5678 } ] # no product should have been tracked -+mem: storing 0 in location 10 ++mem: storing 0 in location 30 :(scenario "run_interactive_ignores_products_in_previous_instructions") def main [ - 1:text <- new [ + 10:text <- new [ add 1, 1 # generates a product foo] # no products - 2:text <- run-sandboxed 1:text - 10:@:char <- copy *2:text + 20:text <- run-sandboxed 10:text + 30:@:char <- copy *20:text ] def foo [ - 20:num <- copy 1234 + 40:num <- copy 1234 { break reply 5678 } ] # no product should have been tracked -+mem: storing 0 in location 10 ++mem: storing 0 in location 30 :(scenario "run_interactive_remembers_products_before_final_label") def main [ - 1:text <- new [ + 10:text <- new [ add 1, 1 # generates a product +foo] # no products - 2:text <- run-sandboxed 1:text - 10:@:char <- copy *2:text + 20:text <- run-sandboxed 10:text + 30:@:char <- copy *20:text ] def foo [ - 20:num <- copy 1234 + 40:num <- copy 1234 { break reply 5678 } ] # product tracked -+mem: storing 50 in location 11 ++mem: storing 50 in location 31 :(scenario "run_interactive_returns_text") def main [ @@ -399,38 +416,42 @@ def main [ y:text <- new [b] z:text <- append x:text, y:text ] - 2:text <- run-sandboxed 1:text - 10:@:char <- copy *2:text + 10:text <- run-sandboxed 1:text +#? $print 10:text 10/newline + 20:@:char <- copy *10:text ] # output contains "ab" -+mem: storing 97 in location 11 -+mem: storing 98 in location 12 +#? ? ++mem: storing 97 in location 21 ++mem: storing 98 in location 22 :(scenario "run_interactive_returns_errors") def main [ # run a command that generates an error - 1:text <- new [x:num <- copy 34 + 10:text <- new [x:num <- copy 34 get x:num, foo:offset] - 2:text, 3:text <- run-sandboxed 1:text - 10:@:char <- copy *3:text + 20:text, 30:text <- run-sandboxed 10:text + 40:@:char <- copy *30:text ] # error should be "unknown element foo in container number" -+mem: storing 117 in location 11 -+mem: storing 110 in location 12 -+mem: storing 107 in location 13 -+mem: storing 110 in location 14 ++mem: storing 117 in location 41 ++mem: storing 110 in location 42 ++mem: storing 107 in location 43 ++mem: storing 110 in location 44 # ... :(scenario run_interactive_with_comment) def main [ # 2 instructions, with a comment after the first - 1:&:@:num <- new [a:num <- copy 0 # abc + 10:text <- new [a:num <- copy 0 # abc b:num <- copy 0 ] - 2:text, 3:text <- run-sandboxed 1:text + 20:text, 30:text <- run-sandboxed 10:text ] # no errors -+mem: storing 0 in location 3 +# skip alloc id ++mem: storing 0 in location 30 ++mem: storing 0 in location 31 :(after "Running One Instruction") if (Track_most_recent_products && SIZE(Current_routine->calls) == Call_depth_to_track_most_recent_products_at @@ -441,6 +462,7 @@ if (Track_most_recent_products && SIZE(Current_routine->calls) == Call_depth_to_ :(before "End Running One Instruction") if (Track_most_recent_products && SIZE(Current_routine->calls) == Call_depth_to_track_most_recent_products_at) { Most_recent_products = track_most_recent_products(current_instruction(), products); +//? cerr << "most recent products: " << Most_recent_products << '\n'; } :(code) string track_most_recent_products(const instruction& instruction, const vector<vector<double> >& products) { @@ -458,8 +480,8 @@ string track_most_recent_products(const instruction& instruction, const vector<v // => abc if (i < SIZE(instruction.products)) { if (is_mu_text(instruction.products.at(i))) { - if (!scalar(products.at(i))) continue; // error handled elsewhere - out << read_mu_text(products.at(i).at(0)) << '\n'; + if (SIZE(products.at(i)) != 2) continue; // weak silent check for address + out << read_mu_text(products.at(i).at(/*skip alloc id*/1)) << '\n'; continue; } } @@ -562,6 +584,7 @@ case RELOAD: { Sandbox_mode = false; Current_routine = save_current_routine; products.resize(1); + products.at(0).push_back(/*alloc id*/0); products.at(0).push_back(trace_error_contents()); run_code_end(); // wait until we're done with the trace contents break; diff --git a/edit/001-editor.mu b/edit/001-editor.mu index 8855395a..036ef07a 100644 --- a/edit/001-editor.mu +++ b/edit/001-editor.mu @@ -81,18 +81,20 @@ scenario editor-initializes-without-data [ assume-screen 5/width, 3/height run [ e:&:editor <- new-editor 0/data, 2/left, 5/right - 2:editor/raw <- copy *e + 1:editor/raw <- copy *e ] memory-should-contain [ - # 2 (data) <- just the § sentinel - # 3 (top of screen) <- the § sentinel - 4 <- 0 # bottom-of-screen; null since text fits on screen - # 5 (before cursor) <- the § sentinel - 6 <- 2 # left - 7 <- 4 # right (inclusive) - 8 <- 0 # bottom (not set until render) - 9 <- 1 # cursor row - 10 <- 2 # cursor column + # 1,2 (data) <- just the § sentinel + # 3,4 (top of screen) <- the § sentinel + # 5 (bottom of screen) <- null since text fits on screen + 5 <- 0 + 6 <- 0 + # 7,8 (before cursor) <- the § sentinel + 9 <- 2 # left + 10 <- 4 # right (inclusive) + 11 <- 0 # bottom (not set until render) + 12 <- 1 # cursor row + 13 <- 2 # cursor column ] screen-should-contain [ . . diff --git a/edit/002-typing.mu b/edit/002-typing.mu index 47885c4f..67fe76a0 100644 --- a/edit/002-typing.mu +++ b/edit/002-typing.mu @@ -280,7 +280,11 @@ scenario editor-handles-empty-event-queue [ assume-screen 10/width, 5/height e:&:editor <- new-editor [abc], 0/left, 10/right editor-render screen, e +#? x:num <- get *screen, num-rows:offset +#? $print [a: ] x 10/newline assume-console [] +#? x:num <- get *screen, num-rows:offset +#? $print [z: ] x 10/newline run [ editor-event-loop screen, console, e ] diff --git a/edit/003-shortcuts.mu b/edit/003-shortcuts.mu index 02ea77d0..78c6e49f 100644 --- a/edit/003-shortcuts.mu +++ b/edit/003-shortcuts.mu @@ -2006,7 +2006,13 @@ after <handle-special-character> [ delete-to-start-of-line?:bool <- equal c, 21/ctrl-u break-unless delete-to-start-of-line? <begin-delete-to-start-of-line> + $print [before: ] cursor-row [ ] cursor-column 10/newline deleted-cells:&:duplex-list:char <- delete-to-start-of-line editor + x:text <- to-text deleted-cells + $print x 10/newline + cursor-row <- get *editor, cursor-row:offset + cursor-column <- get *editor, cursor-column:offset + $print [after: ] cursor-row [ ] cursor-column 10/newline <end-delete-to-start-of-line> go-render?:bool <- minimal-render-for-ctrl-u screen, editor, deleted-cells return @@ -2016,6 +2022,7 @@ after <handle-special-character> [ def minimal-render-for-ctrl-u screen:&:screen, editor:&:editor, deleted-cells:&:duplex-list:char -> go-render?:bool, screen:&:screen [ local-scope load-inputs + $print [minimal render for ctrl-u] 10/newline curr-column:num <- get *editor, cursor-column:offset # accumulate the current line as text and render it buf:&:buffer:char <- new-buffer 30 # accumulator for the text we need to render @@ -2025,6 +2032,7 @@ def minimal-render-for-ctrl-u screen:&:screen, editor:&:editor, deleted-cells:&: { # if we have a wrapped line, give up and render the whole screen wrap?:bool <- greater-or-equal i, right + $print [wrap? ] wrap? 10/newline return-if wrap?, 1/go-render curr <- next curr break-unless curr @@ -2061,6 +2069,7 @@ def delete-to-start-of-line editor:&:editor -> result:&:duplex-list:char, editor { at-start-of-text?:bool <- equal start, init break-if at-start-of-text? + $print [0] 10/newline curr:char <- get *start, value:offset at-start-of-line?:bool <- equal curr, 10/newline break-if at-start-of-line? @@ -2071,14 +2080,23 @@ def delete-to-start-of-line editor:&:editor -> result:&:duplex-list:char, editor assert start, [delete-to-start-of-line tried to move before start of text] loop } + $print [1] 10/newline # snip it out result:&:duplex-list:char <- next start + x:text <- to-text start + $print [start: ] x 10/newline + x:text <- to-text end + $print [end: ] x 10/newline remove-between start, end + x:text <- to-text result + $print [snip: ] x 10/newline # update top-of-screen if it's just been invalidated { break-unless update-top-of-screen? + $print [2] 10/newline put *editor, top-of-screen:offset, start } + $print [3] 10/newline # adjust cursor before-cursor <- copy start *editor <- put *editor, before-cursor:offset, before-cursor @@ -2089,17 +2107,22 @@ def delete-to-start-of-line editor:&:editor -> result:&:duplex-list:char, editor width:num <- subtract right, left num-deleted:num <- length result cursor-row-adjustment:num <- divide-with-remainder num-deleted, width + $print [adj ] num-deleted [/] width [=] cursor-row-adjustment 10/newline return-unless cursor-row-adjustment + $print [4] 10/newline cursor-row:num <- get *editor, cursor-row:offset cursor-row-in-editor:num <- subtract cursor-row, 1 # ignore menubar at-top?:bool <- lesser-or-equal cursor-row-in-editor, cursor-row-adjustment { break-unless at-top? + $print [5] 10/newline cursor-row <- copy 1 # top of editor, below menubar } { break-if at-top? + $print [6] 10/newline cursor-row <- subtract cursor-row, cursor-row-adjustment + $print cursor-row 10/newline } put *editor, cursor-row:offset, cursor-row ] diff --git a/index.html b/index.html index 88e9b14a..dde93753 100644 --- a/index.html +++ b/index.html @@ -158,18 +158,16 @@ for gradually constructing long strings in a piecemeal fashion. space at run-time as pointers or <em>addresses</em>. All Mu instructions can dereference or <a href='html/035lookup.cc.html'><em>lookup</em></a> addresses of values in addition to operating on regular values. These addresses are -manually managed like C. However, all allocations are transparently -reference-counted or <a href='html/036refcount.cc.html'><em>refcounted</em></a>, -with every copy of a pointer updating refcounts appropriately. When the -refcount of an allocation drops to zero it is transparently <a href='html/037abandon.cc.html'>reclaimed</a> -and made available to future allocations. By construction it is impossible to -reclaim memory prematurely, while some other part of a program is still -pointing to it. This eliminates a whole class of undefined behavior and -security vulnerabilities that plague C. Compared to Rust, Mu pays some -additional runtime cost in exchange for C-like flexibility (you can copy -addresses around all you like, and write from any copy of an address) and -simpler implementation (no static analysis). Mu by convention abbreviates type -<tt>address</tt> to <tt>&</tt>. +manually managed like C, and can be reclaimed using the <a href='html/037abandon.cc.html'><tt>abandon</tt></a> +instruction. To ensure that stale addresses aren't used after being +abandoned/reused, each allocation gets a unique <em>alloc id</em> that is also +stored in the address returned. The lookup operation ensures that the alloc id +of an address matches that of its payload. This eliminates a whole class of +undefined behavior and security vulnerabilities that plague C. Compared to +Rust, Mu pays some additional runtime cost in exchange for C-like flexibility +(you can copy addresses around all you like, and write from any copy of an +address) and simpler implementation (no static analysis). Mu by convention +abbreviates type <tt>address</tt> to <tt>&</tt>. <p/>Support for higher-order recipes that can pass <a href='html/072recipe.cc.html'>recipes</a> around like any other value. |