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.
This commit is contained in:
Kartik Agaram 2018-06-15 22:12:03 -07:00
parent 3f34ac9369
commit 0edd9b9fc6
38 changed files with 757 additions and 381 deletions

View File

@ -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

View File

@ -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";

View File

@ -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)

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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());

View File

@ -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

View File

@ -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
}

View File

@ -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();

View File

@ -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;

View File

@ -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 [

View File

@ -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;

View File

@ -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": ()}

View File

@ -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

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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")

View File

@ -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) {

View File

@ -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.

View File

@ -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 [

View File

@ -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);
}

View File

@ -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;

View File

@ -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

View File

@ -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;
}

View File

@ -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");

View File

@ -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 [

View File

@ -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) {

View File

@ -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

View File

@ -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;

View File

@ -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;
}

View File

@ -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);

View File

@ -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();
}

View File

@ -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);

View File

@ -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;

View File

@ -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 [
. .

View File

@ -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
]

View File

@ -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
]

View File

@ -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>&amp;</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>&amp;</tt>.
<p/>Support for higher-order recipes that can pass <a href='html/072recipe.cc.html'>recipes</a>
around like any other value.