From 9da1b126cc017e14035b94c4615d211e5bc4bb21 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Fri, 17 Apr 2015 09:56:04 -0700 Subject: 1072 --- cpp/018container | 170 ------------------ cpp/019address | 117 ------------- cpp/020array | 157 ----------------- cpp/020container | 170 ++++++++++++++++++ cpp/021address | 117 +++++++++++++ cpp/021call | 77 -------- cpp/022array | 157 +++++++++++++++++ cpp/022call_ingredient | 44 ----- cpp/023call_reply | 72 -------- cpp/024brace | 398 ------------------------------------------ cpp/025call | 77 ++++++++ cpp/025name | 172 ------------------ cpp/026call_ingredient | 44 +++++ cpp/026new | 79 --------- cpp/027call_reply | 72 ++++++++ cpp/027space | 90 ---------- cpp/028space_surround | 50 ------ cpp/029literal_string | 91 ---------- cpp/030brace | 398 ++++++++++++++++++++++++++++++++++++++++++ cpp/030length | 30 ---- cpp/031name | 172 ++++++++++++++++++ cpp/031scenario | 132 -------------- cpp/032new | 79 +++++++++ cpp/032scenario_test.mu | 9 - cpp/033space | 90 ++++++++++ cpp/033trace | 21 --- cpp/034scenario_trace | 101 ----------- cpp/034space_surround | 50 ++++++ cpp/035literal_string | 91 ++++++++++ cpp/035scenario_trace_test.mu | 27 --- cpp/036closure_name | 142 --------------- cpp/036length | 30 ++++ cpp/040scenario | 132 ++++++++++++++ cpp/040string.mu | 96 ---------- cpp/041scenario_test.mu | 9 + cpp/042trace | 21 +++ cpp/043scenario_trace | 101 +++++++++++ cpp/044scenario_trace_test.mu | 27 +++ cpp/045closure_name | 142 +++++++++++++++ cpp/050string.mu | 96 ++++++++++ 40 files changed, 2075 insertions(+), 2075 deletions(-) delete mode 100644 cpp/018container delete mode 100644 cpp/019address delete mode 100644 cpp/020array create mode 100644 cpp/020container create mode 100644 cpp/021address delete mode 100644 cpp/021call create mode 100644 cpp/022array delete mode 100644 cpp/022call_ingredient delete mode 100644 cpp/023call_reply delete mode 100644 cpp/024brace create mode 100644 cpp/025call delete mode 100644 cpp/025name create mode 100644 cpp/026call_ingredient delete mode 100644 cpp/026new create mode 100644 cpp/027call_reply delete mode 100644 cpp/027space delete mode 100644 cpp/028space_surround delete mode 100644 cpp/029literal_string create mode 100644 cpp/030brace delete mode 100644 cpp/030length create mode 100644 cpp/031name delete mode 100644 cpp/031scenario create mode 100644 cpp/032new delete mode 100644 cpp/032scenario_test.mu create mode 100644 cpp/033space delete mode 100644 cpp/033trace delete mode 100644 cpp/034scenario_trace create mode 100644 cpp/034space_surround create mode 100644 cpp/035literal_string delete mode 100644 cpp/035scenario_trace_test.mu delete mode 100644 cpp/036closure_name create mode 100644 cpp/036length create mode 100644 cpp/040scenario delete mode 100644 cpp/040string.mu create mode 100644 cpp/041scenario_test.mu create mode 100644 cpp/042trace create mode 100644 cpp/043scenario_trace create mode 100644 cpp/044scenario_trace_test.mu create mode 100644 cpp/045closure_name create mode 100644 cpp/050string.mu diff --git a/cpp/018container b/cpp/018container deleted file mode 100644 index ef6b0057..00000000 --- a/cpp/018container +++ /dev/null @@ -1,170 +0,0 @@ -//: Containers contain a fixed number of elements of different types. -:(before "End Mu Types Initialization") -//: We'll use this container as a running example, with two integer elements. -int point = Type_number["point"] = Next_type_number++; -Type[point].size = 2; -Type[point].kind = container; -Type[point].name = "point"; -vector i; -i.push_back(integer); -Type[point].elements.push_back(i); -Type[point].elements.push_back(i); - -:(scenario copy_multiple_locations) -# Containers can be copied around with a single instruction just like integers, -# no matter how large they are. -recipe main [ - 1:integer <- copy 34:literal - 2:integer <- copy 35:literal - 3:point <- copy 1:point -] -+run: ingredient 0 is 1 -+mem: location 1 is 34 -+mem: location 2 is 35 -+mem: storing 34 in location 3 -+mem: storing 35 in location 4 - -:(before "End Mu Types Initialization") -// A more complex container, containing another container as one of its -// elements. -int point_integer = Type_number["point-integer"] = Next_type_number++; -Type[point_integer].size = 2; -Type[point_integer].kind = container; -Type[point_integer].name = "point-integer"; -vector p2; -p2.push_back(point); -Type[point_integer].elements.push_back(p2); -vector i2; -i2.push_back(integer); -Type[point_integer].elements.push_back(i2); - -:(scenario "copy_handles_nested_container_elements") -recipe main [ - 12:integer <- copy 34:literal - 13:integer <- copy 35:literal - 14:integer <- copy 36:literal - 15:point-integer <- copy 12:point-integer -] -+mem: storing 36 in location 17 - -:(before "End size_of(types) Cases") -type_info t = Type[types[0]]; -if (t.kind == container) { - size_t result = 0; - for (size_t i = 0; i < t.elements.size(); ++i) { - result += size_of(t.elements[i]); - } - return result; -} - -//: To access elements of a container, use 'get' -:(scenario "get") -recipe main [ - 12:integer <- copy 34:literal - 13:integer <- copy 35:literal - 15:integer <- get 12:point, 1:offset -] -+run: instruction main/2 -+run: ingredient 0 is 12 -+run: ingredient 1 is 1 -+run: address to copy is 13 -+run: its type is 1 -+mem: location 13 is 35 -+run: product 0 is 35 -+mem: storing 35 in location 15 - -:(before "End Globals") -const int GET = 18; -:(before "End Primitive Recipe Numbers") -Recipe_number["get"] = GET; -assert(Next_recipe_number == GET); -Next_recipe_number++; -:(before "End Primitive Recipe Implementations") -case GET: { - trace("run") << "ingredient 0 is " << instructions[pc].ingredients[0].name; - reagent base = instructions[pc].ingredients[0]; - int base_address = base.value; - int base_type = base.types[0]; - assert(Type[base_type].kind == container); - trace("run") << "ingredient 1 is " << instructions[pc].ingredients[1].name; - assert(isa_literal(instructions[pc].ingredients[1])); - size_t offset = instructions[pc].ingredients[1].value; - int src = base_address; - for (size_t i = 0; i < offset; ++i) { - src += size_of(Type[base_type].elements[i]); - } - trace("run") << "address to copy is " << src; - assert(Type[base_type].kind == container); - assert(Type[base_type].elements.size() > offset); - int src_type = Type[base_type].elements[offset][0]; - trace("run") << "its type is " << src_type; - reagent tmp; - tmp.set_value(src); - tmp.types.push_back(src_type); - vector result(read_memory(tmp)); - trace("run") << "product 0 is " << result[0]; - write_memory(instructions[pc].products[0], result); - break; -} - -//: 'get' requires a literal in ingredient 1. We'll use a synonym called -//: 'offset' -:(before "End Mu Types Initialization") -Type_number["offset"] = 0; - -:(scenario "get_handles_nested_container_elements") -recipe main [ - 12:integer <- copy 34:literal - 13:integer <- copy 35:literal - 14:integer <- copy 36:literal - 15:integer <- get 12:point-integer, 1:offset -] -+run: instruction main/2 -+run: ingredient 0 is 12 -+run: ingredient 1 is 1 -+run: address to copy is 14 -+run: its type is 1 -+mem: location 14 is 36 -+run: product 0 is 36 -+mem: storing 36 in location 15 - -:(before "End Globals") -// To write to elements of containers, you need their address. -const int GET_ADDRESS = 19; -:(before "End Primitive Recipe Numbers") -Recipe_number["get-address"] = GET_ADDRESS; -assert(Next_recipe_number == GET_ADDRESS); -Next_recipe_number++; -:(before "End Primitive Recipe Implementations") -case GET_ADDRESS: { - trace("run") << "ingredient 0 is " << instructions[pc].ingredients[0].name; - reagent base = instructions[pc].ingredients[0]; - int base_address = base.value; - int base_type = base.types[0]; - assert(Type[base_type].kind == container); - trace("run") << "ingredient 1 is " << instructions[pc].ingredients[1].name; - assert(isa_literal(instructions[pc].ingredients[1])); - size_t offset = instructions[pc].ingredients[1].value; - int src = base_address; - for (size_t i = 0; i < offset; ++i) { - src += size_of(Type[base_type].elements[i]); - } - trace("run") << "address to copy is " << src; - vector result; - result.push_back(src); - trace("run") << "product 0 is " << result[0]; - write_memory(instructions[pc].products[0], result); - break; -} - -:(scenario "get_address") -recipe main [ - 12:integer <- copy 34:literal - 13:integer <- copy 35:literal - 15:address:integer <- get-address 12:point, 1:offset -] -+run: instruction main/2 -+run: ingredient 0 is 12 -+run: ingredient 1 is 1 -+run: address to copy is 13 -+mem: storing 13 in location 15 diff --git a/cpp/019address b/cpp/019address deleted file mode 100644 index 22355fc6..00000000 --- a/cpp/019address +++ /dev/null @@ -1,117 +0,0 @@ -//: Instructions can read from addresses pointing at other locations using the -//: 'deref' property. -:(scenario "copy_indirect") -recipe main [ - 1:address:integer <- copy 2:literal - 2:integer <- copy 34:literal - # This loads location 1 as an address and looks up *that* location. - 3:integer <- copy 1:address:integer/deref -] -+run: instruction main/2 -+mem: location 1 is 2 -+mem: location 2 is 34 -+mem: storing 34 in location 3 - -:(before "int base = x.value" following "vector read_memory(reagent x)") -x = canonize(x); - -//: similarly, write to addresses pointing at other locations using the -//: 'deref' property -:(scenario "store_indirect") -recipe main [ - 1:address:integer <- copy 2:literal - 1:address:integer/deref <- copy 34:literal -] -+run: instruction main/1 -+mem: location 1 is 2 -+mem: storing 34 in location 2 - -:(before "int base = x.value" following "void write_memory(reagent x, vector data)") -x = canonize(x); - -:(code) -reagent canonize(reagent x) { -//? cout << "canonize\n"; //? 1 - reagent r = x; -//? cout << x.to_string() << " => " << r.to_string() << '\n'; //? 1 - while (has_property(r, "deref")) - r = deref(r); - return r; -} - -bool has_property(reagent x, string name) { - for (size_t i = 0; i < x.properties.size(); ++i) { - if (x.properties[i].first == name) return true; - } - return false; -} - -reagent deref(reagent x) { -//? cout << "deref: " << x.to_string() << "\n"; //? 2 - static const int ADDRESS = Type_number["address"]; - reagent result; - assert(x.types[0] == ADDRESS); - - // compute value - result.set_value(Memory[x.value]); - trace("mem") << "location " << x.value << " is " << result.value; - - // populate types - copy(++x.types.begin(), x.types.end(), inserter(result.types, result.types.begin())); - - // drop-one 'deref' - int i = 0; - int len = x.properties.size(); - for (i = 0; i < len; ++i) { - if (x.properties[i].first == "deref") break; - result.properties.push_back(x.properties[i]); - } - ++i; // skip first deref - for (; i < len; ++i) { - result.properties.push_back(x.properties[i]); - } - return result; -} - -//: 'get' can read from container address -:(scenario "get_indirect") -recipe main [ - 1:integer <- copy 2:literal - 2:integer <- copy 34:literal - 3:integer <- copy 35:literal - 4:integer <- get 1:address:point/deref, 0:offset -] -+run: instruction main/3 -+run: address to copy is 2 -+run: product 0 is 34 -+mem: storing 34 in location 4 - -:(scenario "include_nonderef_properties") -recipe main [ - 1:integer <- copy 2:literal - 2:integer <- copy 34:literal - 3:integer <- copy 35:literal - 4:integer <- get 1:address:point/deref/foo, 0:offset -] -+run: instruction main/3 -+run: address to copy is 2 -+run: product 0 is 34 -+mem: storing 34 in location 4 - -:(after "reagent base = " following "case GET:") -base = canonize(base); - -:(scenario "get_address_indirect") -# 'get' can read from container address -recipe main [ - 1:integer <- copy 2:literal - 2:integer <- copy 34:literal - 3:integer <- copy 35:literal - 4:integer <- get-address 1:address:point/deref, 0:offset -] -+run: instruction main/3 -+run: address to copy is 2 -+run: product 0 is 2 - -:(after "reagent base = " following "case GET_ADDRESS:") -base = canonize(base); diff --git a/cpp/020array b/cpp/020array deleted file mode 100644 index 9c2f1439..00000000 --- a/cpp/020array +++ /dev/null @@ -1,157 +0,0 @@ -//: Arrays contain a variable number of elements of the same type. -:(scenario copy_array) -# Arrays can be copied around with a single instruction just like integers, -# no matter how large they are. -recipe main [ - 1:integer <- copy 3:literal - 2:integer <- copy 14:literal - 3:integer <- copy 15:literal - 4:integer <- copy 16:literal - 5:array:integer <- copy 1:array:integer -] -+run: instruction main/4 -+run: ingredient 0 is 1 -+mem: location 1 is 3 -+mem: location 2 is 14 -+mem: location 3 is 15 -+mem: location 4 is 16 -+mem: storing 3 in location 5 -+mem: storing 14 in location 6 -+mem: storing 15 in location 7 -+mem: storing 16 in location 8 - -//: disable the size mismatch check since the destination array need not be initialized -:(replace "if (size_of(x) != data.size())" following "void write_memory(reagent x, vector data)") -if (x.types[0] != Type_number["array"] && size_of(x) != data.size()) -:(after "size_t size_of(const reagent& r)") - static const int ARRAY = Type_number["array"]; - if (r.types[0] == ARRAY) { - assert(r.types.size() > 1); - // skip the 'array' type to get at the element type - return 1 + Memory[r.value]*size_of(array_element(r.types)); - } - -//: array elements are accessed using 'index' -:(scenario "index") -recipe main [ - 1:integer <- copy 3:literal - 2:integer <- copy 14:literal - 3:integer <- copy 15:literal - 4:integer <- copy 16:literal - 5:integer <- index 1:array:integer, 0:literal -] -+run: instruction main/4 -+run: address to copy is 2 -+run: its type is 1 -+mem: location 2 is 14 -+run: product 0 is 14 -+mem: storing 14 in location 5 - -//: array elements are accessed using 'index' -:(scenario "index_direct_offset") -recipe main [ - 1:integer <- copy 3:literal - 2:integer <- copy 14:literal - 3:integer <- copy 15:literal - 4:integer <- copy 16:literal - 5:integer <- copy 0:literal - 6:integer <- index 1:array:integer, 5:integer -] -+run: instruction main/5 -+run: address to copy is 2 -+run: its type is 1 -+mem: location 2 is 14 -+run: product 0 is 14 -+mem: storing 14 in location 6 - -:(before "End Globals") -// Operator to look at elements of arrays. -const int INDEX = 20; -:(before "End Primitive Recipe Numbers") -Recipe_number["index"] = INDEX; -assert(Next_recipe_number == INDEX); -Next_recipe_number++; -:(before "End Primitive Recipe Implementations") -case INDEX: { - static const int ARRAY = Type_number["array"]; -//? if (Trace_stream) Trace_stream->dump_layer = "run"; //? 1 - trace("run") << "ingredient 0 is " << instructions[pc].ingredients[0].to_string(); - reagent base = canonize(instructions[pc].ingredients[0]); -//? trace("run") << "ingredient 0 after canonize: " << instructions[pc].ingredients[0].to_string(); //? 1 - int base_address = base.value; - assert(base.types[0] == ARRAY); - trace("run") << "ingredient 1 is " << instructions[pc].ingredients[1].to_string(); - reagent offset = canonize(instructions[pc].ingredients[1]); - vector offset_val(read_memory(offset)); - vector element_type = array_element(base.types); - int src = base_address + 1 + offset_val[0]*size_of(element_type); - trace("run") << "address to copy is " << src; - trace("run") << "its type is " << element_type[0]; - reagent tmp; - tmp.set_value(src); - copy(element_type.begin(), element_type.end(), inserter(tmp.types, tmp.types.begin())); - tmp.properties.push_back(pair >("raw", vector())); -//? cout << "AAA: " << tmp.to_string() << '\n'; //? 2 - vector result(read_memory(tmp)); - trace("run") << "product 0 is " << result[0]; - write_memory(instructions[pc].products[0], result); -//? if (Trace_stream) Trace_stream->dump_layer = ""; //? 1 - break; -} - -:(code) -vector array_element(const vector& types) { - return vector(++types.begin(), types.end()); -} - -:(scenario "index_address") -recipe main [ - 1:integer <- copy 3:literal - 2:integer <- copy 14:literal - 3:integer <- copy 15:literal - 4:integer <- copy 16:literal - 5:integer <- index-address 1:array:integer, 0:literal -] -+run: instruction main/4 -+run: address to copy is 2 -+mem: storing 2 in location 5 - -:(before "End Globals") -// To write to elements of containers, you need their address. -const int INDEX_ADDRESS = 21; -:(before "End Primitive Recipe Numbers") -Recipe_number["index-address"] = INDEX_ADDRESS; -assert(Next_recipe_number == INDEX_ADDRESS); -Next_recipe_number++; -:(before "End Primitive Recipe Implementations") -case INDEX_ADDRESS: { - static const int ARRAY = Type_number["array"]; - trace("run") << "ingredient 0 is " << instructions[pc].ingredients[0].name; - reagent base = canonize(instructions[pc].ingredients[0]); - int base_address = base.value; - assert(base.types[0] == ARRAY); - trace("run") << "ingredient 1 is " << instructions[pc].ingredients[1].to_string(); - reagent offset = canonize(instructions[pc].ingredients[1]); - vector offset_val(read_memory(offset)); - vector element_type = array_element(base.types); - int src = base_address + 1 + offset_val[0]*size_of(element_type); - trace("run") << "address to copy is " << src; - vector result; - result.push_back(src); - trace("run") << "product 0 is " << result[0]; - write_memory(instructions[pc].products[0], result); - break; -} - -:(scenario "index_indirect") -recipe main [ - 1:integer <- copy 3:literal - 2:integer <- copy 14:literal - 3:integer <- copy 15:literal - 4:integer <- copy 16:literal - 5:address:array:integer <- copy 1:literal - 6:integer <- index 5:address:array:integer/deref, 1:literal -] -+run: instruction main/5 -+mem: storing 15 in location 6 -// vim:ft=cpp diff --git a/cpp/020container b/cpp/020container new file mode 100644 index 00000000..ef6b0057 --- /dev/null +++ b/cpp/020container @@ -0,0 +1,170 @@ +//: Containers contain a fixed number of elements of different types. +:(before "End Mu Types Initialization") +//: We'll use this container as a running example, with two integer elements. +int point = Type_number["point"] = Next_type_number++; +Type[point].size = 2; +Type[point].kind = container; +Type[point].name = "point"; +vector i; +i.push_back(integer); +Type[point].elements.push_back(i); +Type[point].elements.push_back(i); + +:(scenario copy_multiple_locations) +# Containers can be copied around with a single instruction just like integers, +# no matter how large they are. +recipe main [ + 1:integer <- copy 34:literal + 2:integer <- copy 35:literal + 3:point <- copy 1:point +] ++run: ingredient 0 is 1 ++mem: location 1 is 34 ++mem: location 2 is 35 ++mem: storing 34 in location 3 ++mem: storing 35 in location 4 + +:(before "End Mu Types Initialization") +// A more complex container, containing another container as one of its +// elements. +int point_integer = Type_number["point-integer"] = Next_type_number++; +Type[point_integer].size = 2; +Type[point_integer].kind = container; +Type[point_integer].name = "point-integer"; +vector p2; +p2.push_back(point); +Type[point_integer].elements.push_back(p2); +vector i2; +i2.push_back(integer); +Type[point_integer].elements.push_back(i2); + +:(scenario "copy_handles_nested_container_elements") +recipe main [ + 12:integer <- copy 34:literal + 13:integer <- copy 35:literal + 14:integer <- copy 36:literal + 15:point-integer <- copy 12:point-integer +] ++mem: storing 36 in location 17 + +:(before "End size_of(types) Cases") +type_info t = Type[types[0]]; +if (t.kind == container) { + size_t result = 0; + for (size_t i = 0; i < t.elements.size(); ++i) { + result += size_of(t.elements[i]); + } + return result; +} + +//: To access elements of a container, use 'get' +:(scenario "get") +recipe main [ + 12:integer <- copy 34:literal + 13:integer <- copy 35:literal + 15:integer <- get 12:point, 1:offset +] ++run: instruction main/2 ++run: ingredient 0 is 12 ++run: ingredient 1 is 1 ++run: address to copy is 13 ++run: its type is 1 ++mem: location 13 is 35 ++run: product 0 is 35 ++mem: storing 35 in location 15 + +:(before "End Globals") +const int GET = 18; +:(before "End Primitive Recipe Numbers") +Recipe_number["get"] = GET; +assert(Next_recipe_number == GET); +Next_recipe_number++; +:(before "End Primitive Recipe Implementations") +case GET: { + trace("run") << "ingredient 0 is " << instructions[pc].ingredients[0].name; + reagent base = instructions[pc].ingredients[0]; + int base_address = base.value; + int base_type = base.types[0]; + assert(Type[base_type].kind == container); + trace("run") << "ingredient 1 is " << instructions[pc].ingredients[1].name; + assert(isa_literal(instructions[pc].ingredients[1])); + size_t offset = instructions[pc].ingredients[1].value; + int src = base_address; + for (size_t i = 0; i < offset; ++i) { + src += size_of(Type[base_type].elements[i]); + } + trace("run") << "address to copy is " << src; + assert(Type[base_type].kind == container); + assert(Type[base_type].elements.size() > offset); + int src_type = Type[base_type].elements[offset][0]; + trace("run") << "its type is " << src_type; + reagent tmp; + tmp.set_value(src); + tmp.types.push_back(src_type); + vector result(read_memory(tmp)); + trace("run") << "product 0 is " << result[0]; + write_memory(instructions[pc].products[0], result); + break; +} + +//: 'get' requires a literal in ingredient 1. We'll use a synonym called +//: 'offset' +:(before "End Mu Types Initialization") +Type_number["offset"] = 0; + +:(scenario "get_handles_nested_container_elements") +recipe main [ + 12:integer <- copy 34:literal + 13:integer <- copy 35:literal + 14:integer <- copy 36:literal + 15:integer <- get 12:point-integer, 1:offset +] ++run: instruction main/2 ++run: ingredient 0 is 12 ++run: ingredient 1 is 1 ++run: address to copy is 14 ++run: its type is 1 ++mem: location 14 is 36 ++run: product 0 is 36 ++mem: storing 36 in location 15 + +:(before "End Globals") +// To write to elements of containers, you need their address. +const int GET_ADDRESS = 19; +:(before "End Primitive Recipe Numbers") +Recipe_number["get-address"] = GET_ADDRESS; +assert(Next_recipe_number == GET_ADDRESS); +Next_recipe_number++; +:(before "End Primitive Recipe Implementations") +case GET_ADDRESS: { + trace("run") << "ingredient 0 is " << instructions[pc].ingredients[0].name; + reagent base = instructions[pc].ingredients[0]; + int base_address = base.value; + int base_type = base.types[0]; + assert(Type[base_type].kind == container); + trace("run") << "ingredient 1 is " << instructions[pc].ingredients[1].name; + assert(isa_literal(instructions[pc].ingredients[1])); + size_t offset = instructions[pc].ingredients[1].value; + int src = base_address; + for (size_t i = 0; i < offset; ++i) { + src += size_of(Type[base_type].elements[i]); + } + trace("run") << "address to copy is " << src; + vector result; + result.push_back(src); + trace("run") << "product 0 is " << result[0]; + write_memory(instructions[pc].products[0], result); + break; +} + +:(scenario "get_address") +recipe main [ + 12:integer <- copy 34:literal + 13:integer <- copy 35:literal + 15:address:integer <- get-address 12:point, 1:offset +] ++run: instruction main/2 ++run: ingredient 0 is 12 ++run: ingredient 1 is 1 ++run: address to copy is 13 ++mem: storing 13 in location 15 diff --git a/cpp/021address b/cpp/021address new file mode 100644 index 00000000..22355fc6 --- /dev/null +++ b/cpp/021address @@ -0,0 +1,117 @@ +//: Instructions can read from addresses pointing at other locations using the +//: 'deref' property. +:(scenario "copy_indirect") +recipe main [ + 1:address:integer <- copy 2:literal + 2:integer <- copy 34:literal + # This loads location 1 as an address and looks up *that* location. + 3:integer <- copy 1:address:integer/deref +] ++run: instruction main/2 ++mem: location 1 is 2 ++mem: location 2 is 34 ++mem: storing 34 in location 3 + +:(before "int base = x.value" following "vector read_memory(reagent x)") +x = canonize(x); + +//: similarly, write to addresses pointing at other locations using the +//: 'deref' property +:(scenario "store_indirect") +recipe main [ + 1:address:integer <- copy 2:literal + 1:address:integer/deref <- copy 34:literal +] ++run: instruction main/1 ++mem: location 1 is 2 ++mem: storing 34 in location 2 + +:(before "int base = x.value" following "void write_memory(reagent x, vector data)") +x = canonize(x); + +:(code) +reagent canonize(reagent x) { +//? cout << "canonize\n"; //? 1 + reagent r = x; +//? cout << x.to_string() << " => " << r.to_string() << '\n'; //? 1 + while (has_property(r, "deref")) + r = deref(r); + return r; +} + +bool has_property(reagent x, string name) { + for (size_t i = 0; i < x.properties.size(); ++i) { + if (x.properties[i].first == name) return true; + } + return false; +} + +reagent deref(reagent x) { +//? cout << "deref: " << x.to_string() << "\n"; //? 2 + static const int ADDRESS = Type_number["address"]; + reagent result; + assert(x.types[0] == ADDRESS); + + // compute value + result.set_value(Memory[x.value]); + trace("mem") << "location " << x.value << " is " << result.value; + + // populate types + copy(++x.types.begin(), x.types.end(), inserter(result.types, result.types.begin())); + + // drop-one 'deref' + int i = 0; + int len = x.properties.size(); + for (i = 0; i < len; ++i) { + if (x.properties[i].first == "deref") break; + result.properties.push_back(x.properties[i]); + } + ++i; // skip first deref + for (; i < len; ++i) { + result.properties.push_back(x.properties[i]); + } + return result; +} + +//: 'get' can read from container address +:(scenario "get_indirect") +recipe main [ + 1:integer <- copy 2:literal + 2:integer <- copy 34:literal + 3:integer <- copy 35:literal + 4:integer <- get 1:address:point/deref, 0:offset +] ++run: instruction main/3 ++run: address to copy is 2 ++run: product 0 is 34 ++mem: storing 34 in location 4 + +:(scenario "include_nonderef_properties") +recipe main [ + 1:integer <- copy 2:literal + 2:integer <- copy 34:literal + 3:integer <- copy 35:literal + 4:integer <- get 1:address:point/deref/foo, 0:offset +] ++run: instruction main/3 ++run: address to copy is 2 ++run: product 0 is 34 ++mem: storing 34 in location 4 + +:(after "reagent base = " following "case GET:") +base = canonize(base); + +:(scenario "get_address_indirect") +# 'get' can read from container address +recipe main [ + 1:integer <- copy 2:literal + 2:integer <- copy 34:literal + 3:integer <- copy 35:literal + 4:integer <- get-address 1:address:point/deref, 0:offset +] ++run: instruction main/3 ++run: address to copy is 2 ++run: product 0 is 2 + +:(after "reagent base = " following "case GET_ADDRESS:") +base = canonize(base); diff --git a/cpp/021call b/cpp/021call deleted file mode 100644 index 3683db12..00000000 --- a/cpp/021call +++ /dev/null @@ -1,77 +0,0 @@ -//: So far the recipes we define can't run each other. Let's change that. -:(scenario "calling_recipe") -recipe main [ - f -] -recipe f [ - 3:integer <- add 2:literal, 2:literal -] -+mem: storing 4 in location 3 - -:(before "struct routine {") -// Everytime a recipe runs another, we interrupt it and start running the new -// recipe. When that finishes, we continue this one where we left off. -// This requires maintaining a 'stack' of interrupted recipes or 'calls'. -struct call { - recipe_number running_recipe; - size_t pc; - // End call Fields - call(recipe_number r) :running_recipe(r), pc(0) {} -}; -typedef stack call_stack; - -:(replace{} "struct routine") -struct routine { - call_stack calls; - // End routine Fields - routine(recipe_number r); -}; -:(code) - routine::routine(recipe_number r) { - calls.push(call(r)); - } -//: now update routine's helpers -:(replace{} "inline size_t& running_at(routine& rr)") -inline size_t& running_at(routine& rr) { - return rr.calls.top().pc; -} -:(replace{} "inline string recipe_name(routine& rr)") -inline string recipe_name(routine& rr) { - return Recipe[rr.calls.top().running_recipe].name; -} -:(replace{} "inline vector& steps(routine& rr)") -inline vector& steps(routine& rr) { - return Recipe[rr.calls.top().running_recipe].steps; -} - -:(replace{} "default:" following "End Primitive Recipe Implementations") -default: { - // not a primitive; try to look for a matching recipe - if (Recipe.find(instructions[pc].operation) == Recipe.end()) { - raise << "undefined operation " << instructions[pc].operation << ": " << instructions[pc].name << '\n'; - break; - } - rr.calls.push(call(instructions[pc].operation)); - continue; // not done with caller; don't increment pc -} - -//: finally, we need to fix the termination conditions for the run loop - -:(replace{} "inline bool done(routine& rr)") -inline bool done(routine& rr) { - return rr.calls.empty(); -} - -:(before "Running one instruction") -// when we reach the end of one call, we may reach the end of the one below -// it, and the one below that, and so on -while (running_at(rr) >= steps(rr).size()) { - rr.calls.pop(); - if (rr.calls.empty()) return; - // todo: no results returned warning - ++running_at(rr); -} - -:(before "End Includes") -#include -using std::stack; diff --git a/cpp/022array b/cpp/022array new file mode 100644 index 00000000..9c2f1439 --- /dev/null +++ b/cpp/022array @@ -0,0 +1,157 @@ +//: Arrays contain a variable number of elements of the same type. +:(scenario copy_array) +# Arrays can be copied around with a single instruction just like integers, +# no matter how large they are. +recipe main [ + 1:integer <- copy 3:literal + 2:integer <- copy 14:literal + 3:integer <- copy 15:literal + 4:integer <- copy 16:literal + 5:array:integer <- copy 1:array:integer +] ++run: instruction main/4 ++run: ingredient 0 is 1 ++mem: location 1 is 3 ++mem: location 2 is 14 ++mem: location 3 is 15 ++mem: location 4 is 16 ++mem: storing 3 in location 5 ++mem: storing 14 in location 6 ++mem: storing 15 in location 7 ++mem: storing 16 in location 8 + +//: disable the size mismatch check since the destination array need not be initialized +:(replace "if (size_of(x) != data.size())" following "void write_memory(reagent x, vector data)") +if (x.types[0] != Type_number["array"] && size_of(x) != data.size()) +:(after "size_t size_of(const reagent& r)") + static const int ARRAY = Type_number["array"]; + if (r.types[0] == ARRAY) { + assert(r.types.size() > 1); + // skip the 'array' type to get at the element type + return 1 + Memory[r.value]*size_of(array_element(r.types)); + } + +//: array elements are accessed using 'index' +:(scenario "index") +recipe main [ + 1:integer <- copy 3:literal + 2:integer <- copy 14:literal + 3:integer <- copy 15:literal + 4:integer <- copy 16:literal + 5:integer <- index 1:array:integer, 0:literal +] ++run: instruction main/4 ++run: address to copy is 2 ++run: its type is 1 ++mem: location 2 is 14 ++run: product 0 is 14 ++mem: storing 14 in location 5 + +//: array elements are accessed using 'index' +:(scenario "index_direct_offset") +recipe main [ + 1:integer <- copy 3:literal + 2:integer <- copy 14:literal + 3:integer <- copy 15:literal + 4:integer <- copy 16:literal + 5:integer <- copy 0:literal + 6:integer <- index 1:array:integer, 5:integer +] ++run: instruction main/5 ++run: address to copy is 2 ++run: its type is 1 ++mem: location 2 is 14 ++run: product 0 is 14 ++mem: storing 14 in location 6 + +:(before "End Globals") +// Operator to look at elements of arrays. +const int INDEX = 20; +:(before "End Primitive Recipe Numbers") +Recipe_number["index"] = INDEX; +assert(Next_recipe_number == INDEX); +Next_recipe_number++; +:(before "End Primitive Recipe Implementations") +case INDEX: { + static const int ARRAY = Type_number["array"]; +//? if (Trace_stream) Trace_stream->dump_layer = "run"; //? 1 + trace("run") << "ingredient 0 is " << instructions[pc].ingredients[0].to_string(); + reagent base = canonize(instructions[pc].ingredients[0]); +//? trace("run") << "ingredient 0 after canonize: " << instructions[pc].ingredients[0].to_string(); //? 1 + int base_address = base.value; + assert(base.types[0] == ARRAY); + trace("run") << "ingredient 1 is " << instructions[pc].ingredients[1].to_string(); + reagent offset = canonize(instructions[pc].ingredients[1]); + vector offset_val(read_memory(offset)); + vector element_type = array_element(base.types); + int src = base_address + 1 + offset_val[0]*size_of(element_type); + trace("run") << "address to copy is " << src; + trace("run") << "its type is " << element_type[0]; + reagent tmp; + tmp.set_value(src); + copy(element_type.begin(), element_type.end(), inserter(tmp.types, tmp.types.begin())); + tmp.properties.push_back(pair >("raw", vector())); +//? cout << "AAA: " << tmp.to_string() << '\n'; //? 2 + vector result(read_memory(tmp)); + trace("run") << "product 0 is " << result[0]; + write_memory(instructions[pc].products[0], result); +//? if (Trace_stream) Trace_stream->dump_layer = ""; //? 1 + break; +} + +:(code) +vector array_element(const vector& types) { + return vector(++types.begin(), types.end()); +} + +:(scenario "index_address") +recipe main [ + 1:integer <- copy 3:literal + 2:integer <- copy 14:literal + 3:integer <- copy 15:literal + 4:integer <- copy 16:literal + 5:integer <- index-address 1:array:integer, 0:literal +] ++run: instruction main/4 ++run: address to copy is 2 ++mem: storing 2 in location 5 + +:(before "End Globals") +// To write to elements of containers, you need their address. +const int INDEX_ADDRESS = 21; +:(before "End Primitive Recipe Numbers") +Recipe_number["index-address"] = INDEX_ADDRESS; +assert(Next_recipe_number == INDEX_ADDRESS); +Next_recipe_number++; +:(before "End Primitive Recipe Implementations") +case INDEX_ADDRESS: { + static const int ARRAY = Type_number["array"]; + trace("run") << "ingredient 0 is " << instructions[pc].ingredients[0].name; + reagent base = canonize(instructions[pc].ingredients[0]); + int base_address = base.value; + assert(base.types[0] == ARRAY); + trace("run") << "ingredient 1 is " << instructions[pc].ingredients[1].to_string(); + reagent offset = canonize(instructions[pc].ingredients[1]); + vector offset_val(read_memory(offset)); + vector element_type = array_element(base.types); + int src = base_address + 1 + offset_val[0]*size_of(element_type); + trace("run") << "address to copy is " << src; + vector result; + result.push_back(src); + trace("run") << "product 0 is " << result[0]; + write_memory(instructions[pc].products[0], result); + break; +} + +:(scenario "index_indirect") +recipe main [ + 1:integer <- copy 3:literal + 2:integer <- copy 14:literal + 3:integer <- copy 15:literal + 4:integer <- copy 16:literal + 5:address:array:integer <- copy 1:literal + 6:integer <- index 5:address:array:integer/deref, 1:literal +] ++run: instruction main/5 ++mem: storing 15 in location 6 +// vim:ft=cpp diff --git a/cpp/022call_ingredient b/cpp/022call_ingredient deleted file mode 100644 index 963c5fe8..00000000 --- a/cpp/022call_ingredient +++ /dev/null @@ -1,44 +0,0 @@ -//: Calls can take ingredients just like primitives. To access a recipe's -//: ingredients, use 'next-ingredient'. -:(scenario "next_ingredient") -recipe main [ - f 2:literal -] -recipe f [ - 12:integer <- next-ingredient - 13:integer <- add 1:literal, 12:integer -] -+run: instruction f/1 -+mem: location 12 is 2 -+mem: storing 3 in location 13 - -:(before "End call Fields") -vector > ingredient_atoms; -size_t next_ingredient_to_process; -:(replace{} "call(recipe_number r)") -call(recipe_number r) :running_recipe(r), pc(0), next_ingredient_to_process(0) {} - -:(replace "rr.calls.push(call(instructions[pc].operation))" following "End Primitive Recipe Implementations") -call callee(instructions[pc].operation); -for (vector::iterator p = instructions[pc].ingredients.begin(); p != instructions[pc].ingredients.end(); ++p) { - callee.ingredient_atoms.push_back(read_memory(*p)); -} -rr.calls.push(callee); - -:(before "End Globals") -const int NEXT_INGREDIENT = 22; -:(before "End Primitive Recipe Numbers") -Recipe_number["next-ingredient"] = NEXT_INGREDIENT; -assert(Next_recipe_number == NEXT_INGREDIENT); -Next_recipe_number++; -:(before "End Primitive Recipe Implementations") -case NEXT_INGREDIENT: { - if (rr.calls.top().next_ingredient_to_process < rr.calls.top().ingredient_atoms.size()) { - trace("run") << "product 0 is " - << rr.calls.top().ingredient_atoms[rr.calls.top().next_ingredient_to_process][0]; - write_memory(instructions[pc].products[0], - rr.calls.top().ingredient_atoms[rr.calls.top().next_ingredient_to_process]); - ++rr.calls.top().next_ingredient_to_process; - } - break; -} diff --git a/cpp/023call_reply b/cpp/023call_reply deleted file mode 100644 index feb549e6..00000000 --- a/cpp/023call_reply +++ /dev/null @@ -1,72 +0,0 @@ -//: Calls can also generate results, using 'reply'. -:(scenario "reply") -recipe main [ - 3:integer, 4:integer <- f 2:literal -] -recipe f [ - 12:integer <- next-ingredient - 13:integer <- add 1:literal, 12:integer - reply 12:integer, 13:integer -] -+run: instruction main/0 -+run: result 0 is 2 -+mem: storing 2 in location 3 -+run: result 1 is 3 -+mem: storing 3 in location 4 - -:(before "End Globals") -const int REPLY = 23; -:(before "End Primitive Recipe Numbers") -Recipe_number["reply"] = REPLY; -assert(Next_recipe_number == REPLY); -Next_recipe_number++; -:(before "End Primitive Recipe Implementations") -case REPLY: { - vector > callee_results; - for (size_t i = 0; i < instructions[pc].ingredients.size(); ++i) { - callee_results.push_back(read_memory(instructions[pc].ingredients[i])); - } - rr.calls.pop(); - assert(!rr.calls.empty()); - size_t& caller_pc = rr.calls.top().pc; - instruction& caller_instruction = Recipe[rr.calls.top().running_recipe].steps[caller_pc]; - assert(caller_instruction.products.size() <= callee_results.size()); - for (size_t i = 0; i < caller_instruction.products.size(); ++i) { - trace("run") << "result " << i << " is " << to_string(callee_results[i]); - write_memory(caller_instruction.products[i], callee_results[i]); - } - ++caller_pc; - break; -} - -//: Results can include containers and exclusive containers, addresses and arrays. -:(scenario "reply_container") -recipe main [ - 3:point <- f 2:literal -] -recipe f [ - 12:integer <- next-ingredient - 13:integer <- copy 35:literal - reply 12:point -] -+run: instruction main/0 -+run: result 0 is [2, 35] -+mem: storing 2 in location 3 -+mem: storing 35 in location 4 - -:(code) -string to_string(const vector& in) { - if (in.empty()) return "[]"; - ostringstream out; - if (in.size() == 1) { - out << in[0]; - return out.str(); - } - out << "["; - for (size_t i = 0; i < in.size(); ++i) { - if (i > 0) out << ", "; - out << in[i]; - } - out << "]"; - return out.str(); -} diff --git a/cpp/024brace b/cpp/024brace deleted file mode 100644 index 0d68b54f..00000000 --- a/cpp/024brace +++ /dev/null @@ -1,398 +0,0 @@ -//: Structured programming -//: -//: Our jump operators are quite inconvenient to use, so mu provides a -//: lightweight tool called 'transform_braces' to work in a slightly more -//: convenient format with nested braces: -//: -//: { -//: some instructions -//: { -//: more instructions -//: } -//: } -//: -//: Braces are just labels, they require no special parsing. The pseudo -//: recipes 'loop' and 'break' jump to just after the enclosing '{' and '}' -//: respectively. -//: -//: Conditional and unconditional 'loop' and 'break' should give us 80% of the -//: benefits of the control-flow primitives we're used to in other languages, -//: like 'if', 'while', 'for', etc. - -:(scenarios transform_test) -:(scenario "brace_conversion") -recipe main [ - { - break - 1:integer <- copy 0:literal - } -] -+after-brace: recipe main -+after-brace: jump 1:offset -+after-brace: copy ... - -//: one-time setup -:(after "int main") - Transform.push_back(transform_braces); - -:(code) -void transform_braces(const recipe_number r) { -//? cout << "AAA transform_braces\n"; //? 1 -//? exit(0); //? 1 - const int OPEN = 0, CLOSE = 1; - list > braces; - for (size_t index = 0; index < Recipe[r].steps.size(); ++index) { - const instruction& inst = Recipe[r].steps[index]; - if (inst.label == "{") { - trace("brace") << r << ": push (open, " << index << ")"; - braces.push_back(pair(OPEN, index)); - } - if (inst.label == "}") { - trace("brace") << "push (close, " << index << ")"; - braces.push_back(pair(CLOSE, index)); - } - } - stack open_braces; - trace("after-brace") << "recipe " << Recipe[r].name; - for (size_t index = 0; index < Recipe[r].steps.size(); ++index) { - instruction& inst = Recipe[r].steps[index]; -//? cout << "AAA " << inst.name << ": " << inst.operation << '\n'; //? 1 - if (inst.label == "{") open_braces.push(index); - else if (inst.label == "}") open_braces.pop(); - else if (inst.is_label) - ; // do nothing - else if (inst.operation == Recipe_number["loop"]) { - inst.operation = Recipe_number["jump"]; - if (inst.ingredients.size() > 0 && isa_literal(inst.ingredients[0])) { - // explicit target; a later phase will handle it - trace("after-brace") << "jump " << inst.ingredients[0].name << ":offset"; - } - else { - reagent ing; - ing.set_value(open_braces.top()-index); - inst.ingredients.push_back(ing); - trace("after-brace") << "jump " << ing.value << ":offset"; - trace("after-brace") << index << ": " << ing.to_string(); - trace("after-brace") << index << ": " << Recipe[r].steps[index].ingredients[0].to_string(); - } - } - else if (inst.operation == Recipe_number["break"]) { - inst.operation = Recipe_number["jump"]; - if (inst.ingredients.size() > 0 && isa_literal(inst.ingredients[0])) { - // explicit target; a later phase will handle it - trace("after-brace") << "jump " << inst.ingredients[0].name << ":offset"; - } - else { - reagent ing; - ing.set_value(matching_brace(open_braces.top(), braces) - index - 1); - inst.ingredients.push_back(ing); - trace("after-brace") << "jump " << ing.value << ":offset"; - } - } - else if (inst.operation == Recipe_number["loop-if"]) { - inst.operation = Recipe_number["jump-if"]; - if (inst.ingredients.size() > 1 && isa_literal(inst.ingredients[1])) { - // explicit target; a later phase will handle it - trace("after-brace") << "jump " << inst.ingredients[1].name << ":offset"; - } - else { - reagent ing; - ing.set_value(open_braces.top()-index); - inst.ingredients.push_back(ing); - trace("after-brace") << "jump-if " << inst.ingredients[0].name << ", " << ing.value << ":offset"; - } - } - else if (inst.operation == Recipe_number["break-if"]) { - inst.operation = Recipe_number["jump-if"]; - if (inst.ingredients.size() > 1 && isa_literal(inst.ingredients[1])) { - // explicit target; a later phase will handle it - trace("after-brace") << "jump " << inst.ingredients[1].name << ":offset"; - } - else { - reagent ing; - ing.set_value(matching_brace(open_braces.top(), braces) - index - 1); - inst.ingredients.push_back(ing); - trace("after-brace") << "jump-if " << inst.ingredients[0].name << ", " << ing.value << ":offset"; - } - } - else if (inst.operation == Recipe_number["loop-unless"]) { - inst.operation = Recipe_number["jump-unless"]; - if (inst.ingredients.size() > 1 && isa_literal(inst.ingredients[1])) { - // explicit target; a later phase will handle it - trace("after-brace") << "jump " << inst.ingredients[1].name << ":offset"; - } - else { - reagent ing; - ing.set_value(open_braces.top()-index); - inst.ingredients.push_back(ing); - trace("after-brace") << "jump-unless " << inst.ingredients[0].name << ", " << ing.value << ":offset"; - } - } - else if (inst.operation == Recipe_number["break-unless"]) { -//? cout << "AAA break-unless\n"; //? 1 - inst.operation = Recipe_number["jump-unless"]; - if (inst.ingredients.size() > 1 && isa_literal(inst.ingredients[1])) { - // explicit target; a later phase will handle it - trace("after-brace") << "jump " << inst.ingredients[1].name << ":offset"; - } - else { - reagent ing; - ing.set_value(matching_brace(open_braces.top(), braces) - index - 1); - inst.ingredients.push_back(ing); - trace("after-brace") << "jump-unless " << inst.ingredients[0].name << ", " << ing.value << ":offset"; - } - } - else { - trace("after-brace") << inst.name << " ..."; - } - } -} - -size_t matching_brace(size_t index, const list >& braces) { - int stacksize; - for (list >::const_iterator p = braces.begin(); p != braces.end(); ++p) { - if (p->second < index) continue; - stacksize += (p->first ? 1 : -1); - if (stacksize == 0) return p->second; - } - assert(false); - return -1; -} - -// temporarily suppress run -void transform_test(string form) { -//? cout << "AAA transform_test {\n"; //? 1 - vector tmp = add_recipes(form); -//? cout << "AAA done adding recipes\n"; //? 1 - transform_all(); -//? cout << "AAA }\n"; //? 1 -} - -//: Make sure these pseudo recipes get consistent numbers, even though they aren't -//: implemented. -:(before "End Globals") -const int BREAK = 24; -const int BREAK_IF = 25; -const int BREAK_UNLESS = 26; -const int LOOP = 27; -const int LOOP_IF = 28; -const int LOOP_UNLESS = 29; -:(before "End Primitive Recipe Numbers") -Recipe_number["break"] = BREAK; -assert(Next_recipe_number == BREAK); -Next_recipe_number++; -Recipe_number["break-if"] = BREAK_IF; -assert(Next_recipe_number == BREAK_IF); -Next_recipe_number++; -Recipe_number["break-unless"] = BREAK_UNLESS; -assert(Next_recipe_number == BREAK_UNLESS); -Next_recipe_number++; -Recipe_number["loop"] = LOOP; -assert(Next_recipe_number == LOOP); -Next_recipe_number++; -Recipe_number["loop-if"] = LOOP_IF; -assert(Next_recipe_number == LOOP_IF); -Next_recipe_number++; -Recipe_number["loop-unless"] = LOOP_UNLESS; -assert(Next_recipe_number == LOOP_UNLESS); -Next_recipe_number++; - -:(scenario "loop") -recipe main [ - 1:integer <- copy 0:literal - 2:integer <- copy 0:literal - { - 3:integer <- copy 0:literal - loop - } -] -+after-brace: recipe main -+after-brace: copy ... -+after-brace: copy ... -+after-brace: copy ... -+after-brace: jump -2:offset - -:(scenario "break_empty_block") -recipe main [ - 1:integer <- copy 0:literal - { - break - } -] -+after-brace: recipe main -+after-brace: copy ... -+after-brace: jump 0:offset - -:(scenario "break_cascading") -recipe main [ - 1:integer <- copy 0:literal - { - break - } - { - break - } -] -+after-brace: recipe main -+after-brace: copy ... -+after-brace: jump 0:offset -+after-brace: jump 0:offset - -:(scenario "break_cascading2") -recipe main [ - 1:integer <- copy 0:literal - 2:integer <- copy 0:literal - { - break - 3:integer <- copy 0:literal - } - { - break - } -] -+after-brace: recipe main -+after-brace: copy ... -+after-brace: copy ... -+after-brace: jump 1:offset -+after-brace: copy ... -+after-brace: jump 0:offset - -:(scenario "break_if") -recipe main [ - 1:integer <- copy 0:literal - 2:integer <- copy 0:literal - { - break-if 2:integer - 3:integer <- copy 0:literal - } - { - break - } -] -+after-brace: recipe main -+after-brace: copy ... -+after-brace: copy ... -+after-brace: jump-if 2, 1:offset -+after-brace: copy ... -+after-brace: jump 0:offset - -:(scenario "break_nested") -recipe main [ - 1:integer <- copy 0:literal - { - 2:integer <- copy 0:literal - break - { - 3:integer <- copy 0:literal - } - 4:integer <- copy 0:literal - } -] -+after-brace: jump 4:offset - -:(scenario "break_nested_degenerate") -recipe main [ - 1:integer <- copy 0:literal - { - 2:integer <- copy 0:literal - break - { - } - 4:integer <- copy 0:literal - } -] -+after-brace: jump 3:offset - -:(scenario "break_nested_degenerate2") -recipe main [ - 1:integer <- copy 0:literal - { - 2:integer <- copy 0:literal - break - { - } - } -] -+after-brace: jump 2:offset - -:(scenario "break_label") -recipe main [ - 1:integer <- copy 0:literal - { - break +foo:offset - } -] -+after-brace: jump +foo:offset - -:(scenario "break_unless") -recipe main [ - 1:integer <- copy 0:literal - 2:integer <- copy 0:literal - { - break-unless 2:integer - 3:integer <- copy 0:literal - } -] -+after-brace: recipe main -+after-brace: copy ... -+after-brace: copy ... -+after-brace: jump-unless 2, 1:offset -+after-brace: copy ... - -:(scenario "loop_unless") -recipe main [ - 1:integer <- copy 0:literal - 2:integer <- copy 0:literal - { - loop-unless 2:integer - 3:integer <- copy 0:literal - } -] -+after-brace: recipe main -+after-brace: copy ... -+after-brace: copy ... -+after-brace: jump-unless 2, -1:offset -+after-brace: copy ... - -:(scenario "loop_nested") -recipe main [ - 1:integer <- copy 0:literal - { - 2:integer <- copy 0:literal - { - 3:integer <- copy 0:literal - } - loop-if 4:boolean - 5:integer <- copy 0:literal - } -] -+after-brace: recipe main -+after-brace: jump-if 4, -5:offset - -:(scenario "loop_label") -recipe main [ - 1:integer <- copy 0:literal - +foo - 2:integer <- copy 0:literal -] -+after-brace: recipe main -+after-brace: copy ... -+after-brace: copy ... - -//: test how things actually run -:(scenarios run) -:(scenario "factorial") -recipe factorial [ - 1:integer <- copy 5:literal - 2:integer <- copy 1:literal - { - 3:boolean <- equal 1:integer 1:literal - break-if 3:boolean -# $print 1:integer - 2:integer <- multiply 2:integer, 1:integer - 1:integer <- subtract 1:integer, 1:literal - loop - } - 4:integer <- copy 2:integer # trigger a read -] -+mem: location 2 is 120 diff --git a/cpp/025call b/cpp/025call new file mode 100644 index 00000000..3683db12 --- /dev/null +++ b/cpp/025call @@ -0,0 +1,77 @@ +//: So far the recipes we define can't run each other. Let's change that. +:(scenario "calling_recipe") +recipe main [ + f +] +recipe f [ + 3:integer <- add 2:literal, 2:literal +] ++mem: storing 4 in location 3 + +:(before "struct routine {") +// Everytime a recipe runs another, we interrupt it and start running the new +// recipe. When that finishes, we continue this one where we left off. +// This requires maintaining a 'stack' of interrupted recipes or 'calls'. +struct call { + recipe_number running_recipe; + size_t pc; + // End call Fields + call(recipe_number r) :running_recipe(r), pc(0) {} +}; +typedef stack call_stack; + +:(replace{} "struct routine") +struct routine { + call_stack calls; + // End routine Fields + routine(recipe_number r); +}; +:(code) + routine::routine(recipe_number r) { + calls.push(call(r)); + } +//: now update routine's helpers +:(replace{} "inline size_t& running_at(routine& rr)") +inline size_t& running_at(routine& rr) { + return rr.calls.top().pc; +} +:(replace{} "inline string recipe_name(routine& rr)") +inline string recipe_name(routine& rr) { + return Recipe[rr.calls.top().running_recipe].name; +} +:(replace{} "inline vector& steps(routine& rr)") +inline vector& steps(routine& rr) { + return Recipe[rr.calls.top().running_recipe].steps; +} + +:(replace{} "default:" following "End Primitive Recipe Implementations") +default: { + // not a primitive; try to look for a matching recipe + if (Recipe.find(instructions[pc].operation) == Recipe.end()) { + raise << "undefined operation " << instructions[pc].operation << ": " << instructions[pc].name << '\n'; + break; + } + rr.calls.push(call(instructions[pc].operation)); + continue; // not done with caller; don't increment pc +} + +//: finally, we need to fix the termination conditions for the run loop + +:(replace{} "inline bool done(routine& rr)") +inline bool done(routine& rr) { + return rr.calls.empty(); +} + +:(before "Running one instruction") +// when we reach the end of one call, we may reach the end of the one below +// it, and the one below that, and so on +while (running_at(rr) >= steps(rr).size()) { + rr.calls.pop(); + if (rr.calls.empty()) return; + // todo: no results returned warning + ++running_at(rr); +} + +:(before "End Includes") +#include +using std::stack; diff --git a/cpp/025name b/cpp/025name deleted file mode 100644 index 375f9854..00000000 --- a/cpp/025name +++ /dev/null @@ -1,172 +0,0 @@ -//: A big convenience high-level languages provide is the ability to name memory -//: locations. In mu, a transform called 'convert-names' provides this -//: convenience. - -:(scenarios run) -:(scenario "convert_names") -recipe main [ - x:integer <- copy 0:literal -] -+name: assign x 1 -+run: instruction main/0 -+mem: storing 0 in location 1 - -:(scenario "convert_names_warns") -hide warnings -recipe main [ - x:integer <- copy y:integer -] -+warn: use before set: y in main - -:(after "int main") - Transform.push_back(transform_names); - -:(before "End Globals") -unordered_map > Name; -:(after "Clear Other State For recently_added_recipes") -for (size_t i = 0; i < recently_added_recipes.size(); ++i) { - Name.erase(recently_added_recipes[i]); -} - -:(code) -void transform_names(const recipe_number r) { - unordered_map& names = Name[r]; - int curr_idx = 1; -//? cout << "Recipe " << r << '\n'; //? 2 -//? cout << Recipe[r].steps.size(); //? 1 - for (size_t i = 0; i < Recipe[r].steps.size(); ++i) { -//? cout << "instruction " << i << '\n'; //? 2 - instruction& inst = Recipe[r].steps[i]; - // Per-recipe Transforms - // map names to addresses - for (size_t in = 0; in < inst.ingredients.size(); ++in) { -//? cout << "ingredients\n"; //? 2 - if (is_raw(inst.ingredients[in])) continue; -//? cout << "ingredient " << inst.ingredients[in].name << '\n'; //? 2 - if (inst.ingredients[in].name == "default-space") - inst.ingredients[in].initialized = true; - assert(!inst.ingredients[in].types.empty()); - if (inst.ingredients[in].types[0] // not a literal - && !inst.ingredients[in].initialized - && inst.ingredients[in].name.find_first_not_of("0123456789-.") != string::npos) { - if (!already_transformed(inst.ingredients[in], names)) { - raise << "use before set: " << inst.ingredients[in].name << " in " << Recipe[r].name << '\n'; - } - inst.ingredients[in].set_value(lookup_name(inst.ingredients[in], r)); -//? cout << "lookup ingredient " << Recipe[r].name << "/" << i << ": " << inst.ingredients[in].to_string() << '\n'; //? 1 - } - } - for (size_t out = 0; out < inst.products.size(); ++out) { -//? cout << "products\n"; //? 1 - if (is_raw(inst.products[out])) continue; -//? cout << "product " << out << '/' << inst.products.size() << " " << inst.products[out].name << '\n'; //? 4 -//? cout << inst.products[out].types[0] << '\n'; //? 1 - if (inst.products[out].name == "default-space") - inst.products[out].initialized = true; - if (inst.products[out].types[0] // not a literal - && !inst.products[out].initialized - && inst.products[out].name.find_first_not_of("0123456789-.") != string::npos) { - if (names.find(inst.products[out].name) == names.end()) { - trace("name") << "assign " << inst.products[out].name << " " << curr_idx; - names[inst.products[out].name] = curr_idx; - curr_idx += size_of(inst.products[out]); - } - inst.products[out].set_value(lookup_name(inst.products[out], r)); -//? cout << "lookup product " << Recipe[r].name << "/" << i << ": " << inst.products[out].to_string() << '\n'; //? 1 - } - } - } -} - -bool already_transformed(const reagent& r, const unordered_map& names) { - return names.find(r.name) != names.end(); -} - -size_t lookup_name(const reagent& r, const recipe_number default_recipe) { - return Name[default_recipe][r.name]; -} - -type_number skip_addresses(const vector& types) { - for (size_t i = 0; i < types.size(); ++i) { - if (types[i] != Type_number["address"]) return types[i]; - } - raise << "expected a container" << '\n' << die(); - return -1; -} - -int find_element_name(const type_number t, const string& name) { - const type_info& container = Type[t]; -//? cout << "looking for element " << name << " in type " << container.name << " with " << container.element_names.size() << " elements\n"; //? 1 - for (size_t i = 0; i < container.element_names.size(); ++i) { - if (container.element_names[i] == name) return i; - } - raise << "unknown element " << name << " in container " << t << '\n' << die(); - return -1; -} - -bool is_raw(const reagent& r) { - for (size_t i = /*skip value+type*/1; i < r.properties.size(); ++i) { - if (r.properties[i].first == "raw") return true; - } - return false; -} - -:(scenario "convert_names_passes_dummy") -# _ is just a dummy result that never gets consumed -recipe main [ - _, x:integer <- copy 0:literal -] -+name: assign x 1 --name: assign _ 1 - -//: one reserved word that we'll need later -:(scenario "convert_names_passes_default_space") -recipe main [ - default-space:integer, x:integer <- copy 0:literal -] -+name: assign x 1 --name: assign default-space 1 - -//: an escape hatch to suppress name conversion that we'll use later -:(scenario "convert_names_passes_raw") -recipe main [ - x:integer/raw <- copy 0:literal -] --name: assign x 1 - -//: update our running example container for the next test -:(before "End Mu Types Initialization") -Type[point].element_names.push_back("x"); -Type[point].element_names.push_back("y"); -:(scenario "convert_names_transforms_container_elements") -recipe main [ - a:integer <- get 0:point, y:offset - b:integer <- get 0:point, x:offset -] -+name: element y of type point is at offset 1 -+name: element x of type point is at offset 0 - -:(after "Per-recipe Transforms") -// replace element names of containers with offsets -if (inst.operation == Recipe_number["get"] - || inst.operation == Recipe_number["get-address"]) { - // at least 2 args, and second arg is offset - assert(inst.ingredients.size() >= 2); -//? cout << inst.ingredients[1].to_string() << '\n'; //? 1 - assert(isa_literal(inst.ingredients[1])); - if (inst.ingredients[1].name.find_first_not_of("0123456789") == string::npos) continue; - // since first non-address in base type must be a container, we don't have to canonize - type_number container = skip_addresses(inst.ingredients[0].types); - inst.ingredients[1].set_value(find_element_name(container, inst.ingredients[1].name)); - trace("name") << "element " << inst.ingredients[1].name << " of type " << Type[container].name << " is at offset " << inst.ingredients[1].value; -} - -//: this test is actually illegal so can't call run -:(scenarios transform_test) -:(scenario "convert_names_handles_containers") -recipe main [ - a:point <- copy 0:literal - b:integer <- copy 0:literal -] -+name: assign a 1 -+name: assign b 3 diff --git a/cpp/026call_ingredient b/cpp/026call_ingredient new file mode 100644 index 00000000..963c5fe8 --- /dev/null +++ b/cpp/026call_ingredient @@ -0,0 +1,44 @@ +//: Calls can take ingredients just like primitives. To access a recipe's +//: ingredients, use 'next-ingredient'. +:(scenario "next_ingredient") +recipe main [ + f 2:literal +] +recipe f [ + 12:integer <- next-ingredient + 13:integer <- add 1:literal, 12:integer +] ++run: instruction f/1 ++mem: location 12 is 2 ++mem: storing 3 in location 13 + +:(before "End call Fields") +vector > ingredient_atoms; +size_t next_ingredient_to_process; +:(replace{} "call(recipe_number r)") +call(recipe_number r) :running_recipe(r), pc(0), next_ingredient_to_process(0) {} + +:(replace "rr.calls.push(call(instructions[pc].operation))" following "End Primitive Recipe Implementations") +call callee(instructions[pc].operation); +for (vector::iterator p = instructions[pc].ingredients.begin(); p != instructions[pc].ingredients.end(); ++p) { + callee.ingredient_atoms.push_back(read_memory(*p)); +} +rr.calls.push(callee); + +:(before "End Globals") +const int NEXT_INGREDIENT = 22; +:(before "End Primitive Recipe Numbers") +Recipe_number["next-ingredient"] = NEXT_INGREDIENT; +assert(Next_recipe_number == NEXT_INGREDIENT); +Next_recipe_number++; +:(before "End Primitive Recipe Implementations") +case NEXT_INGREDIENT: { + if (rr.calls.top().next_ingredient_to_process < rr.calls.top().ingredient_atoms.size()) { + trace("run") << "product 0 is " + << rr.calls.top().ingredient_atoms[rr.calls.top().next_ingredient_to_process][0]; + write_memory(instructions[pc].products[0], + rr.calls.top().ingredient_atoms[rr.calls.top().next_ingredient_to_process]); + ++rr.calls.top().next_ingredient_to_process; + } + break; +} diff --git a/cpp/026new b/cpp/026new deleted file mode 100644 index e75d4148..00000000 --- a/cpp/026new +++ /dev/null @@ -1,79 +0,0 @@ -//: A simple memory allocator to create space for new variables at runtime. - -:(scenarios run) -:(scenario "new") -# call new two times with identical arguments; you should get back different results -recipe main [ - 1:address:integer/raw <- new integer:type - 2:address:integer/raw <- new integer:type - 3:boolean/raw <- equal 1:address:integer/raw, 2:address:integer/raw -] -+mem: storing 0 in location 3 - -:(before "End Globals") -const size_t Alloc_init = 1000; -:(before "End routine Fields") -size_t alloc; -:(replace{} "routine::routine(recipe_number r)") - routine::routine(recipe_number r) :alloc(Alloc_init) { - calls.push(call(r)); - } - -//: first handle 'type' operands -:(before "End Mu Types Initialization") -Type_number["type"] = 0; -:(after "Per-recipe Transforms") -// replace type names with type_numbers -if (inst.operation == Recipe_number["new"]) { - // first arg must be of type 'type' - assert(inst.ingredients.size() >= 1); -//? cout << inst.ingredients[0].to_string() << '\n'; //? 1 - assert(isa_literal(inst.ingredients[0])); - if (inst.ingredients[0].properties[0].second[0] == "type") { - inst.ingredients[0].set_value(Type_number[inst.ingredients[0].name]); - } - trace("new") << inst.ingredients[0].name << " -> " << inst.ingredients[0].value; -} - -:(before "End Globals") -// Operator to look at elements of arrays. -const int NEW = 30; -:(before "End Primitive Recipe Numbers") -Recipe_number["new"] = NEW; -assert(Next_recipe_number == NEW); -Next_recipe_number++; -:(before "End Primitive Recipe Implementations") -case NEW: { - vector result; - trace("mem") << "new alloc: " << Current_routine->alloc; - result.push_back(Current_routine->alloc); - write_memory(instructions[pc].products[0], result); - vector types; - types.push_back(instructions[pc].ingredients[0].value); - if (instructions[pc].ingredients.size() > 1) { - // array - vector capacity = read_memory(instructions[pc].ingredients[1]); - trace("mem") << "array size is " << capacity[0]; - Memory[Current_routine->alloc] = capacity[0]; - Current_routine->alloc += capacity[0]*size_of(types); - } - else { - // scalar - Current_routine->alloc += size_of(types); - } - break; -} - -:(scenario "new_array") -recipe main [ - 1:address:array:integer/raw <- new integer:type, 5:literal - 2:address:integer/raw <- new integer:type - 3:integer/raw <- subtract 2:address:integer/raw, 1:address:array:integer/raw -] -+run: instruction main/0 -+mem: array size is 5 -+run: instruction main/1 -+run: instruction main/2 -+mem: storing 5 in location 3 - -//: vim: ft=cpp diff --git a/cpp/027call_reply b/cpp/027call_reply new file mode 100644 index 00000000..feb549e6 --- /dev/null +++ b/cpp/027call_reply @@ -0,0 +1,72 @@ +//: Calls can also generate results, using 'reply'. +:(scenario "reply") +recipe main [ + 3:integer, 4:integer <- f 2:literal +] +recipe f [ + 12:integer <- next-ingredient + 13:integer <- add 1:literal, 12:integer + reply 12:integer, 13:integer +] ++run: instruction main/0 ++run: result 0 is 2 ++mem: storing 2 in location 3 ++run: result 1 is 3 ++mem: storing 3 in location 4 + +:(before "End Globals") +const int REPLY = 23; +:(before "End Primitive Recipe Numbers") +Recipe_number["reply"] = REPLY; +assert(Next_recipe_number == REPLY); +Next_recipe_number++; +:(before "End Primitive Recipe Implementations") +case REPLY: { + vector > callee_results; + for (size_t i = 0; i < instructions[pc].ingredients.size(); ++i) { + callee_results.push_back(read_memory(instructions[pc].ingredients[i])); + } + rr.calls.pop(); + assert(!rr.calls.empty()); + size_t& caller_pc = rr.calls.top().pc; + instruction& caller_instruction = Recipe[rr.calls.top().running_recipe].steps[caller_pc]; + assert(caller_instruction.products.size() <= callee_results.size()); + for (size_t i = 0; i < caller_instruction.products.size(); ++i) { + trace("run") << "result " << i << " is " << to_string(callee_results[i]); + write_memory(caller_instruction.products[i], callee_results[i]); + } + ++caller_pc; + break; +} + +//: Results can include containers and exclusive containers, addresses and arrays. +:(scenario "reply_container") +recipe main [ + 3:point <- f 2:literal +] +recipe f [ + 12:integer <- next-ingredient + 13:integer <- copy 35:literal + reply 12:point +] ++run: instruction main/0 ++run: result 0 is [2, 35] ++mem: storing 2 in location 3 ++mem: storing 35 in location 4 + +:(code) +string to_string(const vector& in) { + if (in.empty()) return "[]"; + ostringstream out; + if (in.size() == 1) { + out << in[0]; + return out.str(); + } + out << "["; + for (size_t i = 0; i < in.size(); ++i) { + if (i > 0) out << ", "; + out << in[i]; + } + out << "]"; + return out.str(); +} diff --git a/cpp/027space b/cpp/027space deleted file mode 100644 index 1bb91b8e..00000000 --- a/cpp/027space +++ /dev/null @@ -1,90 +0,0 @@ -//: Spaces help isolate functions from each other. You can create them at will, -//: and all addresses in arguments are implicitly based on the 'default-space' -//: (unless they have the /raw property) - -:(scenarios run) -:(scenario "set_default_space") -# if default-space is 10, and if an array of 5 locals lies from location 11 to 15 (inclusive), -# then location 0 is really location 11, location 1 is really location 12, and so on. -recipe main [ - 10:integer <- copy 5:literal # pretend array; in practice we'll use new - default-space:address:space <- copy 10:literal - 1:integer <- copy 23:literal -] -+mem: storing 23 in location 12 - -:(scenario "deref_sidesteps_default_space") -recipe main [ - # pretend pointer from outside - 7:integer <- copy 34:literal - # pretend array - 10:integer <- copy 5:literal - # actual start of this function - default-space:address:space <- copy 10:literal - 1:address:integer <- copy 7:literal - 8:integer/raw <- copy 1:address:integer/deref -] -+mem: storing 34 in location 8 - -:(before "End call Fields") -size_t default_space; -:(replace "call(recipe_number r) :running_recipe(r)") -call(recipe_number r) :running_recipe(r), pc(0), next_ingredient_to_process(0), default_space(0) {} - -:(replace "reagent r = x" following "reagent canonize(reagent x)") -reagent r = absolutize(x); -:(code) -reagent absolutize(reagent x) { -//? if (Recipe_number.find("increment-counter") != Recipe_number.end()) //? 1 -//? cout << "AAA " << "increment-counter/2: " << Recipe[Recipe_number["increment-counter"]].steps[2].products[0].to_string() << '\n'; //? 1 -//? cout << "absolutize " << x.to_string() << '\n'; //? 3 -//? cout << is_raw(x) << '\n'; //? 1 - if (is_raw(x) || is_dummy(x)) return x; -//? cout << "not raw: " << x.to_string() << '\n'; //? 1 - assert(x.initialized); - reagent r = x; - r.set_value(address(r.value, space_base(r))); -//? cout << "after absolutize: " << r.value << '\n'; //? 1 - r.properties.push_back(pair >("raw", vector())); - assert(is_raw(r)); - return r; -} -:(before "return result" following "reagent deref(reagent x)") -result.properties.push_back(pair >("raw", vector())); - -:(code) -int space_base(const reagent& x) { - return Current_routine->calls.top().default_space; -} - -int address(int offset, int base) { - if (base == 0) return offset; // raw -//? cout << base << '\n'; //? 1 - if (offset >= Memory[base]) { - // todo: test - raise << "location " << offset << " is out of bounds " << Memory[base] << '\n'; - } - return base+1 + offset; -} - -:(after "void write_memory(reagent x, vector data)") - if (x.name == "default-space") { - assert(data.size() == 1); - Current_routine->calls.top().default_space = data[0]; -//? cout << "AAA " << Current_routine->calls.top().default_space << '\n'; //? 1 - return; - } - -:(scenario "get_default_space") -recipe main [ - default-space:address:space <- copy 10:literal - 1:integer/raw <- copy default-space:address:space -] -+mem: storing 10 in location 1 - -:(after "vector read_memory(reagent x)") - if (x.name == "default-space") { - vector result; - result.push_back(Current_routine->calls.top().default_space); - return result; - } diff --git a/cpp/028space_surround b/cpp/028space_surround deleted file mode 100644 index 76cd5807..00000000 --- a/cpp/028space_surround +++ /dev/null @@ -1,50 +0,0 @@ -//: So far you can have global variables by not setting default-space, and -//: local variables by setting default-space. You can isolate variables -//: between those extremes by creating 'surrounding' spaces. -//: -//: (Surrounding spaces are like lexical scopes in other languages.) - -:(scenario "surrounding_space") -# location 1 in space 1 refers to the space surrounding the default space, here 20. -recipe main [ - 10:integer <- copy 5:literal # pretend array - 20:integer <- copy 5:literal # pretend array - default-space:address:space <- copy 10:literal - 0:address:space/names:dummy <- copy 20:literal # later layers will explain the /names: property - 1:integer <- copy 32:literal - 1:integer/space:1 <- copy 33:literal -] -+run: instruction main/3 -+mem: storing 20 in location 11 -+run: instruction main/4 -+mem: storing 32 in location 12 -+run: instruction main/5 -+mem: storing 33 in location 22 - -//: If you think of a space as a collection of variables with a common -//: lifetime, surrounding allows managing shorter lifetimes inside a longer -//: one. - -:(replace{} "int space_base(const reagent& x)") -int space_base(const reagent& x) { - return space_base(x, space_index(x), Current_routine->calls.top().default_space); -} - -int space_base(const reagent& x, int space_index, int base) { -//? trace("foo") << "base of space " << space_index << '\n'; //? 1 - if (space_index == 0) { -//? trace("foo") << "base of space " << space_index << " is " << base << '\n'; //? 1 - return base; - } -//? trace("foo") << "base of space " << space_index << " is " << Memory[base+1] << '\n'; //? 1 - int result = space_base(x, space_index-1, Memory[base+1]); - return result; -} - -int space_index(const reagent& x) { - for (size_t i = 0; i < x.properties.size(); ++i) { - if (x.properties[i].first == "space") - return to_int(x.properties[i].second[0]); - } - return 0; -} diff --git a/cpp/029literal_string b/cpp/029literal_string deleted file mode 100644 index 9ca09115..00000000 --- a/cpp/029literal_string +++ /dev/null @@ -1,91 +0,0 @@ -//: Some instructions can take string literals for convenience. -//: -//: Instead of quotes, we'll use [] to delimit strings. That'll reduce the -//: need for escaping since we can support nested brackets. And we can also -//: imagine that 'recipe' might one day itself be defined in mu, doing its own -//: parsing. - -//: First extend the mu parser to support string literals. -:(scenario "string_literal") -recipe main [ - 1:address:array:character <- new [abc def] -] -+parse: ingredient: {name: "abc def", value: 0, type: 0, properties: ["abc def": "literal-string"]} - -:(scenario "string_literal_with_colons") -recipe main [ - 1:address:array:character <- new [abc:def/ghi] -] -+parse: ingredient: {name: "abc:def/ghi", value: 0, type: 0, properties: ["abc:def/ghi": "literal-string"]} - -:(before "End Mu Types Initialization") -Type_number["literal-string"] = 0; - -:(after "string next_word(istream& in)") -if (in.peek() == '[') return slurp_quoted(in); - -:(code) -string slurp_quoted(istream& in) { - assert(!in.eof()); - assert(in.peek() == '['); - ostringstream out; - int size = 0; - while (!in.eof()) { - char c = in.get(); -//? cout << c << '\n'; //? 1 - out << c; -//? cout << out.str() << "$\n"; //? 1 - if (c == '[') ++size; - if (c == ']') --size; - if (size == 0) break; - } - return out.str(); -} - -:(after "reagent::reagent(string s)") -//? cout << s[0] << '\n'; //? 1 - if (s[0] == '[') { - assert(s[s.size()-1] == ']'); - // delete [] delimiters - s.erase(0, 1); - s.erase(s.size()-1, s.size()); - name = s; - types.push_back(0); - properties.push_back(pair >(name, vector())); - properties.back().second.push_back("literal-string"); - return; - } - -:(scenario "string_literal_nested") -recipe main [ - 1:address:array:character <- new [abc [def]] -] -+parse: ingredient: {name: "abc [def]", value: 0, type: 0, properties: ["abc [def]": "literal-string"]} - -//: Next, extend 'new' to handle a string literal argument. -:(scenario "new_string") -recipe main [ - 1:address:array:character <- new [abc def] - 2:character <- index 1:address:array:character/deref, 5:literal -] -# integer code for 'e' -+mem: storing 101 in location 2 - -:(before "End Mu Types Initialization") -Type_number["character"] = Next_type_number++; - -:(after "case NEW" following "Primitive Recipe Implementations") -if (instructions[pc].ingredients[0].properties[0].second[0] == "literal-string") { - // allocate an array just large enough for it - vector result; - result.push_back(Current_routine->alloc); - write_memory(instructions[pc].products[0], result); - // assume that all characters fit in a single location -//? cout << "new string literal: " << instructions[pc].ingredients[0].name << '\n'; //? 1 - Memory[Current_routine->alloc++] = instructions[pc].ingredients[0].name.size(); - for (size_t i = 0; i < instructions[pc].ingredients[0].name.size(); ++i) { - Memory[Current_routine->alloc++] = instructions[pc].ingredients[0].name[i]; - } - // mu strings are not null-terminated in memory - break; -} diff --git a/cpp/030brace b/cpp/030brace new file mode 100644 index 00000000..0d68b54f --- /dev/null +++ b/cpp/030brace @@ -0,0 +1,398 @@ +//: Structured programming +//: +//: Our jump operators are quite inconvenient to use, so mu provides a +//: lightweight tool called 'transform_braces' to work in a slightly more +//: convenient format with nested braces: +//: +//: { +//: some instructions +//: { +//: more instructions +//: } +//: } +//: +//: Braces are just labels, they require no special parsing. The pseudo +//: recipes 'loop' and 'break' jump to just after the enclosing '{' and '}' +//: respectively. +//: +//: Conditional and unconditional 'loop' and 'break' should give us 80% of the +//: benefits of the control-flow primitives we're used to in other languages, +//: like 'if', 'while', 'for', etc. + +:(scenarios transform_test) +:(scenario "brace_conversion") +recipe main [ + { + break + 1:integer <- copy 0:literal + } +] ++after-brace: recipe main ++after-brace: jump 1:offset ++after-brace: copy ... + +//: one-time setup +:(after "int main") + Transform.push_back(transform_braces); + +:(code) +void transform_braces(const recipe_number r) { +//? cout << "AAA transform_braces\n"; //? 1 +//? exit(0); //? 1 + const int OPEN = 0, CLOSE = 1; + list > braces; + for (size_t index = 0; index < Recipe[r].steps.size(); ++index) { + const instruction& inst = Recipe[r].steps[index]; + if (inst.label == "{") { + trace("brace") << r << ": push (open, " << index << ")"; + braces.push_back(pair(OPEN, index)); + } + if (inst.label == "}") { + trace("brace") << "push (close, " << index << ")"; + braces.push_back(pair(CLOSE, index)); + } + } + stack open_braces; + trace("after-brace") << "recipe " << Recipe[r].name; + for (size_t index = 0; index < Recipe[r].steps.size(); ++index) { + instruction& inst = Recipe[r].steps[index]; +//? cout << "AAA " << inst.name << ": " << inst.operation << '\n'; //? 1 + if (inst.label == "{") open_braces.push(index); + else if (inst.label == "}") open_braces.pop(); + else if (inst.is_label) + ; // do nothing + else if (inst.operation == Recipe_number["loop"]) { + inst.operation = Recipe_number["jump"]; + if (inst.ingredients.size() > 0 && isa_literal(inst.ingredients[0])) { + // explicit target; a later phase will handle it + trace("after-brace") << "jump " << inst.ingredients[0].name << ":offset"; + } + else { + reagent ing; + ing.set_value(open_braces.top()-index); + inst.ingredients.push_back(ing); + trace("after-brace") << "jump " << ing.value << ":offset"; + trace("after-brace") << index << ": " << ing.to_string(); + trace("after-brace") << index << ": " << Recipe[r].steps[index].ingredients[0].to_string(); + } + } + else if (inst.operation == Recipe_number["break"]) { + inst.operation = Recipe_number["jump"]; + if (inst.ingredients.size() > 0 && isa_literal(inst.ingredients[0])) { + // explicit target; a later phase will handle it + trace("after-brace") << "jump " << inst.ingredients[0].name << ":offset"; + } + else { + reagent ing; + ing.set_value(matching_brace(open_braces.top(), braces) - index - 1); + inst.ingredients.push_back(ing); + trace("after-brace") << "jump " << ing.value << ":offset"; + } + } + else if (inst.operation == Recipe_number["loop-if"]) { + inst.operation = Recipe_number["jump-if"]; + if (inst.ingredients.size() > 1 && isa_literal(inst.ingredients[1])) { + // explicit target; a later phase will handle it + trace("after-brace") << "jump " << inst.ingredients[1].name << ":offset"; + } + else { + reagent ing; + ing.set_value(open_braces.top()-index); + inst.ingredients.push_back(ing); + trace("after-brace") << "jump-if " << inst.ingredients[0].name << ", " << ing.value << ":offset"; + } + } + else if (inst.operation == Recipe_number["break-if"]) { + inst.operation = Recipe_number["jump-if"]; + if (inst.ingredients.size() > 1 && isa_literal(inst.ingredients[1])) { + // explicit target; a later phase will handle it + trace("after-brace") << "jump " << inst.ingredients[1].name << ":offset"; + } + else { + reagent ing; + ing.set_value(matching_brace(open_braces.top(), braces) - index - 1); + inst.ingredients.push_back(ing); + trace("after-brace") << "jump-if " << inst.ingredients[0].name << ", " << ing.value << ":offset"; + } + } + else if (inst.operation == Recipe_number["loop-unless"]) { + inst.operation = Recipe_number["jump-unless"]; + if (inst.ingredients.size() > 1 && isa_literal(inst.ingredients[1])) { + // explicit target; a later phase will handle it + trace("after-brace") << "jump " << inst.ingredients[1].name << ":offset"; + } + else { + reagent ing; + ing.set_value(open_braces.top()-index); + inst.ingredients.push_back(ing); + trace("after-brace") << "jump-unless " << inst.ingredients[0].name << ", " << ing.value << ":offset"; + } + } + else if (inst.operation == Recipe_number["break-unless"]) { +//? cout << "AAA break-unless\n"; //? 1 + inst.operation = Recipe_number["jump-unless"]; + if (inst.ingredients.size() > 1 && isa_literal(inst.ingredients[1])) { + // explicit target; a later phase will handle it + trace("after-brace") << "jump " << inst.ingredients[1].name << ":offset"; + } + else { + reagent ing; + ing.set_value(matching_brace(open_braces.top(), braces) - index - 1); + inst.ingredients.push_back(ing); + trace("after-brace") << "jump-unless " << inst.ingredients[0].name << ", " << ing.value << ":offset"; + } + } + else { + trace("after-brace") << inst.name << " ..."; + } + } +} + +size_t matching_brace(size_t index, const list >& braces) { + int stacksize; + for (list >::const_iterator p = braces.begin(); p != braces.end(); ++p) { + if (p->second < index) continue; + stacksize += (p->first ? 1 : -1); + if (stacksize == 0) return p->second; + } + assert(false); + return -1; +} + +// temporarily suppress run +void transform_test(string form) { +//? cout << "AAA transform_test {\n"; //? 1 + vector tmp = add_recipes(form); +//? cout << "AAA done adding recipes\n"; //? 1 + transform_all(); +//? cout << "AAA }\n"; //? 1 +} + +//: Make sure these pseudo recipes get consistent numbers, even though they aren't +//: implemented. +:(before "End Globals") +const int BREAK = 24; +const int BREAK_IF = 25; +const int BREAK_UNLESS = 26; +const int LOOP = 27; +const int LOOP_IF = 28; +const int LOOP_UNLESS = 29; +:(before "End Primitive Recipe Numbers") +Recipe_number["break"] = BREAK; +assert(Next_recipe_number == BREAK); +Next_recipe_number++; +Recipe_number["break-if"] = BREAK_IF; +assert(Next_recipe_number == BREAK_IF); +Next_recipe_number++; +Recipe_number["break-unless"] = BREAK_UNLESS; +assert(Next_recipe_number == BREAK_UNLESS); +Next_recipe_number++; +Recipe_number["loop"] = LOOP; +assert(Next_recipe_number == LOOP); +Next_recipe_number++; +Recipe_number["loop-if"] = LOOP_IF; +assert(Next_recipe_number == LOOP_IF); +Next_recipe_number++; +Recipe_number["loop-unless"] = LOOP_UNLESS; +assert(Next_recipe_number == LOOP_UNLESS); +Next_recipe_number++; + +:(scenario "loop") +recipe main [ + 1:integer <- copy 0:literal + 2:integer <- copy 0:literal + { + 3:integer <- copy 0:literal + loop + } +] ++after-brace: recipe main ++after-brace: copy ... ++after-brace: copy ... ++after-brace: copy ... ++after-brace: jump -2:offset + +:(scenario "break_empty_block") +recipe main [ + 1:integer <- copy 0:literal + { + break + } +] ++after-brace: recipe main ++after-brace: copy ... ++after-brace: jump 0:offset + +:(scenario "break_cascading") +recipe main [ + 1:integer <- copy 0:literal + { + break + } + { + break + } +] ++after-brace: recipe main ++after-brace: copy ... ++after-brace: jump 0:offset ++after-brace: jump 0:offset + +:(scenario "break_cascading2") +recipe main [ + 1:integer <- copy 0:literal + 2:integer <- copy 0:literal + { + break + 3:integer <- copy 0:literal + } + { + break + } +] ++after-brace: recipe main ++after-brace: copy ... ++after-brace: copy ... ++after-brace: jump 1:offset ++after-brace: copy ... ++after-brace: jump 0:offset + +:(scenario "break_if") +recipe main [ + 1:integer <- copy 0:literal + 2:integer <- copy 0:literal + { + break-if 2:integer + 3:integer <- copy 0:literal + } + { + break + } +] ++after-brace: recipe main ++after-brace: copy ... ++after-brace: copy ... ++after-brace: jump-if 2, 1:offset ++after-brace: copy ... ++after-brace: jump 0:offset + +:(scenario "break_nested") +recipe main [ + 1:integer <- copy 0:literal + { + 2:integer <- copy 0:literal + break + { + 3:integer <- copy 0:literal + } + 4:integer <- copy 0:literal + } +] ++after-brace: jump 4:offset + +:(scenario "break_nested_degenerate") +recipe main [ + 1:integer <- copy 0:literal + { + 2:integer <- copy 0:literal + break + { + } + 4:integer <- copy 0:literal + } +] ++after-brace: jump 3:offset + +:(scenario "break_nested_degenerate2") +recipe main [ + 1:integer <- copy 0:literal + { + 2:integer <- copy 0:literal + break + { + } + } +] ++after-brace: jump 2:offset + +:(scenario "break_label") +recipe main [ + 1:integer <- copy 0:literal + { + break +foo:offset + } +] ++after-brace: jump +foo:offset + +:(scenario "break_unless") +recipe main [ + 1:integer <- copy 0:literal + 2:integer <- copy 0:literal + { + break-unless 2:integer + 3:integer <- copy 0:literal + } +] ++after-brace: recipe main ++after-brace: copy ... ++after-brace: copy ... ++after-brace: jump-unless 2, 1:offset ++after-brace: copy ... + +:(scenario "loop_unless") +recipe main [ + 1:integer <- copy 0:literal + 2:integer <- copy 0:literal + { + loop-unless 2:integer + 3:integer <- copy 0:literal + } +] ++after-brace: recipe main ++after-brace: copy ... ++after-brace: copy ... ++after-brace: jump-unless 2, -1:offset ++after-brace: copy ... + +:(scenario "loop_nested") +recipe main [ + 1:integer <- copy 0:literal + { + 2:integer <- copy 0:literal + { + 3:integer <- copy 0:literal + } + loop-if 4:boolean + 5:integer <- copy 0:literal + } +] ++after-brace: recipe main ++after-brace: jump-if 4, -5:offset + +:(scenario "loop_label") +recipe main [ + 1:integer <- copy 0:literal + +foo + 2:integer <- copy 0:literal +] ++after-brace: recipe main ++after-brace: copy ... ++after-brace: copy ... + +//: test how things actually run +:(scenarios run) +:(scenario "factorial") +recipe factorial [ + 1:integer <- copy 5:literal + 2:integer <- copy 1:literal + { + 3:boolean <- equal 1:integer 1:literal + break-if 3:boolean +# $print 1:integer + 2:integer <- multiply 2:integer, 1:integer + 1:integer <- subtract 1:integer, 1:literal + loop + } + 4:integer <- copy 2:integer # trigger a read +] ++mem: location 2 is 120 diff --git a/cpp/030length b/cpp/030length deleted file mode 100644 index b9e3bc3c..00000000 --- a/cpp/030length +++ /dev/null @@ -1,30 +0,0 @@ -:(scenario "array_length") -recipe main [ - 1:integer <- copy 3:literal - 2:integer <- copy 14:literal - 3:integer <- copy 15:literal - 4:integer <- copy 16:literal - 5:integer <- length 1:array:integer -] -+run: instruction main/4 -+mem: storing 3 in location 5 - -:(before "End Globals") -const int LENGTH = 31; -:(before "End Primitive Recipe Numbers") -Recipe_number["length"] = LENGTH; -assert(Next_recipe_number == LENGTH); -Next_recipe_number++; -:(before "End Primitive Recipe Implementations") -case LENGTH: { - reagent x = canonize(instructions[pc].ingredients[0]); - if (x.types[0] != Type_number["array"]) { - raise << "tried to calculate length of non-array " << x.to_string() << '\n'; - break; - } - vector result; -//? cout << "length: " << x.value << '\n'; //? 1 - result.push_back(Memory[x.value]); - write_memory(instructions[pc].products[0], result); - break; -} diff --git a/cpp/031name b/cpp/031name new file mode 100644 index 00000000..375f9854 --- /dev/null +++ b/cpp/031name @@ -0,0 +1,172 @@ +//: A big convenience high-level languages provide is the ability to name memory +//: locations. In mu, a transform called 'convert-names' provides this +//: convenience. + +:(scenarios run) +:(scenario "convert_names") +recipe main [ + x:integer <- copy 0:literal +] ++name: assign x 1 ++run: instruction main/0 ++mem: storing 0 in location 1 + +:(scenario "convert_names_warns") +hide warnings +recipe main [ + x:integer <- copy y:integer +] ++warn: use before set: y in main + +:(after "int main") + Transform.push_back(transform_names); + +:(before "End Globals") +unordered_map > Name; +:(after "Clear Other State For recently_added_recipes") +for (size_t i = 0; i < recently_added_recipes.size(); ++i) { + Name.erase(recently_added_recipes[i]); +} + +:(code) +void transform_names(const recipe_number r) { + unordered_map& names = Name[r]; + int curr_idx = 1; +//? cout << "Recipe " << r << '\n'; //? 2 +//? cout << Recipe[r].steps.size(); //? 1 + for (size_t i = 0; i < Recipe[r].steps.size(); ++i) { +//? cout << "instruction " << i << '\n'; //? 2 + instruction& inst = Recipe[r].steps[i]; + // Per-recipe Transforms + // map names to addresses + for (size_t in = 0; in < inst.ingredients.size(); ++in) { +//? cout << "ingredients\n"; //? 2 + if (is_raw(inst.ingredients[in])) continue; +//? cout << "ingredient " << inst.ingredients[in].name << '\n'; //? 2 + if (inst.ingredients[in].name == "default-space") + inst.ingredients[in].initialized = true; + assert(!inst.ingredients[in].types.empty()); + if (inst.ingredients[in].types[0] // not a literal + && !inst.ingredients[in].initialized + && inst.ingredients[in].name.find_first_not_of("0123456789-.") != string::npos) { + if (!already_transformed(inst.ingredients[in], names)) { + raise << "use before set: " << inst.ingredients[in].name << " in " << Recipe[r].name << '\n'; + } + inst.ingredients[in].set_value(lookup_name(inst.ingredients[in], r)); +//? cout << "lookup ingredient " << Recipe[r].name << "/" << i << ": " << inst.ingredients[in].to_string() << '\n'; //? 1 + } + } + for (size_t out = 0; out < inst.products.size(); ++out) { +//? cout << "products\n"; //? 1 + if (is_raw(inst.products[out])) continue; +//? cout << "product " << out << '/' << inst.products.size() << " " << inst.products[out].name << '\n'; //? 4 +//? cout << inst.products[out].types[0] << '\n'; //? 1 + if (inst.products[out].name == "default-space") + inst.products[out].initialized = true; + if (inst.products[out].types[0] // not a literal + && !inst.products[out].initialized + && inst.products[out].name.find_first_not_of("0123456789-.") != string::npos) { + if (names.find(inst.products[out].name) == names.end()) { + trace("name") << "assign " << inst.products[out].name << " " << curr_idx; + names[inst.products[out].name] = curr_idx; + curr_idx += size_of(inst.products[out]); + } + inst.products[out].set_value(lookup_name(inst.products[out], r)); +//? cout << "lookup product " << Recipe[r].name << "/" << i << ": " << inst.products[out].to_string() << '\n'; //? 1 + } + } + } +} + +bool already_transformed(const reagent& r, const unordered_map& names) { + return names.find(r.name) != names.end(); +} + +size_t lookup_name(const reagent& r, const recipe_number default_recipe) { + return Name[default_recipe][r.name]; +} + +type_number skip_addresses(const vector& types) { + for (size_t i = 0; i < types.size(); ++i) { + if (types[i] != Type_number["address"]) return types[i]; + } + raise << "expected a container" << '\n' << die(); + return -1; +} + +int find_element_name(const type_number t, const string& name) { + const type_info& container = Type[t]; +//? cout << "looking for element " << name << " in type " << container.name << " with " << container.element_names.size() << " elements\n"; //? 1 + for (size_t i = 0; i < container.element_names.size(); ++i) { + if (container.element_names[i] == name) return i; + } + raise << "unknown element " << name << " in container " << t << '\n' << die(); + return -1; +} + +bool is_raw(const reagent& r) { + for (size_t i = /*skip value+type*/1; i < r.properties.size(); ++i) { + if (r.properties[i].first == "raw") return true; + } + return false; +} + +:(scenario "convert_names_passes_dummy") +# _ is just a dummy result that never gets consumed +recipe main [ + _, x:integer <- copy 0:literal +] ++name: assign x 1 +-name: assign _ 1 + +//: one reserved word that we'll need later +:(scenario "convert_names_passes_default_space") +recipe main [ + default-space:integer, x:integer <- copy 0:literal +] ++name: assign x 1 +-name: assign default-space 1 + +//: an escape hatch to suppress name conversion that we'll use later +:(scenario "convert_names_passes_raw") +recipe main [ + x:integer/raw <- copy 0:literal +] +-name: assign x 1 + +//: update our running example container for the next test +:(before "End Mu Types Initialization") +Type[point].element_names.push_back("x"); +Type[point].element_names.push_back("y"); +:(scenario "convert_names_transforms_container_elements") +recipe main [ + a:integer <- get 0:point, y:offset + b:integer <- get 0:point, x:offset +] ++name: element y of type point is at offset 1 ++name: element x of type point is at offset 0 + +:(after "Per-recipe Transforms") +// replace element names of containers with offsets +if (inst.operation == Recipe_number["get"] + || inst.operation == Recipe_number["get-address"]) { + // at least 2 args, and second arg is offset + assert(inst.ingredients.size() >= 2); +//? cout << inst.ingredients[1].to_string() << '\n'; //? 1 + assert(isa_literal(inst.ingredients[1])); + if (inst.ingredients[1].name.find_first_not_of("0123456789") == string::npos) continue; + // since first non-address in base type must be a container, we don't have to canonize + type_number container = skip_addresses(inst.ingredients[0].types); + inst.ingredients[1].set_value(find_element_name(container, inst.ingredients[1].name)); + trace("name") << "element " << inst.ingredients[1].name << " of type " << Type[container].name << " is at offset " << inst.ingredients[1].value; +} + +//: this test is actually illegal so can't call run +:(scenarios transform_test) +:(scenario "convert_names_handles_containers") +recipe main [ + a:point <- copy 0:literal + b:integer <- copy 0:literal +] ++name: assign a 1 ++name: assign b 3 diff --git a/cpp/031scenario b/cpp/031scenario deleted file mode 100644 index c595ce82..00000000 --- a/cpp/031scenario +++ /dev/null @@ -1,132 +0,0 @@ -//: Allow tests to be written in mu files. -:(before "End Types") -struct scenario { - string name; - string to_run; - map memory_expectations; - // End scenario Fields -}; - -:(before "End Globals") -vector Scenarios; - -:(before "End Tests") -time_t mu_time; time(&mu_time); -cerr << "\nMu tests: " << ctime(&mu_time); -for (size_t i = 0; i < Scenarios.size(); ++i) { - setup(); - Trace_file = Scenarios[i].name; - START_TRACING_UNTIL_END_OF_SCOPE -//? Trace_stream->dump_layer = "all"; //? 1 -//? cout << "Before:\n"; dump_memory(); //? 1 -//? cout << Scenarios[i].to_run; //? 1 - run(Scenarios[i].to_run); -//? cout << "After:\n"; dump_memory(); //? 1 - for (map::iterator p = Scenarios[i].memory_expectations.begin(); - p != Scenarios[i].memory_expectations.end(); - ++p) { - if (Memory[p->first] != p->second) { - cerr << Scenarios[i].name << ": Expected location " << p->first << " to contain " << p->second << " but saw " << Memory[p->first] << '\n'; - Passed = false; - } - } - // End Scenario Checks - if (Passed) cerr << "."; -} - -:(before "End Command Handlers") -else if (command == "scenario") { -//? cout << "AAA scenario\n"; //? 1 - Scenarios.push_back(parse_scenario(in)); -} - -:(code) -scenario parse_scenario(istream& in) { - scenario x; - x.name = next_word(in); - trace("parse") << "reading scenario " << x.name; - skip_bracket(in, "'scenario' must begin with '['"); - ostringstream buffer; - slurp_until_matching_bracket(in, buffer); -//? cout << "inner buffer: ^" << buffer.str() << "$\n"; //? 1 - istringstream inner(buffer.str()); - inner >> std::noskipws; - while (!inner.eof()) { - skip_whitespace_and_comments(inner); - string scenario_command = next_word(inner); - if (scenario_command.empty() && inner.eof()) break; - // Scenario Command Handlers - if (scenario_command == "run") { - handle_scenario_run_directive(inner, x); - trace("parse") << "scenario will run: " << x.to_run; - } - else if (scenario_command == "memory") { - handle_scenario_memory_directive(inner, x); - } - // End Scenario Command Handlers - else { - raise << "unknown command in scenario: ^" << scenario_command << "$\n"; - } - } - return x; -} - -void handle_scenario_run_directive(istream& in, scenario& result) { - skip_bracket(in, "'run' inside scenario must begin with '['"); - ostringstream buffer; - slurp_until_matching_bracket(in, buffer); - result.to_run = "recipe test-"+result.name+" [" + buffer.str() + "]"; -} - -void handle_scenario_memory_directive(istream& in, scenario& out) { - if (next_word(in) != "should") { - raise << "'memory' directive inside scenario must continue 'memory should'\n"; - } - if (next_word(in) != "contain") { - raise << "'memory' directive inside scenario must continue 'memory should contain'\n"; - } - skip_bracket(in, "'memory' directive inside scenario must begin with 'memory should contain ['\n"); - while (true) { - skip_whitespace_and_comments(in); - if (in.eof()) break; -//? cout << "a: " << in.peek() << '\n'; //? 1 - if (in.peek() == ']') break; - int address = 0; in >> address; -//? cout << "address: " << address << '\n'; //? 2 -//? cout << "b: " << in.peek() << '\n'; //? 1 - skip_whitespace_and_comments(in); -//? cout << "c: " << in.peek() << '\n'; //? 1 - string _assign; in >> _assign; assert(_assign == "<-"); - skip_whitespace_and_comments(in); - int value = 0; in >> value; - out.memory_expectations[address] = value; - trace("parse") << "memory expectation: *" << address << " == " << value; - } - skip_whitespace(in); - assert(in.get() == ']'); -} - -void slurp_until_matching_bracket(istream& in, ostream& out) { - int brace_depth = 1; // just scanned '[' - char c; - while (in >> c) { - if (c == '[') ++brace_depth; - if (c == ']') --brace_depth; - if (brace_depth == 0) break; // drop final ']' - out << c; - } -} - -void skip_bracket(istream& in, string message) { - skip_whitespace(in); skip_comments_and_newlines(in); skip_whitespace(in); - if (in.get() != '[') - raise << message << '\n'; -} - -void skip_whitespace_and_comments(istream& in) { - while (true) { - if (isspace(in.peek())) in.get(); - else if (in.peek() == '#') skip_comment(in); - else break; - } -} diff --git a/cpp/032new b/cpp/032new new file mode 100644 index 00000000..e75d4148 --- /dev/null +++ b/cpp/032new @@ -0,0 +1,79 @@ +//: A simple memory allocator to create space for new variables at runtime. + +:(scenarios run) +:(scenario "new") +# call new two times with identical arguments; you should get back different results +recipe main [ + 1:address:integer/raw <- new integer:type + 2:address:integer/raw <- new integer:type + 3:boolean/raw <- equal 1:address:integer/raw, 2:address:integer/raw +] ++mem: storing 0 in location 3 + +:(before "End Globals") +const size_t Alloc_init = 1000; +:(before "End routine Fields") +size_t alloc; +:(replace{} "routine::routine(recipe_number r)") + routine::routine(recipe_number r) :alloc(Alloc_init) { + calls.push(call(r)); + } + +//: first handle 'type' operands +:(before "End Mu Types Initialization") +Type_number["type"] = 0; +:(after "Per-recipe Transforms") +// replace type names with type_numbers +if (inst.operation == Recipe_number["new"]) { + // first arg must be of type 'type' + assert(inst.ingredients.size() >= 1); +//? cout << inst.ingredients[0].to_string() << '\n'; //? 1 + assert(isa_literal(inst.ingredients[0])); + if (inst.ingredients[0].properties[0].second[0] == "type") { + inst.ingredients[0].set_value(Type_number[inst.ingredients[0].name]); + } + trace("new") << inst.ingredients[0].name << " -> " << inst.ingredients[0].value; +} + +:(before "End Globals") +// Operator to look at elements of arrays. +const int NEW = 30; +:(before "End Primitive Recipe Numbers") +Recipe_number["new"] = NEW; +assert(Next_recipe_number == NEW); +Next_recipe_number++; +:(before "End Primitive Recipe Implementations") +case NEW: { + vector result; + trace("mem") << "new alloc: " << Current_routine->alloc; + result.push_back(Current_routine->alloc); + write_memory(instructions[pc].products[0], result); + vector types; + types.push_back(instructions[pc].ingredients[0].value); + if (instructions[pc].ingredients.size() > 1) { + // array + vector capacity = read_memory(instructions[pc].ingredients[1]); + trace("mem") << "array size is " << capacity[0]; + Memory[Current_routine->alloc] = capacity[0]; + Current_routine->alloc += capacity[0]*size_of(types); + } + else { + // scalar + Current_routine->alloc += size_of(types); + } + break; +} + +:(scenario "new_array") +recipe main [ + 1:address:array:integer/raw <- new integer:type, 5:literal + 2:address:integer/raw <- new integer:type + 3:integer/raw <- subtract 2:address:integer/raw, 1:address:array:integer/raw +] ++run: instruction main/0 ++mem: array size is 5 ++run: instruction main/1 ++run: instruction main/2 ++mem: storing 5 in location 3 + +//: vim: ft=cpp diff --git a/cpp/032scenario_test.mu b/cpp/032scenario_test.mu deleted file mode 100644 index bf30ce8f..00000000 --- a/cpp/032scenario_test.mu +++ /dev/null @@ -1,9 +0,0 @@ -# tests for 'scenario' in previous layer -scenario first_scenario_in_mu [ - run [ - 1:integer <- add 2:literal, 2:literal - ] - memory should contain [ - 1 <- 4 - ] -] diff --git a/cpp/033space b/cpp/033space new file mode 100644 index 00000000..1bb91b8e --- /dev/null +++ b/cpp/033space @@ -0,0 +1,90 @@ +//: Spaces help isolate functions from each other. You can create them at will, +//: and all addresses in arguments are implicitly based on the 'default-space' +//: (unless they have the /raw property) + +:(scenarios run) +:(scenario "set_default_space") +# if default-space is 10, and if an array of 5 locals lies from location 11 to 15 (inclusive), +# then location 0 is really location 11, location 1 is really location 12, and so on. +recipe main [ + 10:integer <- copy 5:literal # pretend array; in practice we'll use new + default-space:address:space <- copy 10:literal + 1:integer <- copy 23:literal +] ++mem: storing 23 in location 12 + +:(scenario "deref_sidesteps_default_space") +recipe main [ + # pretend pointer from outside + 7:integer <- copy 34:literal + # pretend array + 10:integer <- copy 5:literal + # actual start of this function + default-space:address:space <- copy 10:literal + 1:address:integer <- copy 7:literal + 8:integer/raw <- copy 1:address:integer/deref +] ++mem: storing 34 in location 8 + +:(before "End call Fields") +size_t default_space; +:(replace "call(recipe_number r) :running_recipe(r)") +call(recipe_number r) :running_recipe(r), pc(0), next_ingredient_to_process(0), default_space(0) {} + +:(replace "reagent r = x" following "reagent canonize(reagent x)") +reagent r = absolutize(x); +:(code) +reagent absolutize(reagent x) { +//? if (Recipe_number.find("increment-counter") != Recipe_number.end()) //? 1 +//? cout << "AAA " << "increment-counter/2: " << Recipe[Recipe_number["increment-counter"]].steps[2].products[0].to_string() << '\n'; //? 1 +//? cout << "absolutize " << x.to_string() << '\n'; //? 3 +//? cout << is_raw(x) << '\n'; //? 1 + if (is_raw(x) || is_dummy(x)) return x; +//? cout << "not raw: " << x.to_string() << '\n'; //? 1 + assert(x.initialized); + reagent r = x; + r.set_value(address(r.value, space_base(r))); +//? cout << "after absolutize: " << r.value << '\n'; //? 1 + r.properties.push_back(pair >("raw", vector())); + assert(is_raw(r)); + return r; +} +:(before "return result" following "reagent deref(reagent x)") +result.properties.push_back(pair >("raw", vector())); + +:(code) +int space_base(const reagent& x) { + return Current_routine->calls.top().default_space; +} + +int address(int offset, int base) { + if (base == 0) return offset; // raw +//? cout << base << '\n'; //? 1 + if (offset >= Memory[base]) { + // todo: test + raise << "location " << offset << " is out of bounds " << Memory[base] << '\n'; + } + return base+1 + offset; +} + +:(after "void write_memory(reagent x, vector data)") + if (x.name == "default-space") { + assert(data.size() == 1); + Current_routine->calls.top().default_space = data[0]; +//? cout << "AAA " << Current_routine->calls.top().default_space << '\n'; //? 1 + return; + } + +:(scenario "get_default_space") +recipe main [ + default-space:address:space <- copy 10:literal + 1:integer/raw <- copy default-space:address:space +] ++mem: storing 10 in location 1 + +:(after "vector read_memory(reagent x)") + if (x.name == "default-space") { + vector result; + result.push_back(Current_routine->calls.top().default_space); + return result; + } diff --git a/cpp/033trace b/cpp/033trace deleted file mode 100644 index 3a7b0b0e..00000000 --- a/cpp/033trace +++ /dev/null @@ -1,21 +0,0 @@ -:(scenario "trace") -recipe main [ - trace [foo], [this is a trace in mu] -] -+foo: this is a trace in mu - -:(before "End Globals") -const int TRACE = 32; -:(before "End Primitive Recipe Numbers") -Recipe_number["trace"] = TRACE; -assert(Next_recipe_number == TRACE); -Next_recipe_number++; -:(before "End Primitive Recipe Implementations") -case TRACE: { - assert(isa_literal(instructions[pc].ingredients[0])); - string label = instructions[pc].ingredients[0].name; - assert(isa_literal(instructions[pc].ingredients[1])); - string message = instructions[pc].ingredients[1].name; - trace(label) << message; - break; -} diff --git a/cpp/034scenario_trace b/cpp/034scenario_trace deleted file mode 100644 index 90dab204..00000000 --- a/cpp/034scenario_trace +++ /dev/null @@ -1,101 +0,0 @@ -:(before "End scenario Fields") -vector > trace_checks; -vector > trace_negative_checks; - -:(before "End Scenario Command Handlers") -else if (scenario_command == "trace") { - handle_scenario_trace_directive(inner, x); -} - -:(before "End Scenario Checks") -check_trace_contents(Scenarios[i]); -check_trace_negative_contents(Scenarios[i]); - -:(code) -void handle_scenario_trace_directive(istream& in, scenario& out) { - if (next_word(in) != "should") { - raise << "'trace' directive inside scenario must continue 'trace should'\n"; - } - string s = next_word(in); - if (s == "not") { - handle_scenario_trace_negative_directive(in, out); - return; - } - if (s != "contain") { - raise << "'trace' directive inside scenario must continue 'trace should [not] contain'\n"; - } - skip_bracket(in, "'trace' directive inside scenario must begin with 'trace should contain ['\n"); - while (true) { - skip_whitespace_and_comments(in); - if (in.eof()) break; - if (in.peek() == ']') break; - string curr_line; - getline(in, curr_line); - istringstream tmp(curr_line); - tmp >> std::noskipws; - string label = slurp_until(tmp, ':'); - if (tmp.get() != ' ') { - raise << "'trace' directive inside scenario should contain lines of the form 'label: message', instead got " << curr_line; - continue; - } - string message = slurp_rest(tmp); - out.trace_checks.push_back(pair(label, message)); - trace("scenario") << "trace: " << label << ": " << message << '\n'; - } - skip_whitespace(in); - assert(in.get() == ']'); -} - -void handle_scenario_trace_negative_directive(istream& in, scenario& out) { - // 'not' already slurped - if (next_word(in) != "contain") { - raise << "'trace' directive inside scenario must continue 'trace should not contain'\n"; - } - skip_bracket(in, "'trace' directive inside scenario must begin with 'trace should not contain ['\n"); - while (true) { - skip_whitespace_and_comments(in); - if (in.eof()) break; - if (in.peek() == ']') break; - string curr_line; - getline(in, curr_line); - istringstream tmp(curr_line); - tmp >> std::noskipws; - string label = slurp_until(tmp, ':'); - if (tmp.get() != ' ') { - raise << "'trace' directive inside scenario should contain lines of the form 'label: message', instead got " << curr_line; - continue; - } - string message = slurp_rest(tmp); - out.trace_negative_checks.push_back(pair(label, message)); - trace("scenario") << "trace: " << label << ": " << message << '\n'; - } - skip_whitespace(in); - assert(in.get() == ']'); -} - -string slurp_rest(istream& in) { - ostringstream out; - char c; - while (in >> c) { - out << c; - } - return out.str(); -} - -void check_trace_contents(const scenario& s) { - if (s.trace_checks.empty()) return; - ostringstream contents; - for (size_t i = 0; i < s.trace_checks.size(); ++i) { - contents << s.trace_checks[i].first << ": " << s.trace_checks[i].second << ""; - } - CHECK_TRACE_CONTENTS(contents.str()); -} - -void check_trace_negative_contents(const scenario& s) { - for (size_t i = 0; i < s.trace_negative_checks.size(); ++i) { - if (trace_count(s.trace_negative_checks[i].first, s.trace_negative_checks[i].second) > 0) { - raise << "trace shouldn't contain " << s.trace_negative_checks[i].first << ": " << s.trace_negative_checks[i].second << '\n'; - Passed = false; - } - } -} diff --git a/cpp/034space_surround b/cpp/034space_surround new file mode 100644 index 00000000..76cd5807 --- /dev/null +++ b/cpp/034space_surround @@ -0,0 +1,50 @@ +//: So far you can have global variables by not setting default-space, and +//: local variables by setting default-space. You can isolate variables +//: between those extremes by creating 'surrounding' spaces. +//: +//: (Surrounding spaces are like lexical scopes in other languages.) + +:(scenario "surrounding_space") +# location 1 in space 1 refers to the space surrounding the default space, here 20. +recipe main [ + 10:integer <- copy 5:literal # pretend array + 20:integer <- copy 5:literal # pretend array + default-space:address:space <- copy 10:literal + 0:address:space/names:dummy <- copy 20:literal # later layers will explain the /names: property + 1:integer <- copy 32:literal + 1:integer/space:1 <- copy 33:literal +] ++run: instruction main/3 ++mem: storing 20 in location 11 ++run: instruction main/4 ++mem: storing 32 in location 12 ++run: instruction main/5 ++mem: storing 33 in location 22 + +//: If you think of a space as a collection of variables with a common +//: lifetime, surrounding allows managing shorter lifetimes inside a longer +//: one. + +:(replace{} "int space_base(const reagent& x)") +int space_base(const reagent& x) { + return space_base(x, space_index(x), Current_routine->calls.top().default_space); +} + +int space_base(const reagent& x, int space_index, int base) { +//? trace("foo") << "base of space " << space_index << '\n'; //? 1 + if (space_index == 0) { +//? trace("foo") << "base of space " << space_index << " is " << base << '\n'; //? 1 + return base; + } +//? trace("foo") << "base of space " << space_index << " is " << Memory[base+1] << '\n'; //? 1 + int result = space_base(x, space_index-1, Memory[base+1]); + return result; +} + +int space_index(const reagent& x) { + for (size_t i = 0; i < x.properties.size(); ++i) { + if (x.properties[i].first == "space") + return to_int(x.properties[i].second[0]); + } + return 0; +} diff --git a/cpp/035literal_string b/cpp/035literal_string new file mode 100644 index 00000000..9ca09115 --- /dev/null +++ b/cpp/035literal_string @@ -0,0 +1,91 @@ +//: Some instructions can take string literals for convenience. +//: +//: Instead of quotes, we'll use [] to delimit strings. That'll reduce the +//: need for escaping since we can support nested brackets. And we can also +//: imagine that 'recipe' might one day itself be defined in mu, doing its own +//: parsing. + +//: First extend the mu parser to support string literals. +:(scenario "string_literal") +recipe main [ + 1:address:array:character <- new [abc def] +] ++parse: ingredient: {name: "abc def", value: 0, type: 0, properties: ["abc def": "literal-string"]} + +:(scenario "string_literal_with_colons") +recipe main [ + 1:address:array:character <- new [abc:def/ghi] +] ++parse: ingredient: {name: "abc:def/ghi", value: 0, type: 0, properties: ["abc:def/ghi": "literal-string"]} + +:(before "End Mu Types Initialization") +Type_number["literal-string"] = 0; + +:(after "string next_word(istream& in)") +if (in.peek() == '[') return slurp_quoted(in); + +:(code) +string slurp_quoted(istream& in) { + assert(!in.eof()); + assert(in.peek() == '['); + ostringstream out; + int size = 0; + while (!in.eof()) { + char c = in.get(); +//? cout << c << '\n'; //? 1 + out << c; +//? cout << out.str() << "$\n"; //? 1 + if (c == '[') ++size; + if (c == ']') --size; + if (size == 0) break; + } + return out.str(); +} + +:(after "reagent::reagent(string s)") +//? cout << s[0] << '\n'; //? 1 + if (s[0] == '[') { + assert(s[s.size()-1] == ']'); + // delete [] delimiters + s.erase(0, 1); + s.erase(s.size()-1, s.size()); + name = s; + types.push_back(0); + properties.push_back(pair >(name, vector())); + properties.back().second.push_back("literal-string"); + return; + } + +:(scenario "string_literal_nested") +recipe main [ + 1:address:array:character <- new [abc [def]] +] ++parse: ingredient: {name: "abc [def]", value: 0, type: 0, properties: ["abc [def]": "literal-string"]} + +//: Next, extend 'new' to handle a string literal argument. +:(scenario "new_string") +recipe main [ + 1:address:array:character <- new [abc def] + 2:character <- index 1:address:array:character/deref, 5:literal +] +# integer code for 'e' ++mem: storing 101 in location 2 + +:(before "End Mu Types Initialization") +Type_number["character"] = Next_type_number++; + +:(after "case NEW" following "Primitive Recipe Implementations") +if (instructions[pc].ingredients[0].properties[0].second[0] == "literal-string") { + // allocate an array just large enough for it + vector result; + result.push_back(Current_routine->alloc); + write_memory(instructions[pc].products[0], result); + // assume that all characters fit in a single location +//? cout << "new string literal: " << instructions[pc].ingredients[0].name << '\n'; //? 1 + Memory[Current_routine->alloc++] = instructions[pc].ingredients[0].name.size(); + for (size_t i = 0; i < instructions[pc].ingredients[0].name.size(); ++i) { + Memory[Current_routine->alloc++] = instructions[pc].ingredients[0].name[i]; + } + // mu strings are not null-terminated in memory + break; +} diff --git a/cpp/035scenario_trace_test.mu b/cpp/035scenario_trace_test.mu deleted file mode 100644 index abf0ef9a..00000000 --- a/cpp/035scenario_trace_test.mu +++ /dev/null @@ -1,27 +0,0 @@ -# tests for trace-checking scenario in previous layer -scenario first_scenario_checking_trace [ - run [ - 1:integer <- add 2:literal, 2:literal - ] - trace should contain [ - mem: storing 4 in location 1 - ] -] - -scenario first_scenario_checking_trace_negative [ - run [ - 1:integer <- add 2:literal, 2:literal - ] - trace should not contain [ - mem: storing 5 in location 1 - ] -] - -scenario trace_in_mu [ - run [ - trace [foo], [aaa] - ] - trace should contain [ - foo: aaa - ] -] diff --git a/cpp/036closure_name b/cpp/036closure_name deleted file mode 100644 index 76a1bdd7..00000000 --- a/cpp/036closure_name +++ /dev/null @@ -1,142 +0,0 @@ -//: Writing to a literal (not computed) address of 0 in a recipe chains two -//: spaces together. When a variable has a property of /space:1, it looks up -//: the variable in the chained/surrounding space. /space:2 looks up the -//: surrounding space of the surrounding space, etc. -:(scenario "closure") -recipe main [ - default-space:address:space <- new location:type, 30:literal - 1:address:space/names:init-counter <- init-counter -#? $print [AAAAAAAAAAAAAAAA] -#? $print 1:address:space - 2:integer/raw <- increment-counter 1:address:space/names:init-counter - 3:integer/raw <- increment-counter 1:address:space/names:init-counter -] - -recipe init-counter [ - default-space:address:space <- new location:type, 30:literal - x:integer <- copy 23:literal - y:integer <- copy 3:literal # variable that will be incremented - reply default-space:address:space -] - -recipe increment-counter [ - default-space:address:space <- new space:literal, 30:literal - 0:address:space/names:init-counter <- next-ingredient # outer space must be created by 'init-counter' above - y:integer/space:1 <- add y:integer/space:1, 1:literal # increment - y:integer <- copy 234:literal # dummy - reply y:integer/space:1 -] - -+name: recipe increment-counter is surrounded by init-counter -+mem: storing 5 in location 3 - -//: To make this work, compute the recipe that provides names for the -//: surrounding space of each recipe. This must happen before transform_names. -:(before "End Globals") -unordered_map Surrounding_space; - -:(after "int main") - Transform.push_back(collect_surrounding_spaces); - -:(code) -void collect_surrounding_spaces(const recipe_number r) { - for (size_t i = 0; i < Recipe[r].steps.size(); ++i) { - const instruction& inst = Recipe[r].steps[i]; - if (inst.is_label) continue; - for (size_t j = 0; j < inst.products.size(); ++j) { - if (isa_literal(inst.products[j])) continue; - if (inst.products[j].name != "0") continue; - if (inst.products[j].types.size() != 2 - || inst.products[j].types[0] != Type_number["address"] - || inst.products[j].types[1] != Type_number["space"]) { - raise << "slot 0 should always have type address:space, but is " << inst.products[j].to_string() << '\n'; - continue; - } - vector s = property(inst.products[j], "names"); - if (s.empty()) - raise << "slot 0 requires a /names property in recipe " << Recipe[r].name << die(); - if (s.size() > 1) raise << "slot 0 should have a single value in /names, got " << inst.products[j].to_string() << '\n'; - string surrounding_recipe_name = s[0]; - if (Surrounding_space.find(r) != Surrounding_space.end() - && Surrounding_space[r] != Recipe_number[surrounding_recipe_name]) { - raise << "recipe " << Recipe[r].name << " can have only one 'surrounding' recipe but has " << Recipe[Surrounding_space[r]].name << " and " << surrounding_recipe_name << '\n'; - continue; - } - trace("name") << "recipe " << Recipe[r].name << " is surrounded by " << surrounding_recipe_name; - Surrounding_space[r] = Recipe_number[surrounding_recipe_name]; - } - } -} - -vector property(const reagent& r, const string& name) { - for (size_t p = 0; p != r.properties.size(); ++p) { - if (r.properties[p].first == name) - return r.properties[p].second; - } - return vector(); -} - -//: Once surrounding spaces are available, transform_names uses them to handle -//: /space properties. - -:(replace{} "size_t lookup_name(const reagent& r, const recipe_number default_recipe)") -size_t lookup_name(const reagent& x, const recipe_number default_recipe) { -//? cout << "AAA " << default_recipe << " " << Recipe[default_recipe].name << '\n'; //? 2 -//? cout << "AAA " << x.to_string() << '\n'; //? 1 - if (!has_property(x, "space")) { - if (Name[default_recipe].empty()) raise << "name not found: " << x.name << '\n' << die(); - return Name[default_recipe][x.name]; - } - vector p = property(x, "space"); - if (p.size() != 1) raise << "/space property should have exactly one (non-negative integer) value\n"; - int n = to_int(p[0]); - assert(n >= 0); - recipe_number surrounding_recipe = lookup_surrounding_recipe(default_recipe, n); - set done; - vector path; - return lookup_name(x, surrounding_recipe, done, path); -} - -// If the recipe we need to lookup this name in doesn't have names done yet, -// recursively call transform_names on it. -size_t lookup_name(const reagent& x, const recipe_number r, set& done, vector& path) { - if (!Name[r].empty()) return Name[r][x.name]; - if (done.find(r) != done.end()) { - raise << "can't compute address of " << x.to_string() << " because "; - for (size_t i = 1; i < path.size(); ++i) { - raise << path[i-1] << " requires computing names of " << path[i] << '\n'; - } - raise << path[path.size()-1] << " requires computing names of " << r << "..ad infinitum\n" << die(); - return 0; - } - done.insert(r); - path.push_back(r); - transform_names(r); // Not passing 'done' through. Might this somehow cause an infinite loop? - assert(!Name[r].empty()); - return Name[r][x.name]; -} - -recipe_number lookup_surrounding_recipe(const recipe_number r, size_t n) { - if (n == 0) return r; - if (Surrounding_space.find(r) == Surrounding_space.end()) { - raise << "don't know surrounding recipe of " << Recipe[r].name << '\n'; - return 0; - } - assert(Surrounding_space[r]); - return lookup_surrounding_recipe(Surrounding_space[r], n-1); -} - -//: weaken use-before-set warnings just a tad -:(replace{} "bool already_transformed(const reagent& r, const unordered_map& names)") -bool already_transformed(const reagent& r, const unordered_map& names) { - if (has_property(r, "space")) { - vector p = property(r, "space"); - assert(p.size() == 1); - if (p[0] != "0") return true; - } - return names.find(r.name) != names.end(); -} - -:(before "End Includes") -#include -using std::set; diff --git a/cpp/036length b/cpp/036length new file mode 100644 index 00000000..b9e3bc3c --- /dev/null +++ b/cpp/036length @@ -0,0 +1,30 @@ +:(scenario "array_length") +recipe main [ + 1:integer <- copy 3:literal + 2:integer <- copy 14:literal + 3:integer <- copy 15:literal + 4:integer <- copy 16:literal + 5:integer <- length 1:array:integer +] ++run: instruction main/4 ++mem: storing 3 in location 5 + +:(before "End Globals") +const int LENGTH = 31; +:(before "End Primitive Recipe Numbers") +Recipe_number["length"] = LENGTH; +assert(Next_recipe_number == LENGTH); +Next_recipe_number++; +:(before "End Primitive Recipe Implementations") +case LENGTH: { + reagent x = canonize(instructions[pc].ingredients[0]); + if (x.types[0] != Type_number["array"]) { + raise << "tried to calculate length of non-array " << x.to_string() << '\n'; + break; + } + vector result; +//? cout << "length: " << x.value << '\n'; //? 1 + result.push_back(Memory[x.value]); + write_memory(instructions[pc].products[0], result); + break; +} diff --git a/cpp/040scenario b/cpp/040scenario new file mode 100644 index 00000000..c595ce82 --- /dev/null +++ b/cpp/040scenario @@ -0,0 +1,132 @@ +//: Allow tests to be written in mu files. +:(before "End Types") +struct scenario { + string name; + string to_run; + map memory_expectations; + // End scenario Fields +}; + +:(before "End Globals") +vector Scenarios; + +:(before "End Tests") +time_t mu_time; time(&mu_time); +cerr << "\nMu tests: " << ctime(&mu_time); +for (size_t i = 0; i < Scenarios.size(); ++i) { + setup(); + Trace_file = Scenarios[i].name; + START_TRACING_UNTIL_END_OF_SCOPE +//? Trace_stream->dump_layer = "all"; //? 1 +//? cout << "Before:\n"; dump_memory(); //? 1 +//? cout << Scenarios[i].to_run; //? 1 + run(Scenarios[i].to_run); +//? cout << "After:\n"; dump_memory(); //? 1 + for (map::iterator p = Scenarios[i].memory_expectations.begin(); + p != Scenarios[i].memory_expectations.end(); + ++p) { + if (Memory[p->first] != p->second) { + cerr << Scenarios[i].name << ": Expected location " << p->first << " to contain " << p->second << " but saw " << Memory[p->first] << '\n'; + Passed = false; + } + } + // End Scenario Checks + if (Passed) cerr << "."; +} + +:(before "End Command Handlers") +else if (command == "scenario") { +//? cout << "AAA scenario\n"; //? 1 + Scenarios.push_back(parse_scenario(in)); +} + +:(code) +scenario parse_scenario(istream& in) { + scenario x; + x.name = next_word(in); + trace("parse") << "reading scenario " << x.name; + skip_bracket(in, "'scenario' must begin with '['"); + ostringstream buffer; + slurp_until_matching_bracket(in, buffer); +//? cout << "inner buffer: ^" << buffer.str() << "$\n"; //? 1 + istringstream inner(buffer.str()); + inner >> std::noskipws; + while (!inner.eof()) { + skip_whitespace_and_comments(inner); + string scenario_command = next_word(inner); + if (scenario_command.empty() && inner.eof()) break; + // Scenario Command Handlers + if (scenario_command == "run") { + handle_scenario_run_directive(inner, x); + trace("parse") << "scenario will run: " << x.to_run; + } + else if (scenario_command == "memory") { + handle_scenario_memory_directive(inner, x); + } + // End Scenario Command Handlers + else { + raise << "unknown command in scenario: ^" << scenario_command << "$\n"; + } + } + return x; +} + +void handle_scenario_run_directive(istream& in, scenario& result) { + skip_bracket(in, "'run' inside scenario must begin with '['"); + ostringstream buffer; + slurp_until_matching_bracket(in, buffer); + result.to_run = "recipe test-"+result.name+" [" + buffer.str() + "]"; +} + +void handle_scenario_memory_directive(istream& in, scenario& out) { + if (next_word(in) != "should") { + raise << "'memory' directive inside scenario must continue 'memory should'\n"; + } + if (next_word(in) != "contain") { + raise << "'memory' directive inside scenario must continue 'memory should contain'\n"; + } + skip_bracket(in, "'memory' directive inside scenario must begin with 'memory should contain ['\n"); + while (true) { + skip_whitespace_and_comments(in); + if (in.eof()) break; +//? cout << "a: " << in.peek() << '\n'; //? 1 + if (in.peek() == ']') break; + int address = 0; in >> address; +//? cout << "address: " << address << '\n'; //? 2 +//? cout << "b: " << in.peek() << '\n'; //? 1 + skip_whitespace_and_comments(in); +//? cout << "c: " << in.peek() << '\n'; //? 1 + string _assign; in >> _assign; assert(_assign == "<-"); + skip_whitespace_and_comments(in); + int value = 0; in >> value; + out.memory_expectations[address] = value; + trace("parse") << "memory expectation: *" << address << " == " << value; + } + skip_whitespace(in); + assert(in.get() == ']'); +} + +void slurp_until_matching_bracket(istream& in, ostream& out) { + int brace_depth = 1; // just scanned '[' + char c; + while (in >> c) { + if (c == '[') ++brace_depth; + if (c == ']') --brace_depth; + if (brace_depth == 0) break; // drop final ']' + out << c; + } +} + +void skip_bracket(istream& in, string message) { + skip_whitespace(in); skip_comments_and_newlines(in); skip_whitespace(in); + if (in.get() != '[') + raise << message << '\n'; +} + +void skip_whitespace_and_comments(istream& in) { + while (true) { + if (isspace(in.peek())) in.get(); + else if (in.peek() == '#') skip_comment(in); + else break; + } +} diff --git a/cpp/040string.mu b/cpp/040string.mu deleted file mode 100644 index e85dc083..00000000 --- a/cpp/040string.mu +++ /dev/null @@ -1,96 +0,0 @@ -recipe string-equal [ - default-space:address:space <- new location:type, 30:literal - a:address:array:character <- next-ingredient - a-len:integer <- length a:address:array:character/deref - b:address:array:character <- next-ingredient - b-len:integer <- length b:address:array:character/deref - # compare lengths - { - trace [string-equal], [comparing lengths] - length-equal?:boolean <- equal a-len:integer, b-len:integer - break-if length-equal?:boolean - reply 0:literal - } - # compare each corresponding character - trace [string-equal], [comparing characters] - i:integer <- copy 0:literal - { - done?:boolean <- greater-or-equal i:integer, a-len:integer - break-if done?:boolean - a2:character <- index a:address:array:character/deref, i:integer - b2:character <- index b:address:array:character/deref, i:integer - { - chars-match?:boolean <- equal a2:character, b2:character - break-if chars-match?:boolean - reply 0:literal - } - i:integer <- add i:integer, 1:literal - loop - } - reply 1:literal -] - -scenario string-equal-reflexive [ - run [ - default-space:address:space <- new location:type, 30:literal - x:address:array:character <- new [abc] - 3:boolean/raw <- string-equal x:address:array:character, x:address:array:character - ] - memory should contain [ - 3 <- 1 # x == x for all x - ] -] - -scenario string-equal-identical [ - run [ - default-space:address:space <- new location:type, 30:literal - x:address:array:character <- new [abc] - y:address:array:character <- new [abc] - 3:boolean/raw <- string-equal x:address:array:character, y:address:array:character - ] - memory should contain [ - 3 <- 1 # abc == abc - ] -] - -scenario string-equal-distinct-lengths [ - run [ - default-space:address:space <- new location:type, 30:literal - x:address:array:character <- new [abc] - y:address:array:character <- new [abcd] - 3:boolean/raw <- string-equal x:address:array:character, y:address:array:character - ] - memory should contain [ - 3 <- 0 # abc != abcd - ] - trace should contain [ - string-equal: comparing lengths - ] - trace should not contain [ - string-equal: comparing characters - ] -] - -scenario string-equal-with-empty [ - run [ - default-space:address:space <- new location:type, 30:literal - x:address:array:character <- new [] - y:address:array:character <- new [abcd] - 3:boolean/raw <- string-equal x:address:array:character, y:address:array:character - ] - memory should contain [ - 3 <- 0 # "" != abcd - ] -] - -scenario string-equal-common-lengths-but-distinct [ - run [ - default-space:address:space <- new location:type, 30:literal - x:address:array:character <- new [abc] - y:address:array:character <- new [abd] - 3:boolean/raw <- string-equal x:address:array:character, y:address:array:character - ] - memory should contain [ - 3 <- 0 # abc != abd - ] -] diff --git a/cpp/041scenario_test.mu b/cpp/041scenario_test.mu new file mode 100644 index 00000000..bf30ce8f --- /dev/null +++ b/cpp/041scenario_test.mu @@ -0,0 +1,9 @@ +# tests for 'scenario' in previous layer +scenario first_scenario_in_mu [ + run [ + 1:integer <- add 2:literal, 2:literal + ] + memory should contain [ + 1 <- 4 + ] +] diff --git a/cpp/042trace b/cpp/042trace new file mode 100644 index 00000000..3a7b0b0e --- /dev/null +++ b/cpp/042trace @@ -0,0 +1,21 @@ +:(scenario "trace") +recipe main [ + trace [foo], [this is a trace in mu] +] ++foo: this is a trace in mu + +:(before "End Globals") +const int TRACE = 32; +:(before "End Primitive Recipe Numbers") +Recipe_number["trace"] = TRACE; +assert(Next_recipe_number == TRACE); +Next_recipe_number++; +:(before "End Primitive Recipe Implementations") +case TRACE: { + assert(isa_literal(instructions[pc].ingredients[0])); + string label = instructions[pc].ingredients[0].name; + assert(isa_literal(instructions[pc].ingredients[1])); + string message = instructions[pc].ingredients[1].name; + trace(label) << message; + break; +} diff --git a/cpp/043scenario_trace b/cpp/043scenario_trace new file mode 100644 index 00000000..90dab204 --- /dev/null +++ b/cpp/043scenario_trace @@ -0,0 +1,101 @@ +:(before "End scenario Fields") +vector > trace_checks; +vector > trace_negative_checks; + +:(before "End Scenario Command Handlers") +else if (scenario_command == "trace") { + handle_scenario_trace_directive(inner, x); +} + +:(before "End Scenario Checks") +check_trace_contents(Scenarios[i]); +check_trace_negative_contents(Scenarios[i]); + +:(code) +void handle_scenario_trace_directive(istream& in, scenario& out) { + if (next_word(in) != "should") { + raise << "'trace' directive inside scenario must continue 'trace should'\n"; + } + string s = next_word(in); + if (s == "not") { + handle_scenario_trace_negative_directive(in, out); + return; + } + if (s != "contain") { + raise << "'trace' directive inside scenario must continue 'trace should [not] contain'\n"; + } + skip_bracket(in, "'trace' directive inside scenario must begin with 'trace should contain ['\n"); + while (true) { + skip_whitespace_and_comments(in); + if (in.eof()) break; + if (in.peek() == ']') break; + string curr_line; + getline(in, curr_line); + istringstream tmp(curr_line); + tmp >> std::noskipws; + string label = slurp_until(tmp, ':'); + if (tmp.get() != ' ') { + raise << "'trace' directive inside scenario should contain lines of the form 'label: message', instead got " << curr_line; + continue; + } + string message = slurp_rest(tmp); + out.trace_checks.push_back(pair(label, message)); + trace("scenario") << "trace: " << label << ": " << message << '\n'; + } + skip_whitespace(in); + assert(in.get() == ']'); +} + +void handle_scenario_trace_negative_directive(istream& in, scenario& out) { + // 'not' already slurped + if (next_word(in) != "contain") { + raise << "'trace' directive inside scenario must continue 'trace should not contain'\n"; + } + skip_bracket(in, "'trace' directive inside scenario must begin with 'trace should not contain ['\n"); + while (true) { + skip_whitespace_and_comments(in); + if (in.eof()) break; + if (in.peek() == ']') break; + string curr_line; + getline(in, curr_line); + istringstream tmp(curr_line); + tmp >> std::noskipws; + string label = slurp_until(tmp, ':'); + if (tmp.get() != ' ') { + raise << "'trace' directive inside scenario should contain lines of the form 'label: message', instead got " << curr_line; + continue; + } + string message = slurp_rest(tmp); + out.trace_negative_checks.push_back(pair(label, message)); + trace("scenario") << "trace: " << label << ": " << message << '\n'; + } + skip_whitespace(in); + assert(in.get() == ']'); +} + +string slurp_rest(istream& in) { + ostringstream out; + char c; + while (in >> c) { + out << c; + } + return out.str(); +} + +void check_trace_contents(const scenario& s) { + if (s.trace_checks.empty()) return; + ostringstream contents; + for (size_t i = 0; i < s.trace_checks.size(); ++i) { + contents << s.trace_checks[i].first << ": " << s.trace_checks[i].second << ""; + } + CHECK_TRACE_CONTENTS(contents.str()); +} + +void check_trace_negative_contents(const scenario& s) { + for (size_t i = 0; i < s.trace_negative_checks.size(); ++i) { + if (trace_count(s.trace_negative_checks[i].first, s.trace_negative_checks[i].second) > 0) { + raise << "trace shouldn't contain " << s.trace_negative_checks[i].first << ": " << s.trace_negative_checks[i].second << '\n'; + Passed = false; + } + } +} diff --git a/cpp/044scenario_trace_test.mu b/cpp/044scenario_trace_test.mu new file mode 100644 index 00000000..abf0ef9a --- /dev/null +++ b/cpp/044scenario_trace_test.mu @@ -0,0 +1,27 @@ +# tests for trace-checking scenario in previous layer +scenario first_scenario_checking_trace [ + run [ + 1:integer <- add 2:literal, 2:literal + ] + trace should contain [ + mem: storing 4 in location 1 + ] +] + +scenario first_scenario_checking_trace_negative [ + run [ + 1:integer <- add 2:literal, 2:literal + ] + trace should not contain [ + mem: storing 5 in location 1 + ] +] + +scenario trace_in_mu [ + run [ + trace [foo], [aaa] + ] + trace should contain [ + foo: aaa + ] +] diff --git a/cpp/045closure_name b/cpp/045closure_name new file mode 100644 index 00000000..76a1bdd7 --- /dev/null +++ b/cpp/045closure_name @@ -0,0 +1,142 @@ +//: Writing to a literal (not computed) address of 0 in a recipe chains two +//: spaces together. When a variable has a property of /space:1, it looks up +//: the variable in the chained/surrounding space. /space:2 looks up the +//: surrounding space of the surrounding space, etc. +:(scenario "closure") +recipe main [ + default-space:address:space <- new location:type, 30:literal + 1:address:space/names:init-counter <- init-counter +#? $print [AAAAAAAAAAAAAAAA] +#? $print 1:address:space + 2:integer/raw <- increment-counter 1:address:space/names:init-counter + 3:integer/raw <- increment-counter 1:address:space/names:init-counter +] + +recipe init-counter [ + default-space:address:space <- new location:type, 30:literal + x:integer <- copy 23:literal + y:integer <- copy 3:literal # variable that will be incremented + reply default-space:address:space +] + +recipe increment-counter [ + default-space:address:space <- new space:literal, 30:literal + 0:address:space/names:init-counter <- next-ingredient # outer space must be created by 'init-counter' above + y:integer/space:1 <- add y:integer/space:1, 1:literal # increment + y:integer <- copy 234:literal # dummy + reply y:integer/space:1 +] + ++name: recipe increment-counter is surrounded by init-counter ++mem: storing 5 in location 3 + +//: To make this work, compute the recipe that provides names for the +//: surrounding space of each recipe. This must happen before transform_names. +:(before "End Globals") +unordered_map Surrounding_space; + +:(after "int main") + Transform.push_back(collect_surrounding_spaces); + +:(code) +void collect_surrounding_spaces(const recipe_number r) { + for (size_t i = 0; i < Recipe[r].steps.size(); ++i) { + const instruction& inst = Recipe[r].steps[i]; + if (inst.is_label) continue; + for (size_t j = 0; j < inst.products.size(); ++j) { + if (isa_literal(inst.products[j])) continue; + if (inst.products[j].name != "0") continue; + if (inst.products[j].types.size() != 2 + || inst.products[j].types[0] != Type_number["address"] + || inst.products[j].types[1] != Type_number["space"]) { + raise << "slot 0 should always have type address:space, but is " << inst.products[j].to_string() << '\n'; + continue; + } + vector s = property(inst.products[j], "names"); + if (s.empty()) + raise << "slot 0 requires a /names property in recipe " << Recipe[r].name << die(); + if (s.size() > 1) raise << "slot 0 should have a single value in /names, got " << inst.products[j].to_string() << '\n'; + string surrounding_recipe_name = s[0]; + if (Surrounding_space.find(r) != Surrounding_space.end() + && Surrounding_space[r] != Recipe_number[surrounding_recipe_name]) { + raise << "recipe " << Recipe[r].name << " can have only one 'surrounding' recipe but has " << Recipe[Surrounding_space[r]].name << " and " << surrounding_recipe_name << '\n'; + continue; + } + trace("name") << "recipe " << Recipe[r].name << " is surrounded by " << surrounding_recipe_name; + Surrounding_space[r] = Recipe_number[surrounding_recipe_name]; + } + } +} + +vector property(const reagent& r, const string& name) { + for (size_t p = 0; p != r.properties.size(); ++p) { + if (r.properties[p].first == name) + return r.properties[p].second; + } + return vector(); +} + +//: Once surrounding spaces are available, transform_names uses them to handle +//: /space properties. + +:(replace{} "size_t lookup_name(const reagent& r, const recipe_number default_recipe)") +size_t lookup_name(const reagent& x, const recipe_number default_recipe) { +//? cout << "AAA " << default_recipe << " " << Recipe[default_recipe].name << '\n'; //? 2 +//? cout << "AAA " << x.to_string() << '\n'; //? 1 + if (!has_property(x, "space")) { + if (Name[default_recipe].empty()) raise << "name not found: " << x.name << '\n' << die(); + return Name[default_recipe][x.name]; + } + vector p = property(x, "space"); + if (p.size() != 1) raise << "/space property should have exactly one (non-negative integer) value\n"; + int n = to_int(p[0]); + assert(n >= 0); + recipe_number surrounding_recipe = lookup_surrounding_recipe(default_recipe, n); + set done; + vector path; + return lookup_name(x, surrounding_recipe, done, path); +} + +// If the recipe we need to lookup this name in doesn't have names done yet, +// recursively call transform_names on it. +size_t lookup_name(const reagent& x, const recipe_number r, set& done, vector& path) { + if (!Name[r].empty()) return Name[r][x.name]; + if (done.find(r) != done.end()) { + raise << "can't compute address of " << x.to_string() << " because "; + for (size_t i = 1; i < path.size(); ++i) { + raise << path[i-1] << " requires computing names of " << path[i] << '\n'; + } + raise << path[path.size()-1] << " requires computing names of " << r << "..ad infinitum\n" << die(); + return 0; + } + done.insert(r); + path.push_back(r); + transform_names(r); // Not passing 'done' through. Might this somehow cause an infinite loop? + assert(!Name[r].empty()); + return Name[r][x.name]; +} + +recipe_number lookup_surrounding_recipe(const recipe_number r, size_t n) { + if (n == 0) return r; + if (Surrounding_space.find(r) == Surrounding_space.end()) { + raise << "don't know surrounding recipe of " << Recipe[r].name << '\n'; + return 0; + } + assert(Surrounding_space[r]); + return lookup_surrounding_recipe(Surrounding_space[r], n-1); +} + +//: weaken use-before-set warnings just a tad +:(replace{} "bool already_transformed(const reagent& r, const unordered_map& names)") +bool already_transformed(const reagent& r, const unordered_map& names) { + if (has_property(r, "space")) { + vector p = property(r, "space"); + assert(p.size() == 1); + if (p[0] != "0") return true; + } + return names.find(r.name) != names.end(); +} + +:(before "End Includes") +#include +using std::set; diff --git a/cpp/050string.mu b/cpp/050string.mu new file mode 100644 index 00000000..e85dc083 --- /dev/null +++ b/cpp/050string.mu @@ -0,0 +1,96 @@ +recipe string-equal [ + default-space:address:space <- new location:type, 30:literal + a:address:array:character <- next-ingredient + a-len:integer <- length a:address:array:character/deref + b:address:array:character <- next-ingredient + b-len:integer <- length b:address:array:character/deref + # compare lengths + { + trace [string-equal], [comparing lengths] + length-equal?:boolean <- equal a-len:integer, b-len:integer + break-if length-equal?:boolean + reply 0:literal + } + # compare each corresponding character + trace [string-equal], [comparing characters] + i:integer <- copy 0:literal + { + done?:boolean <- greater-or-equal i:integer, a-len:integer + break-if done?:boolean + a2:character <- index a:address:array:character/deref, i:integer + b2:character <- index b:address:array:character/deref, i:integer + { + chars-match?:boolean <- equal a2:character, b2:character + break-if chars-match?:boolean + reply 0:literal + } + i:integer <- add i:integer, 1:literal + loop + } + reply 1:literal +] + +scenario string-equal-reflexive [ + run [ + default-space:address:space <- new location:type, 30:literal + x:address:array:character <- new [abc] + 3:boolean/raw <- string-equal x:address:array:character, x:address:array:character + ] + memory should contain [ + 3 <- 1 # x == x for all x + ] +] + +scenario string-equal-identical [ + run [ + default-space:address:space <- new location:type, 30:literal + x:address:array:character <- new [abc] + y:address:array:character <- new [abc] + 3:boolean/raw <- string-equal x:address:array:character, y:address:array:character + ] + memory should contain [ + 3 <- 1 # abc == abc + ] +] + +scenario string-equal-distinct-lengths [ + run [ + default-space:address:space <- new location:type, 30:literal + x:address:array:character <- new [abc] + y:address:array:character <- new [abcd] + 3:boolean/raw <- string-equal x:address:array:character, y:address:array:character + ] + memory should contain [ + 3 <- 0 # abc != abcd + ] + trace should contain [ + string-equal: comparing lengths + ] + trace should not contain [ + string-equal: comparing characters + ] +] + +scenario string-equal-with-empty [ + run [ + default-space:address:space <- new location:type, 30:literal + x:address:array:character <- new [] + y:address:array:character <- new [abcd] + 3:boolean/raw <- string-equal x:address:array:character, y:address:array:character + ] + memory should contain [ + 3 <- 0 # "" != abcd + ] +] + +scenario string-equal-common-lengths-but-distinct [ + run [ + default-space:address:space <- new location:type, 30:literal + x:address:array:character <- new [abc] + y:address:array:character <- new [abd] + 3:boolean/raw <- string-equal x:address:array:character, y:address:array:character + ] + memory should contain [ + 3 <- 0 # abc != abd + ] +] -- cgit 1.4.1-2-gfad0