From cdf2822743b3beeb37ebc3deea8e08b6130698c5 Mon Sep 17 00:00:00 2001 From: Kartik Agaram Date: Sat, 12 May 2018 10:22:26 -0700 Subject: 4242 - get rid of refcounts entirely We're going to lean back into the experiment of commit 4179 back in Jan. If we delete memory it's up to us to ensure no pointers into it survive. Since deep-copy depends on our refcounting infrastructure, it's gone as well. So we're going to have to start watching out for pointers shared over channels. --- html/010vm.cc.html | 6 +- html/036refcount.cc.html | 554 +--------------- html/055shape_shifting_container.cc.html | 266 ++++---- html/061text.mu.html | 12 +- html/063array.mu.html | 8 +- html/068random.mu.html | 14 +- html/070table.mu.html | 16 +- html/073scheduler.cc.html | 885 +++++++++++++------------- html/075channel.mu.html | 1012 +++++++++++++++--------------- html/081print.mu.html | 10 +- html/084console.mu.html | 4 +- html/088file.mu.html | 48 +- html/090scenario_filesystem_test.mu.html | 34 +- html/092socket.mu.html | 30 +- html/channel.mu.html | 16 +- html/chessboard.mu.html | 60 +- html/filesystem.mu.html | 6 +- html/http-client.mu.html | 2 +- html/http-server.mu.html | 6 +- html/real-files.mu.html | 2 +- 20 files changed, 1215 insertions(+), 1776 deletions(-) (limited to 'html') diff --git a/html/010vm.cc.html b/html/010vm.cc.html index 096cc89f..a22385f6 100644 --- a/html/010vm.cc.html +++ b/html/010vm.cc.html @@ -408,7 +408,7 @@ if ('onhashchange' in window) { 345 int value = 0; 346 if (contains_key(Type_ordinal, type_name)) 347 value = get(Type_ordinal, type_name); -348 else if (is_integer(type_name)) // sometimes types will contain non-type tags, like numbers for the size of an array +348 else if (is_integer(type_name)) // sometimes types will contain literal integers, like for the size of an array 349 value = 0; 350 else if (properties->value == "->") // used in recipe types 351 value = 0; @@ -464,7 +464,7 @@ if ('onhashchange' in window) { 401 // only constraint we care about: if a < b then !(b < a) 402 bool type_tree::operator<(const type_tree& other) const { 403 if (atom != other.atom) return atom > other.atom; // atoms before non-atoms -404 if (atom) return name < other.name; // sort atoms in lexical order +404 if (atom) return value < other.value; 405 // first location in one that's missing in the other makes that side 'smaller' 406 if (left && !other.left) return false; 407 if (!left && other.left) return true; @@ -518,7 +518,7 @@ if ('onhashchange' in window) { 455 CHECK(!(*b.type < *a.type)); 456 } 457 void test_compare_list_with_smaller_left_but_larger_right() { -458 reagent a("a:address:number"), b("b:character:array"); +458 reagent a("a:number:character"), b("b:boolean:array"); 459 CHECK(*a.type < *b.type); 460 CHECK(!(*b.type < *a.type)); 461 } diff --git a/html/036refcount.cc.html b/html/036refcount.cc.html index ffc58fb3..c5635881 100644 --- a/html/036refcount.cc.html +++ b/html/036refcount.cc.html @@ -22,7 +22,6 @@ a:hover { text-decoration: underline; } .LineNr { color: #444444; } .Identifier { color: #c0a020; } .Normal { color: #aaaaaa; background-color: #080808; padding-bottom: 1px; } -.cSpecial { color: #008000; } --> @@ -57,530 +56,35 @@ if ('onhashchange' in window) {
-  1 int payload_size(reagent/*copy*/ x) {
-  2   x.properties.push_back(pair<string, string_tree*>("lookup", NULL));
-  3   lookup_memory_core(x, /*check_for_null*/false);
-  4   return size_of(x);
-  5 }
-  6 
-  7 :(before "End type_tree Definition")
-  8 struct address_element_info {
-  9   // Where inside a container type (after flattening nested containers!) the
- 10   // address lies
- 11   int offset;
- 12 
- 13   // All the information we need to compute sizes of items inside an address
- 14   // inside a container. 'payload_type' doesn't need to be a full-scale
- 15   // reagent because an address inside a container can never be an array, and
- 16   // because arrays are the only type that need to know their location to
- 17   // compute their size.
- 18   const type_tree* payload_type;
- 19 
- 20   address_element_info(int o, const type_tree* p);
- 21   address_element_info(const address_element_info& other);
- 22   ~address_element_info();
- 23   address_element_info& operator=(const address_element_info& other);
- 24 };
- 25 :(code)
- 26 address_element_info::address_element_info(int o, const type_tree* p) {
- 27   offset = o;
- 28   payload_type = p;
- 29 }
- 30 address_element_info::address_element_info(const address_element_info& other) {
- 31   offset = other.offset;
- 32   payload_type = copy(other.payload_type);
- 33 }
- 34 address_element_info::~address_element_info() {
- 35   if (payload_type) {
- 36     delete payload_type;
- 37     payload_type = NULL;
- 38   }
- 39 }
- 40 address_element_info& address_element_info::operator=(const address_element_info& other) {
- 41   offset = other.offset;
- 42   if (payload_type) delete payload_type;
- 43   payload_type = copy(other.payload_type);
- 44   return *this;
- 45 }
- 46 
- 47 :(before "End type_tree Definition")
- 48 // For exclusive containers we might sometimes have an address at some offset
- 49 // if some other offset has a specific tag. This struct encapsulates such
- 50 // guards.
- 51 struct tag_condition_info {
- 52   int offset;
- 53   int tag;
- 54   tag_condition_info(int o, int t) :offset(o), tag(t) {}
- 55 };
- 56 
- 57 :(before "End container_metadata Fields")
- 58 // a list of facts of the form:
- 59 //
- 60 //  IF offset o1 has tag t2 AND offset o2 has tag t2 AND .., THEN
- 61 //    for all address_element_infos:
- 62 //      there is an address at 'offset' pointing to a payload of type payload_type
- 63 map<set<tag_condition_info>, set<address_element_info> > address;
- 64 :(code)
- 65 bool operator<(const set<tag_condition_info>& a, const set<tag_condition_info>& b) {
- 66   if (a.size() != b.size()) return a.size() < b.size();
- 67   for (set<tag_condition_info>::const_iterator pa = a.begin(), pb = b.begin();  pa != a.end();  ++pa, ++pb) {
- 68     if (pa->offset != pb->offset) return pa->offset < pb->offset;
- 69     if (pa->tag != pb->tag) return pa->tag < pb->tag;
- 70   }
- 71   return false;  // equal
- 72 }
- 73 bool operator<(const tag_condition_info& a, const tag_condition_info& b) {
- 74   if (a.offset != b.offset) return a.offset < b.offset;
- 75   if (a.tag != b.tag) return a.tag < b.tag;
- 76   return false;  // equal
- 77 }
- 78 bool operator<(const set<address_element_info>& a, const set<address_element_info>& b) {
- 79   if (a.size() != b.size()) return a.size() < b.size();
- 80   for (set<address_element_info>::const_iterator pa = a.begin(), pb = b.begin();  pa != a.end();  ++pa, ++pb) {
- 81     if (pa->offset != pb->offset) return pa->offset < pb->offset;
- 82   }
- 83   return false;  // equal
- 84 }
- 85 bool operator<(const address_element_info& a, const address_element_info& b) {
- 86   if (a.offset != b.offset) return a.offset < b.offset;
- 87   return false;  // equal
- 88 }
- 89 
- 90 //: populate metadata.address in a separate transform, because it requires
- 91 //: already knowing the sizes of all types
- 92 
- 93 :(after "Transform.push_back(compute_container_sizes)")
- 94 Transform.push_back(compute_container_address_offsets);  // idempotent
- 95 :(code)
- 96 void compute_container_address_offsets(const recipe_ordinal r) {
- 97   recipe& caller = get(Recipe, r);
- 98   trace(9992, "transform") << "--- compute address offsets for " << caller.name << end();
- 99   for (int i = 0;  i < SIZE(caller.steps);  ++i) {
-100     instruction& inst = caller.steps.at(i);
-101     trace(9993, "transform") << "- compute address offsets for " << to_string(inst) << end();
-102     for (int i = 0;  i < SIZE(inst.ingredients);  ++i)
-103       compute_container_address_offsets(inst.ingredients.at(i), " in '"+inst.original_string+"'");
-104     for (int i = 0;  i < SIZE(inst.products);  ++i)
-105       compute_container_address_offsets(inst.products.at(i), " in '"+inst.original_string+"'");
-106   }
-107 }
-108 
-109 void compute_container_address_offsets(reagent& r, const string& location_for_error_messages) {
-110   if (is_literal(r) || is_dummy(r)) return;
-111   compute_container_address_offsets(r.type, location_for_error_messages);
-112   if (contains_key(Container_metadata, r.type))
-113     r.metadata = get(Container_metadata, r.type);
-114 }
-115 
-116 // the recursive structure of this function needs to exactly match
-117 // compute_container_sizes
-118 void compute_container_address_offsets(const type_tree* type, const string& location_for_error_messages) {
-119   if (!type) return;
-120   if (!type->atom) {
-121     if (!type->left->atom) {
-122       raise << "invalid type " << to_string(type) << location_for_error_messages << '\n' << end();
-123       return;
-124     }
-125     if (type->left->name == "address")
-126       compute_container_address_offsets(payload_type(type), location_for_error_messages);
-127     else if (type->left->name == "array")
-128       compute_container_address_offsets(array_element(type), location_for_error_messages);
-129     // End compute_container_address_offsets Non-atom Special-cases
-130   }
-131   const type_tree* base_type = type;
-132   // Update base_type in compute_container_address_offsets
-133   if (!contains_key(Type, base_type->value)) return;  // error raised elsewhere
-134   type_info& info = get(Type, base_type->value);
-135   if (info.kind == CONTAINER) {
-136     compute_container_address_offsets(info, type, location_for_error_messages);
-137   }
-138   if (info.kind == EXCLUSIVE_CONTAINER) {
-139     compute_exclusive_container_address_offsets(info, type, location_for_error_messages);
-140   }
-141 }
-142 
-143 void compute_container_address_offsets(const type_info& container_info, const type_tree* full_type, const string& location_for_error_messages) {
-144   container_metadata& metadata = get(Container_metadata, full_type);
-145   if (!metadata.address.empty()) return;
-146   trace(9994, "transform") << "compute address offsets for container " << container_info.name << end();
-147   append_addresses(0, full_type, metadata.address, set<tag_condition_info>(), location_for_error_messages);
-148 }
-149 
-150 void compute_exclusive_container_address_offsets(const type_info& exclusive_container_info, const type_tree* full_type, const string& location_for_error_messages) {
-151   container_metadata& metadata = get(Container_metadata, full_type);
-152   trace(9994, "transform") << "compute address offsets for exclusive container " << exclusive_container_info.name << end();
-153   for (int tag = 0;  tag < SIZE(exclusive_container_info.elements);  ++tag) {
-154     set<tag_condition_info> key;
-155     key.insert(tag_condition_info(/*tag is at offset*/0, tag));
-156     append_addresses(/*skip tag offset*/1, variant_type(full_type, tag).type, metadata.address, key, location_for_error_messages);
-157   }
-158 }
-159 
-160 void append_addresses(int base_offset, const type_tree* type, map<set<tag_condition_info>, set<address_element_info> >& out, const set<tag_condition_info>& key, const string& location_for_error_messages) {
-161   if (is_mu_address(type)) {
-162     get_or_insert(out, key).insert(address_element_info(base_offset, new type_tree(*payload_type(type))));
-163     return;
-164   }
-165   const type_tree* base_type = type;
-166   // Update base_type in append_container_address_offsets
-167   const type_info& info = get(Type, base_type->value);
-168   if (info.kind == CONTAINER) {
-169     for (int curr_index = 0, curr_offset = base_offset;  curr_index < SIZE(info.elements);  ++curr_index) {
-170       trace(9993, "transform") << "checking container " << base_type->name << ", element " << curr_index << end();
-171       reagent/*copy*/ element = element_type(type, curr_index);  // not base_type
-172       // Compute Container Address Offset(element)
-173       if (is_mu_address(element)) {
-174         trace(9993, "transform") << "address at offset " << curr_offset << end();
-175         get_or_insert(out, key).insert(address_element_info(curr_offset, new type_tree(*payload_type(element.type))));
-176         ++curr_offset;
-177       }
-178       else if (is_mu_array(element)) {
-179         curr_offset += /*array length*/1;
-180         const type_tree* array_element_type = array_element(element.type);
-181         int array_element_size = size_of(array_element_type);
-182         for (int i = 0; i < static_array_length(element.type); ++i) {
-183           append_addresses(curr_offset, array_element_type, out, key, location_for_error_messages);
-184           curr_offset += array_element_size;
-185         }
-186       }
-187       else if (is_mu_container(element)) {
-188         append_addresses(curr_offset, element.type, out, key, location_for_error_messages);
-189         curr_offset += size_of(element);
-190       }
-191       else if (is_mu_exclusive_container(element)) {
-192         const type_tree* element_base_type = element.type;
-193         // Update element_base_type For Exclusive Container in append_addresses
-194         const type_info& element_info = get(Type, element_base_type->value);
-195         for (int tag = 0;  tag < SIZE(element_info.elements);  ++tag) {
-196           set<tag_condition_info> new_key = key;
-197           new_key.insert(tag_condition_info(curr_offset, tag));
-198           if (!contains_key(out, new_key))
-199             append_addresses(curr_offset+/*skip tag*/1, variant_type(element.type, tag).type, out, new_key, location_for_error_messages);
-200         }
-201         curr_offset += size_of(element);
-202       }
-203       else {
-204         // non-address primitive
-205         ++curr_offset;
-206       }
-207     }
-208   }
-209   else if (info.kind == EXCLUSIVE_CONTAINER) {
-210     for (int tag = 0;  tag < SIZE(info.elements);  ++tag) {
-211       set<tag_condition_info> new_key = key;
-212       new_key.insert(tag_condition_info(base_offset, tag));
-213       if (!contains_key(out, new_key))
-214         append_addresses(base_offset+/*skip tag*/1, variant_type(type, tag).type, out, new_key, location_for_error_messages);
-215     }
-216   }
-217 }
-218 
-219 //: for the following unit tests we'll do the work of the transform by hand
-220 
-221 :(before "End Unit Tests")
-222 void test_container_address_offsets_empty() {
-223   int old_size = SIZE(Container_metadata);
-224   // define a container with no addresses
-225   reagent r("x:point");
-226   compute_container_sizes(r, "");  // need to first pre-populate the metadata
-227   // scan
-228   compute_container_address_offsets(r, "");
-229   // global metadata contains just the entry for foo
-230   // no entries for non-container types or other junk
-231   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
-232   // the reagent we scanned knows it has no addresses
-233   CHECK(r.metadata.address.empty());
-234   // the global table contains an identical entry
-235   CHECK(contains_key(Container_metadata, r.type));
-236   CHECK(get(Container_metadata, r.type).address.empty());
-237   // compute_container_address_offsets creates no new entries
-238   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
-239 }
-240 
-241 void test_container_address_offsets() {
-242   int old_size = SIZE(Container_metadata);
-243   // define a container with an address at offset 0 that we have the size for
-244   run("container foo [\n"
-245       "  x:address:num\n"
-246       "]\n");
-247   reagent r("x:foo");
-248   compute_container_sizes(r, "");  // need to first pre-populate the metadata
-249   // scan
-250   compute_container_address_offsets(r, "");
-251   // global metadata contains just the entry for foo
-252   // no entries for non-container types or other junk
-253   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
-254   // the reagent we scanned knows it has an address at offset 0
-255   CHECK_EQ(SIZE(r.metadata.address), 1);
-256   CHECK(contains_key(r.metadata.address, set<tag_condition_info>()));
-257   const set<address_element_info>& address_offsets = get(r.metadata.address, set<tag_condition_info>());  // unconditional for containers
-258   CHECK_EQ(SIZE(address_offsets), 1);
-259   CHECK_EQ(address_offsets.begin()->offset, 0);
-260   CHECK(address_offsets.begin()->payload_type->atom);
-261   CHECK_EQ(address_offsets.begin()->payload_type->name, "number");
-262   // the global table contains an identical entry
-263   CHECK(contains_key(Container_metadata, r.type));
-264   const set<address_element_info>& address_offsets2 = get(get(Container_metadata, r.type).address, set<tag_condition_info>());
-265   CHECK_EQ(SIZE(address_offsets2), 1);
-266   CHECK_EQ(address_offsets2.begin()->offset, 0);
-267   CHECK(address_offsets2.begin()->payload_type->atom);
-268   CHECK_EQ(address_offsets2.begin()->payload_type->name, "number");
-269   // compute_container_address_offsets creates no new entries
-270   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
-271 }
-272 
-273 void test_container_address_offsets_2() {
-274   int old_size = SIZE(Container_metadata);
-275   // define a container with an address at offset 1 that we have the size for
-276   run("container foo [\n"
-277       "  x:num\n"
-278       "  y:address:num\n"
-279       "]\n");
-280   reagent r("x:foo");
-281   compute_container_sizes(r, "");  // need to first pre-populate the metadata
-282   // global metadata contains just the entry for foo
-283   // no entries for non-container types or other junk
-284   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
-285   // scan
-286   compute_container_address_offsets(r, "");
-287   // compute_container_address_offsets creates no new entries
-288   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
-289   // the reagent we scanned knows it has an address at offset 1
-290   CHECK_EQ(SIZE(r.metadata.address), 1);
-291   CHECK(contains_key(r.metadata.address, set<tag_condition_info>()));
-292   const set<address_element_info>& address_offsets = get(r.metadata.address, set<tag_condition_info>());
-293   CHECK_EQ(SIZE(address_offsets), 1);
-294   CHECK_EQ(address_offsets.begin()->offset, 1);  //
-295   CHECK(address_offsets.begin()->payload_type->atom);
-296   CHECK_EQ(address_offsets.begin()->payload_type->name, "number");
-297   // the global table contains an identical entry
-298   CHECK(contains_key(Container_metadata, r.type));
-299   const set<address_element_info>& address_offsets2 = get(get(Container_metadata, r.type).address, set<tag_condition_info>());
-300   CHECK_EQ(SIZE(address_offsets2), 1);
-301   CHECK_EQ(address_offsets2.begin()->offset, 1);  //
-302   CHECK(address_offsets2.begin()->payload_type->atom);
-303   CHECK_EQ(address_offsets2.begin()->payload_type->name, "number");
-304 }
-305 
-306 void test_container_address_offsets_nested() {
-307   int old_size = SIZE(Container_metadata);
-308   // define a container with a nested container containing an address
-309   run("container foo [\n"
-310       "  x:address:num\n"
-311       "  y:num\n"
-312       "]\n"
-313       "container bar [\n"
-314       "  p:point\n"
-315       "  f:foo\n"  // nested container containing address
-316       "]\n");
-317   reagent r("x:bar");
-318   compute_container_sizes(r, "");  // need to first pre-populate the metadata
-319   // global metadata contains entries for bar and included types: point and foo
-320   // no entries for non-container types or other junk
-321   CHECK_EQ(SIZE(Container_metadata)-old_size, 3);
-322   // scan
-323   compute_container_address_offsets(r, "");
-324   // the reagent we scanned knows it has an address at offset 2
-325   CHECK_EQ(SIZE(r.metadata.address), 1);
-326   CHECK(contains_key(r.metadata.address, set<tag_condition_info>()));
-327   const set<address_element_info>& address_offsets = get(r.metadata.address, set<tag_condition_info>());
-328   CHECK_EQ(SIZE(address_offsets), 1);
-329   CHECK_EQ(address_offsets.begin()->offset, 2);  //
-330   CHECK(address_offsets.begin()->payload_type->atom);
-331   CHECK_EQ(address_offsets.begin()->payload_type->name, "number");
-332   // the global table also knows its address offset
-333   CHECK(contains_key(Container_metadata, r.type));
-334   const set<address_element_info>& address_offsets2 = get(get(Container_metadata, r.type).address, set<tag_condition_info>());
-335   CHECK_EQ(SIZE(address_offsets2), 1);
-336   CHECK_EQ(address_offsets2.begin()->offset, 2);  //
-337   CHECK(address_offsets2.begin()->payload_type->atom);
-338   CHECK_EQ(address_offsets2.begin()->payload_type->name, "number");
-339   // compute_container_address_offsets creates no new entries
-340   CHECK_EQ(SIZE(Container_metadata)-old_size, 3);
-341 }
-342 
-343 void test_container_address_offsets_from_address() {
-344   int old_size = SIZE(Container_metadata);
-345   // define a container with an address at offset 0
-346   run("container foo [\n"
-347       "  x:address:num\n"
-348       "]\n");
-349   reagent r("x:address:foo");
-350   compute_container_sizes(r, "");  // need to first pre-populate the metadata
-351   // global metadata contains just the entry for foo
-352   // no entries for non-container types or other junk
-353   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
-354   // scan an address to the container
-355   compute_container_address_offsets(r, "");
-356   // compute_container_address_offsets creates no new entries
-357   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
-358   // scanning precomputed metadata for the container
-359   reagent container("x:foo");
-360   CHECK(contains_key(Container_metadata, container.type));
-361   const set<address_element_info>& address_offsets2 = get(get(Container_metadata, container.type).address, set<tag_condition_info>());
-362   CHECK_EQ(SIZE(address_offsets2), 1);
-363   CHECK_EQ(address_offsets2.begin()->offset, 0);
-364   CHECK(address_offsets2.begin()->payload_type->atom);
-365   CHECK_EQ(address_offsets2.begin()->payload_type->name, "number");
-366 }
-367 
-368 void test_container_address_offsets_from_array() {
-369   int old_size = SIZE(Container_metadata);
-370   // define a container with an address at offset 0
-371   run("container foo [\n"
-372       "  x:address:num\n"
-373       "]\n");
-374   reagent r("x:array:foo");
-375   compute_container_sizes(r, "");  // need to first pre-populate the metadata
-376   // global metadata contains just the entry for foo
-377   // no entries for non-container types or other junk
-378   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
-379   // scan an array of the container
-380   compute_container_address_offsets(r, "");
-381   // compute_container_address_offsets creates no new entries
-382   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
-383   // scanning precomputed metadata for the container
-384   reagent container("x:foo");
-385   CHECK(contains_key(Container_metadata, container.type));
-386   const set<address_element_info>& address_offsets2 = get(get(Container_metadata, container.type).address, set<tag_condition_info>());
-387   CHECK_EQ(SIZE(address_offsets2), 1);
-388   CHECK_EQ(address_offsets2.begin()->offset, 0);
-389   CHECK(address_offsets2.begin()->payload_type->atom);
-390   CHECK_EQ(address_offsets2.begin()->payload_type->name, "number");
-391 }
-392 
-393 void test_container_address_offsets_from_address_to_array() {
-394   int old_size = SIZE(Container_metadata);
-395   // define a container with an address at offset 0
-396   run("container foo [\n"
-397       "  x:address:num\n"
-398       "]\n");
-399   reagent r("x:address:array:foo");
-400   compute_container_sizes(r, "");  // need to first pre-populate the metadata
-401   // global metadata contains just the entry for foo
-402   // no entries for non-container types or other junk
-403   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
-404   // scan an address to an array of the container
-405   compute_container_address_offsets(r, "");
-406   // compute_container_address_offsets creates no new entries
-407   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
-408   // scanning precomputed metadata for the container
-409   reagent container("x:foo");
-410   CHECK(contains_key(Container_metadata, container.type));
-411   const set<address_element_info>& address_offsets2 = get(get(Container_metadata, container.type).address, set<tag_condition_info>());
-412   CHECK_EQ(SIZE(address_offsets2), 1);
-413   CHECK_EQ(address_offsets2.begin()->offset, 0);
-414   CHECK(address_offsets2.begin()->payload_type->atom);
-415   CHECK_EQ(address_offsets2.begin()->payload_type->name, "number");
-416 }
-417 
-418 void test_container_address_offsets_from_static_array() {
-419   int old_size = SIZE(Container_metadata);
-420   // define a container with an address at offset 0
-421   run("container foo [\n"
-422       "  x:address:num\n"
-423       "]\n");
-424   reagent r("x:array:foo:10");
-425   compute_container_sizes(r, "");  // need to first pre-populate the metadata
-426   // global metadata contains just the entry for foo
-427   // no entries for non-container types or other junk
-428   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
-429   // scan a static array of the container
-430   compute_container_address_offsets(r, "");
-431   // compute_container_address_offsets creates no new entries
-432   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
-433   // scanning precomputed metadata for the container
-434   reagent container("x:foo");
-435   CHECK(contains_key(Container_metadata, container.type));
-436   const set<address_element_info>& address_offsets2 = get(get(Container_metadata, container.type).address, set<tag_condition_info>());
-437   CHECK_EQ(SIZE(address_offsets2), 1);
-438   CHECK_EQ(address_offsets2.begin()->offset, 0);
-439   CHECK(address_offsets2.begin()->payload_type->atom);
-440   CHECK_EQ(address_offsets2.begin()->payload_type->name, "number");
-441 }
-442 
-443 void test_container_address_offsets_from_address_to_static_array() {
-444   int old_size = SIZE(Container_metadata);
-445   // define a container with an address at offset 0
-446   run("container foo [\n"
-447       "  x:address:num\n"
-448       "]\n");
-449   reagent r("x:address:array:foo:10");
-450   compute_container_sizes(r, "");  // need to first pre-populate the metadata
-451   // global metadata contains just the entry for foo
-452   // no entries for non-container types or other junk
-453   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
-454   // scan an address to a static array of the container
-455   compute_container_address_offsets(r, "");
-456   // compute_container_address_offsets creates no new entries
-457   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
-458   // scanning precomputed metadata for the container
-459   reagent container("x:foo");
-460   CHECK(contains_key(Container_metadata, container.type));
-461   const set<address_element_info>& address_offsets2 = get(get(Container_metadata, container.type).address, set<tag_condition_info>());
-462   CHECK_EQ(SIZE(address_offsets2), 1);
-463   CHECK_EQ(address_offsets2.begin()->offset, 0);
-464   CHECK(address_offsets2.begin()->payload_type->atom);
-465   CHECK_EQ(address_offsets2.begin()->payload_type->name, "number");
-466 }
-467 
-468 void test_container_address_offsets_from_repeated_address_and_array_types() {
-469   int old_size = SIZE(Container_metadata);
-470   // define a container with an address at offset 0
-471   run("container foo [\n"
-472       "  x:address:num\n"
-473       "]\n");
-474   // scan a deep nest of 'address' and 'array' types modifying a container
-475   reagent r("x:address:array:address:address:array:foo:10");
-476   compute_container_sizes(r, "");  // need to first pre-populate the metadata
-477   // global metadata contains just the entry for foo
-478   // no entries for non-container types or other junk
-479   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
-480   compute_container_address_offsets(r, "");
-481   // compute_container_address_offsets creates no new entries
-482   CHECK_EQ(SIZE(Container_metadata)-old_size, 1);
-483   // scanning precomputed metadata for the container
-484   reagent container("x:foo");
-485   CHECK(contains_key(Container_metadata, container.type));
-486   const set<address_element_info>& address_offsets2 = get(get(Container_metadata, container.type).address, set<tag_condition_info>());
-487   CHECK_EQ(SIZE(address_offsets2), 1);
-488   CHECK_EQ(address_offsets2.begin()->offset, 0);
-489   CHECK(address_offsets2.begin()->payload_type->atom);
-490   CHECK_EQ(address_offsets2.begin()->payload_type->name, "number");
-491 }
-492 
-493 :(code)
-494 bool all_match(const vector<double>& data, const set<tag_condition_info>& conditions) {
-495   for (set<tag_condition_info>::const_iterator p = conditions.begin();  p != conditions.end();  ++p) {
-496     if (data.at(p->offset) != p->tag)
-497       return false;
-498   }
-499   return true;
-500 }
-501 
-502 bool is_mu_container(const reagent& r) {
-503   return is_mu_container(r.type);
-504 }
-505 bool is_mu_container(const type_tree* type) {
-506   if (!type) return false;
-507   // End is_mu_container(type) Special-cases
-508   if (type->value == 0) return false;
-509   if (!contains_key(Type, type->value)) return false;  // error raised elsewhere
-510   type_info& info = get(Type, type->value);
-511   return info.kind == CONTAINER;
-512 }
-513 
-514 bool is_mu_exclusive_container(const reagent& r) {
-515   return is_mu_exclusive_container(r.type);
-516 }
-517 bool is_mu_exclusive_container(const type_tree* type) {
-518   if (!type) return false;
-519   // End is_mu_exclusive_container(type) Special-cases
-520   if (type->value == 0) return false;
-521   if (!contains_key(Type, type->value)) return false;  // error raised elsewhere
-522   type_info& info = get(Type, type->value);
-523   return info.kind == EXCLUSIVE_CONTAINER;
-524 }
+ 1 int payload_size(reagent/*copy*/ x) {
+ 2   x.properties.push_back(pair<string, string_tree*>("lookup", NULL));
+ 3   lookup_memory_core(x, /*check_for_null*/false);
+ 4   return size_of(x);
+ 5 }
+ 6 
+ 7 bool is_mu_container(const reagent& r) {
+ 8   return is_mu_container(r.type);
+ 9 }
+10 bool is_mu_container(const type_tree* type) {
+11   if (!type) return false;
+12   // End is_mu_container(type) Special-cases
+13   if (type->value == 0) return false;
+14   if (!contains_key(Type, type->value)) return false;  // error raised elsewhere
+15   type_info& info = get(Type, type->value);
+16   return info.kind == CONTAINER;
+17 }
+18 
+19 bool is_mu_exclusive_container(const reagent& r) {
+20   return is_mu_exclusive_container(r.type);
+21 }
+22 bool is_mu_exclusive_container(const type_tree* type) {
+23   if (!type) return false;
+24   // End is_mu_exclusive_container(type) Special-cases
+25   if (type->value == 0) return false;
+26   if (!contains_key(Type, type->value)) return false;  // error raised elsewhere
+27   type_info& info = get(Type, type->value);
+28   return info.kind == EXCLUSIVE_CONTAINER;
+29 }
 
diff --git a/html/055shape_shifting_container.cc.html b/html/055shape_shifting_container.cc.html index fcff28ed..419864c4 100644 --- a/html/055shape_shifting_container.cc.html +++ b/html/055shape_shifting_container.cc.html @@ -87,12 +87,12 @@ if ('onhashchange' in window) { 22 base_type = get_base_type(base_type); 23 :(after "Update base_type in element_type") 24 base_type = get_base_type(base_type); - 25 :(after "Update base_type in compute_container_address_offsets") - 26 base_type = get_base_type(base_type); - 27 :(after "Update base_type in append_container_address_offsets") - 28 base_type = get_base_type(base_type); - 29 :(after "Update element_base_type For Exclusive Container in append_addresses") - 30 element_base_type = get_base_type(element_base_type); + 25 //? :(after "Update base_type in compute_container_address_offsets") + 26 //? base_type = get_base_type(base_type); + 27 //? :(after "Update base_type in append_container_address_offsets") + 28 //? base_type = get_base_type(base_type); + 29 //? :(after "Update element_base_type For Exclusive Container in append_addresses") + 30 //? element_base_type = get_base_type(element_base_type); 31 :(after "Update base_type in skip_addresses") 32 base_type = get_base_type(base_type); 33 :(replace{} "const type_tree* get_base_type(const type_tree* t)") @@ -385,9 +385,9 @@ if ('onhashchange' in window) { 320 replace_type_ingredients(element, full_type, container_info, location_for_error_messages); 321 :(before "Compute Exclusive Container Size(element, full_type)") 322 replace_type_ingredients(element, full_type, exclusive_container_info, location_for_error_messages); -323 :(before "Compute Container Address Offset(element)") -324 replace_type_ingredients(element, type, info, location_for_error_messages); -325 if (contains_type_ingredient(element)) return; // error raised elsewhere +323 //? :(before "Compute Container Address Offset(element)") +324 //? replace_type_ingredients(element, type, info, location_for_error_messages); +325 //? if (contains_type_ingredient(element)) return; // error raised elsewhere 326 327 :(after "Compute size_of Container") 328 assert(!contains_type_ingredient(type)); @@ -680,160 +680,104 @@ if ('onhashchange' in window) { 615 CHECK_EQ(r2.metadata.size, 2); 616 } 617 -618 :(before "End compute_container_address_offsets Non-atom Special-cases") -619 const type_tree* root = get_base_type(type); -620 if (!contains_key(Type, root->value)) return; // error raised elsewhere -621 type_info& info = get(Type, root->value); -622 if (info.kind == CONTAINER) { -623 compute_container_address_offsets(info, type, location_for_error_messages); -624 return; -625 } -626 if (info.kind == EXCLUSIVE_CONTAINER) { -627 compute_exclusive_container_address_offsets(info, type, location_for_error_messages); -628 return; -629 } -630 -631 :(before "End Unit Tests") -632 void test_container_address_offsets_in_shape_shifting_container() { -633 run("container foo:_t [\n" -634 " x:num\n" -635 " y:_t\n" -636 "]\n"); -637 reagent r("x:foo:&:num"); -638 compute_container_sizes(r, ""); -639 compute_container_address_offsets(r, ""); -640 CHECK_EQ(SIZE(r.metadata.address), 1); -641 CHECK(contains_key(r.metadata.address, set<tag_condition_info>())); -642 set<address_element_info>& offset_info = get(r.metadata.address, set<tag_condition_info>()); -643 CHECK_EQ(SIZE(offset_info), 1); -644 CHECK_EQ(offset_info.begin()->offset, 1); // -645 CHECK(offset_info.begin()->payload_type->atom); -646 CHECK_EQ(offset_info.begin()->payload_type->name, "number"); -647 } -648 -649 void test_container_address_offsets_in_nested_shape_shifting_container() { -650 run("container foo:_t [\n" -651 " x:num\n" -652 " y:_t\n" -653 "]\n" -654 "container bar:_t [\n" -655 " x:_t\n" -656 " y:foo:_t\n" -657 "]\n"); -658 reagent r("x:bar:&:num"); -659 CLEAR_TRACE; -660 compute_container_sizes(r, ""); -661 compute_container_address_offsets(r, ""); -662 CHECK_EQ(SIZE(r.metadata.address), 1); -663 CHECK(contains_key(r.metadata.address, set<tag_condition_info>())); -664 set<address_element_info>& offset_info = get(r.metadata.address, set<tag_condition_info>()); -665 CHECK_EQ(SIZE(offset_info), 2); -666 CHECK_EQ(offset_info.begin()->offset, 0); // -667 CHECK(offset_info.begin()->payload_type->atom); -668 CHECK_EQ(offset_info.begin()->payload_type->name, "number"); -669 CHECK_EQ((++offset_info.begin())->offset, 2); // -670 CHECK((++offset_info.begin())->payload_type->atom); -671 CHECK_EQ((++offset_info.begin())->payload_type->name, "number"); -672 } -673 -674 :(scenario typos_in_container_definitions) -675 % Hide_errors = true; -676 container foo:_t [ -677 x:adress:_t # typo -678 ] -679 def main [ -680 local-scope -681 x:address:foo:num <- new {(foo num): type} +618 :(scenario typos_in_container_definitions) +619 % Hide_errors = true; +620 container foo:_t [ +621 x:adress:_t # typo +622 ] +623 def main [ +624 local-scope +625 x:address:foo:num <- new {(foo num): type} +626 ] +627 # no crash +628 +629 :(scenario typos_in_recipes) +630 % Hide_errors = true; +631 def foo [ +632 local-scope +633 x:adress:array:number <- copy 0 # typo +634 ] +635 # shouldn't crash +636 +637 //:: 'merge' on shape-shifting containers +638 +639 :(scenario merge_check_shape_shifting_container_containing_exclusive_container) +640 container foo:_elem [ +641 x:num +642 y:_elem +643 ] +644 exclusive-container bar [ +645 x:num +646 y:num +647 ] +648 def main [ +649 1:foo:bar <- merge 23, 1/y, 34 +650 ] +651 +mem: storing 23 in location 1 +652 +mem: storing 1 in location 2 +653 +mem: storing 34 in location 3 +654 $error: 0 +655 +656 :(scenario merge_check_shape_shifting_container_containing_exclusive_container_2) +657 % Hide_errors = true; +658 container foo:_elem [ +659 x:num +660 y:_elem +661 ] +662 exclusive-container bar [ +663 x:num +664 y:num +665 ] +666 def main [ +667 1:foo:bar <- merge 23, 1/y, 34, 35 +668 ] +669 +error: main: too many ingredients in '1:foo:bar <- merge 23, 1/y, 34, 35' +670 +671 :(scenario merge_check_shape_shifting_exclusive_container_containing_container) +672 exclusive-container foo:_elem [ +673 x:num +674 y:_elem +675 ] +676 container bar [ +677 x:num +678 y:num +679 ] +680 def main [ +681 1:foo:bar <- merge 1/y, 23, 34 682 ] -683 # no crash -684 -685 :(scenario typos_in_recipes) -686 % Hide_errors = true; -687 def foo [ -688 local-scope -689 x:adress:array:number <- copy 0 # typo -690 ] -691 # shouldn't crash -692 -693 //:: 'merge' on shape-shifting containers -694 -695 :(scenario merge_check_shape_shifting_container_containing_exclusive_container) -696 container foo:_elem [ -697 x:num -698 y:_elem +683 +mem: storing 1 in location 1 +684 +mem: storing 23 in location 2 +685 +mem: storing 34 in location 3 +686 $error: 0 +687 +688 :(scenario merge_check_shape_shifting_exclusive_container_containing_container_2) +689 exclusive-container foo:_elem [ +690 x:num +691 y:_elem +692 ] +693 container bar [ +694 x:num +695 y:num +696 ] +697 def main [ +698 1:foo:bar <- merge 0/x, 23 699 ] -700 exclusive-container bar [ -701 x:num -702 y:num -703 ] -704 def main [ -705 1:foo:bar <- merge 23, 1/y, 34 -706 ] -707 +mem: storing 23 in location 1 -708 +mem: storing 1 in location 2 -709 +mem: storing 34 in location 3 -710 $error: 0 -711 -712 :(scenario merge_check_shape_shifting_container_containing_exclusive_container_2) -713 % Hide_errors = true; -714 container foo:_elem [ -715 x:num -716 y:_elem -717 ] -718 exclusive-container bar [ -719 x:num -720 y:num -721 ] -722 def main [ -723 1:foo:bar <- merge 23, 1/y, 34, 35 -724 ] -725 +error: main: too many ingredients in '1:foo:bar <- merge 23, 1/y, 34, 35' -726 -727 :(scenario merge_check_shape_shifting_exclusive_container_containing_container) -728 exclusive-container foo:_elem [ -729 x:num -730 y:_elem -731 ] -732 container bar [ -733 x:num -734 y:num -735 ] -736 def main [ -737 1:foo:bar <- merge 1/y, 23, 34 -738 ] -739 +mem: storing 1 in location 1 -740 +mem: storing 23 in location 2 -741 +mem: storing 34 in location 3 -742 $error: 0 -743 -744 :(scenario merge_check_shape_shifting_exclusive_container_containing_container_2) -745 exclusive-container foo:_elem [ -746 x:num -747 y:_elem -748 ] -749 container bar [ -750 x:num -751 y:num -752 ] -753 def main [ -754 1:foo:bar <- merge 0/x, 23 -755 ] -756 $error: 0 -757 -758 :(scenario merge_check_shape_shifting_exclusive_container_containing_container_3) -759 % Hide_errors = true; -760 exclusive-container foo:_elem [ -761 x:num -762 y:_elem -763 ] -764 container bar [ -765 x:num -766 y:num -767 ] -768 def main [ -769 1:foo:bar <- merge 1/y, 23 -770 ] -771 +error: main: too few ingredients in '1:foo:bar <- merge 1/y, 23' +700 $error: 0 +701 +702 :(scenario merge_check_shape_shifting_exclusive_container_containing_container_3) +703 % Hide_errors = true; +704 exclusive-container foo:_elem [ +705 x:num +706 y:_elem +707 ] +708 container bar [ +709 x:num +710 y:num +711 ] +712 def main [ +713 1:foo:bar <- merge 1/y, 23 +714 ] +715 +error: main: too few ingredients in '1:foo:bar <- merge 1/y, 23' diff --git a/html/061text.mu.html b/html/061text.mu.html index 1cf8aa34..d069e621 100644 --- a/html/061text.mu.html +++ b/html/061text.mu.html @@ -183,17 +183,17 @@ if ('onhashchange' in window) { 122 data:&:@:_elem 123 ] 124 - 125 def new-buffer capacity:num -> result:&:buffer:_elem [ + 125 def new-buffer capacity:num -> result:&:buffer:_elem [ 126 local-scope 127 load-inputs 128 result <- new {(buffer _elem): type} 129 *result <- put *result, length:offset, 0 130 { - 131 break-if capacity + 131 break-if capacity 132 # capacity not provided - 133 capacity <- copy 10 + 133 capacity <- copy 10 134 } - 135 data:&:@:_elem <- new _elem:type, capacity + 135 data:&:@:_elem <- new _elem:type, capacity 136 *result <- put *result, data:offset, data 137 return result 138 ] @@ -224,8 +224,8 @@ if ('onhashchange' in window) { 163 load-inputs 164 len:num <- get *in, length:offset 165 s:&:@:_elem <- get *in, data:offset - 166 capacity:num <- length *s - 167 result <- greater-or-equal len, capacity + 166 capacity:num <- length *s + 167 result <- greater-or-equal len, capacity 168 ] 169 170 # most broadly applicable definition of append to a buffer diff --git a/html/063array.mu.html b/html/063array.mu.html index 80244daf..e194f20a 100644 --- a/html/063array.mu.html +++ b/html/063array.mu.html @@ -75,20 +75,20 @@ if ('onhashchange' in window) { 15 # create an array out of a list of args 16 def new-array -> result:&:@:_elem [ 17 local-scope - 18 capacity:num <- copy 0 + 18 capacity:num <- copy 0 19 { 20 # while read curr-value 21 curr-value:_elem, exists?:bool <- next-input 22 break-unless exists? - 23 capacity <- add capacity, 1 + 23 capacity <- add capacity, 1 24 loop 25 } - 26 result <- new _elem:type, capacity + 26 result <- new _elem:type, capacity 27 rewind-inputs 28 i:num <- copy 0 29 { 30 # while read curr-value - 31 done?:bool <- greater-or-equal i, capacity + 31 done?:bool <- greater-or-equal i, capacity 32 break-if done? 33 curr-value:_elem, exists?:bool <- next-input 34 assert exists?, [error in rewinding inputs to new-array] diff --git a/html/068random.mu.html b/html/068random.mu.html index 1c2d47fc..accb90a3 100644 --- a/html/068random.mu.html +++ b/html/068random.mu.html @@ -97,11 +97,11 @@ if ('onhashchange' in window) { 37 38 scenario random-numbers-in-scenario [ 39 local-scope -40 source:&:stream:num <- assume-random-numbers 34, 35, 37 -41 1:num/raw, 2:bool/raw <- random source -42 3:num/raw, 4:bool/raw <- random source -43 5:num/raw, 6:bool/raw <- random source -44 7:num/raw, 8:bool/raw <- random source +40 source:&:stream:num <- assume-random-numbers 34, 35, 37 +41 1:num/raw, 2:bool/raw <- random source +42 3:num/raw, 4:bool/raw <- random source +43 5:num/raw, 6:bool/raw <- random source +44 7:num/raw, 8:bool/raw <- random source 45 memory-should-contain [ 46 1 <- 34 47 2 <- 0 # everything went well @@ -127,8 +127,8 @@ if ('onhashchange' in window) { 67 68 scenario random-in-range [ 69 local-scope -70 source:&:stream:num <- assume-random-numbers 91 -71 1:num/raw <- random-in-range source, 40, 50 +70 source:&:stream:num <- assume-random-numbers 91 +71 1:num/raw <- random-in-range source, 40, 50 72 memory-should-contain [ 73 1 <- 41 74 ] diff --git a/html/070table.mu.html b/html/070table.mu.html index 6e1c73f7..f8e12f73 100644 --- a/html/070table.mu.html +++ b/html/070table.mu.html @@ -104,7 +104,7 @@ if ('onhashchange' in window) { 43 44 container table:_key:_value [ 45 length:num - 46 capacity:num + 46 capacity:num 47 data:&:@:table-row:_key:_value 48 ] 49 @@ -114,12 +114,12 @@ if ('onhashchange' in window) { 53 value:_value 54 ] 55 - 56 def new-table capacity:num -> result:&:table:_key:_value [ + 56 def new-table capacity:num -> result:&:table:_key:_value [ 57 local-scope 58 load-inputs 59 result <- new {(table _key _value): type} - 60 data:&:@:table-row:_key:_value <- new {(table-row _key _value): type}, capacity - 61 *result <- merge 0/length, capacity, data + 60 data:&:@:table-row:_key:_value <- new {(table-row _key _value): type}, capacity + 61 *result <- merge 0/length, capacity, data 62 ] 63 64 # todo: tag results as /required so that call-sites are forbidden from ignoring them @@ -129,8 +129,8 @@ if ('onhashchange' in window) { 68 load-inputs 69 hash:num <- hash key 70 hash <- abs hash - 71 capacity:num <- get *table, capacity:offset - 72 _, hash-key:num <- divide-with-remainder hash, capacity + 71 capacity:num <- get *table, capacity:offset + 72 _, hash-key:num <- divide-with-remainder hash, capacity 73 hash-key <- abs hash-key # in case hash overflows from a double into a negative integer inside 'divide-with-remainder' above 74 table-data:&:@:table-row:_key:_value <- get *table, data:offset 75 x:table-row:_key:_value <- index *table-data, hash-key @@ -146,8 +146,8 @@ if ('onhashchange' in window) { 85 load-inputs 86 hash:num <- hash key 87 hash <- abs hash - 88 capacity:num <- get *table, capacity:offset - 89 _, hash-key:num <- divide-with-remainder hash, capacity + 88 capacity:num <- get *table, capacity:offset + 89 _, hash-key:num <- divide-with-remainder hash, capacity 90 hash-key <- abs hash-key # in case hash overflows from a double into a negative integer inside 'divide-with-remainder' above 91 table-data:&:@:table-row:_key:_value <- get *table, data:offset 92 x:table-row:_key:_value <- index *table-data, hash-key diff --git a/html/073scheduler.cc.html b/html/073scheduler.cc.html index 9d95b952..b3e6e792 100644 --- a/html/073scheduler.cc.html +++ b/html/073scheduler.cc.html @@ -242,449 +242,448 @@ if ('onhashchange' in window) { 178 new_routine->parent_index = Current_routine_index; 179 // populate ingredients 180 for (int i = /*skip callee*/1; i < SIZE(current_instruction().ingredients); ++i) { -181 reagent/*copy*/ ingredient = current_instruction().ingredients.at(i); -182 new_routine->calls.front().ingredients.push_back(ingredient); -183 vector<double> new_ingredient_atoms = deep_copy(ingredient); -184 new_routine->calls.front().ingredient_atoms.push_back(new_ingredient_atoms); -185 // End Populate start-running Ingredient -186 } -187 Routines.push_back(new_routine); -188 products.resize(1); -189 products.at(0).push_back(new_routine->id); -190 break; -191 } -192 -193 :(scenario scheduler_runs_single_routine) -194 % Scheduling_interval = 1; -195 def f1 [ -196 1:num <- copy 0 -197 2:num <- copy 0 -198 ] -199 +schedule: f1 -200 +run: {1: "number"} <- copy {0: "literal"} -201 +schedule: f1 -202 +run: {2: "number"} <- copy {0: "literal"} -203 -204 :(scenario scheduler_interleaves_routines) -205 % Scheduling_interval = 1; -206 def f1 [ -207 start-running f2 -208 1:num <- copy 0 -209 2:num <- copy 0 -210 ] -211 def f2 [ -212 3:num <- copy 0 -213 4:num <- copy 0 -214 ] -215 +schedule: f1 -216 +run: start-running {f2: "recipe-literal"} -217 +schedule: f2 -218 +run: {3: "number"} <- copy {0: "literal"} -219 +schedule: f1 -220 +run: {1: "number"} <- copy {0: "literal"} -221 +schedule: f2 -222 +run: {4: "number"} <- copy {0: "literal"} -223 +schedule: f1 -224 +run: {2: "number"} <- copy {0: "literal"} -225 -226 :(scenario start_running_takes_ingredients) -227 def f1 [ -228 start-running f2, 3 -229 # wait for f2 to run -230 { -231 jump-unless 1:num, -1 -232 } -233 ] -234 def f2 [ -235 1:num <- next-ingredient -236 2:num <- add 1:num, 1 -237 ] -238 +mem: storing 4 in location 2 -239 -240 //: type-checking for 'start-running' -241 -242 :(scenario start_running_checks_types) -243 % Hide_errors = true; -244 def f1 [ -245 start-running f2, 3 -246 ] -247 def f2 n:&:num [ -248 ] -249 +error: f1: ingredient 0 has the wrong type at 'start-running f2, 3' -250 -251 // 'start-running' only uses the ingredients of the callee, not its products -252 :(before "End is_indirect_call_with_ingredients Special-cases") -253 if (r == START_RUNNING) return true; -254 -255 //: back to testing 'start-running' -256 -257 :(scenario start_running_returns_routine_id) -258 def f1 [ -259 1:num <- start-running f2 -260 ] -261 def f2 [ -262 12:num <- copy 44 -263 ] -264 +mem: storing 2 in location 1 -265 -266 //: this scenario will require some careful setup in escaped C++ -267 //: (straining our tangle capabilities to near-breaking point) -268 :(scenario scheduler_skips_completed_routines) -269 % recipe_ordinal f1 = load("recipe f1 [\n1:num <- copy 0\n]\n").front(); -270 % recipe_ordinal f2 = load("recipe f2 [\n2:num <- copy 0\n]\n").front(); -271 % Routines.push_back(new routine(f1)); // f1 meant to run -272 % Routines.push_back(new routine(f2)); -273 % Routines.back()->state = COMPLETED; // f2 not meant to run -274 # must have at least one routine without escaping -275 def f3 [ -276 3:num <- copy 0 -277 ] -278 # by interleaving '+' lines with '-' lines, we allow f1 and f3 to run in any order -279 +schedule: f1 -280 +mem: storing 0 in location 1 -281 -schedule: f2 -282 -mem: storing 0 in location 2 -283 +schedule: f3 -284 +mem: storing 0 in location 3 -285 -286 :(scenario scheduler_starts_at_middle_of_routines) -287 % Routines.push_back(new routine(COPY)); -288 % Routines.back()->state = COMPLETED; -289 def f1 [ -290 1:num <- copy 0 -291 2:num <- copy 0 -292 ] -293 +schedule: f1 -294 -run: idle -295 -296 //:: Errors in a routine cause it to terminate. -297 -298 :(scenario scheduler_terminates_routines_after_errors) -299 % Hide_errors = true; -300 % Scheduling_interval = 2; -301 def f1 [ -302 start-running f2 -303 1:num <- copy 0 -304 2:num <- copy 0 -305 ] -306 def f2 [ -307 # divide by 0 twice -308 3:num <- divide-with-remainder 4, 0 -309 4:num <- divide-with-remainder 4, 0 -310 ] -311 # f2 should stop after first divide by 0 -312 +error: f2: divide by zero in '3:num <- divide-with-remainder 4, 0' -313 -error: f2: divide by zero in '4:num <- divide-with-remainder 4, 0' -314 -315 :(after "operator<<(ostream& os, vestigial end)") -316 if (Trace_stream && Trace_stream->curr_label == "error" && Current_routine) { -317 Current_routine->state = COMPLETED; -318 } -319 -320 //:: Routines are marked completed when their parent completes. -321 -322 :(scenario scheduler_kills_orphans) -323 def main [ -324 start-running f1 -325 # f1 never actually runs because its parent completes without waiting for it -326 ] -327 def f1 [ -328 1:num <- copy 0 -329 ] -330 -schedule: f1 -331 -332 :(before "End Scheduler Cleanup") -333 for (int i = 0; i < SIZE(Routines); ++i) { -334 if (Routines.at(i)->state == COMPLETED) continue; -335 if (Routines.at(i)->parent_index < 0) continue; // root thread -336 // structured concurrency: http://250bpm.com/blog:71 -337 if (has_completed_parent(i)) { -338 Routines.at(i)->state = COMPLETED; -339 } -340 } -341 -342 :(code) -343 bool has_completed_parent(int routine_index) { -344 for (int j = routine_index; j >= 0; j = Routines.at(j)->parent_index) { -345 if (Routines.at(j)->state == COMPLETED) -346 return true; -347 } -348 return false; -349 } -350 -351 //:: 'routine-state' can tell if a given routine id is running -352 -353 :(scenario routine_state_test) -354 % Scheduling_interval = 2; -355 def f1 [ -356 1:num/child-id <- start-running f2 -357 12:num <- copy 0 # race condition since we don't care about location 12 -358 # thanks to Scheduling_interval, f2's one instruction runs in between here and completes -359 2:num/state <- routine-state 1:num/child-id -360 ] -361 def f2 [ -362 12:num <- copy 0 -363 # trying to run a second instruction marks routine as completed -364 ] -365 # recipe f2 should be in state COMPLETED -366 +mem: storing 1 in location 2 -367 -368 :(before "End Primitive Recipe Declarations") -369 ROUTINE_STATE, -370 :(before "End Primitive Recipe Numbers") -371 put(Recipe_ordinal, "routine-state", ROUTINE_STATE); -372 :(before "End Primitive Recipe Checks") -373 case ROUTINE_STATE: { -374 if (SIZE(inst.ingredients) != 1) { -375 raise << maybe(get(Recipe, r).name) << "'routine-state' requires exactly one ingredient, but got '" << to_original_string(inst) << "'\n" << end(); -376 break; -377 } -378 if (!is_mu_number(inst.ingredients.at(0))) { -379 raise << maybe(get(Recipe, r).name) << "first ingredient of 'routine-state' should be a routine id generated by 'start-running', but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); -380 break; -381 } -382 break; -383 } -384 :(before "End Primitive Recipe Implementations") -385 case ROUTINE_STATE: { -386 int id = ingredients.at(0).at(0); -387 int result = -1; -388 for (int i = 0; i < SIZE(Routines); ++i) { -389 if (Routines.at(i)->id == id) { -390 result = Routines.at(i)->state; -391 break; -392 } -393 } -394 products.resize(1); -395 products.at(0).push_back(result); -396 break; -397 } -398 -399 //:: miscellaneous helpers -400 -401 :(before "End Primitive Recipe Declarations") -402 STOP, -403 :(before "End Primitive Recipe Numbers") -404 put(Recipe_ordinal, "stop", STOP); -405 :(before "End Primitive Recipe Checks") -406 case STOP: { -407 if (SIZE(inst.ingredients) != 1) { -408 raise << maybe(get(Recipe, r).name) << "'stop' requires exactly one ingredient, but got '" << to_original_string(inst) << "'\n" << end(); -409 break; -410 } -411 if (!is_mu_number(inst.ingredients.at(0))) { -412 raise << maybe(get(Recipe, r).name) << "first ingredient of 'stop' should be a routine id generated by 'start-running', but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); -413 break; -414 } -415 break; -416 } -417 :(before "End Primitive Recipe Implementations") -418 case STOP: { -419 int id = ingredients.at(0).at(0); -420 for (int i = 0; i < SIZE(Routines); ++i) { -421 if (Routines.at(i)->id == id) { -422 Routines.at(i)->state = COMPLETED; -423 break; -424 } -425 } -426 break; -427 } -428 -429 :(before "End Primitive Recipe Declarations") -430 _DUMP_ROUTINES, -431 :(before "End Primitive Recipe Numbers") -432 put(Recipe_ordinal, "$dump-routines", _DUMP_ROUTINES); -433 :(before "End Primitive Recipe Checks") -434 case _DUMP_ROUTINES: { -435 break; -436 } -437 :(before "End Primitive Recipe Implementations") -438 case _DUMP_ROUTINES: { -439 for (int i = 0; i < SIZE(Routines); ++i) { -440 cerr << i << ": " << Routines.at(i)->id << ' ' << Routines.at(i)->state << ' ' << Routines.at(i)->parent_index << '\n'; -441 } -442 break; -443 } -444 -445 //: support for stopping routines after some number of cycles -446 -447 :(scenario routine_discontinues_past_limit) -448 % Scheduling_interval = 2; -449 def f1 [ -450 1:num/child-id <- start-running f2 -451 limit-time 1:num/child-id, 10 -452 # padding loop just to make sure f2 has time to completed -453 2:num <- copy 20 -454 2:num <- subtract 2:num, 1 -455 jump-if 2:num, -2:offset -456 ] -457 def f2 [ -458 jump -1:offset # run forever -459 $print [should never get here], 10/newline -460 ] -461 # f2 terminates -462 +schedule: discontinuing routine 2 -463 -464 :(before "End routine States") -465 DISCONTINUED, -466 :(before "End Scheduler State Transitions") -467 if (Current_routine->limit >= 0) { -468 if (Current_routine->limit <= Scheduling_interval) { -469 trace("schedule") << "discontinuing routine " << Current_routine->id << end(); -470 Current_routine->state = DISCONTINUED; -471 Current_routine->limit = 0; -472 } -473 else { -474 Current_routine->limit -= Scheduling_interval; -475 } -476 } -477 -478 :(before "End Test Teardown") -479 if (Passed && any_routines_with_error()) -480 raise << "some routines died with errors\n" << end(); -481 :(before "End Mu Test Teardown") -482 if (Passed && any_routines_with_error()) -483 raise << Current_scenario->name << ": some routines died with errors\n" << end(); -484 -485 :(code) -486 bool any_routines_with_error() { -487 for (int i = 0; i < SIZE(Routines); ++i) { -488 if (Routines.at(i)->state == DISCONTINUED) -489 return true; -490 } -491 return false; -492 } -493 -494 :(before "End routine Fields") -495 int limit; -496 :(before "End routine Constructor") -497 limit = -1; /* no limit */ -498 -499 :(before "End Primitive Recipe Declarations") -500 LIMIT_TIME, -501 :(before "End Primitive Recipe Numbers") -502 put(Recipe_ordinal, "limit-time", LIMIT_TIME); -503 :(before "End Primitive Recipe Checks") -504 case LIMIT_TIME: { -505 if (SIZE(inst.ingredients) != 2) { -506 raise << maybe(get(Recipe, r).name) << "'limit-time' requires exactly two ingredient, but got '" << to_original_string(inst) << "'\n" << end(); -507 break; -508 } -509 if (!is_mu_number(inst.ingredients.at(0))) { -510 raise << maybe(get(Recipe, r).name) << "first ingredient of 'limit-time' should be a routine id generated by 'start-running', but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); -511 break; -512 } -513 if (!is_mu_number(inst.ingredients.at(1))) { -514 raise << maybe(get(Recipe, r).name) << "second ingredient of 'limit-time' should be a number (of instructions to run for), but got '" << inst.ingredients.at(1).original_string << "'\n" << end(); -515 break; -516 } -517 break; -518 } -519 :(before "End Primitive Recipe Implementations") -520 case LIMIT_TIME: { -521 int id = ingredients.at(0).at(0); -522 for (int i = 0; i < SIZE(Routines); ++i) { -523 if (Routines.at(i)->id == id) { -524 Routines.at(i)->limit = ingredients.at(1).at(0); -525 break; -526 } -527 } -528 break; -529 } -530 -531 :(before "End routine Fields") -532 int instructions_run; -533 :(before "End routine Constructor") -534 instructions_run = 0; -535 :(before "Reset instructions_run_this_scheduling_slice") -536 Current_routine->instructions_run += Current_routine->instructions_run_this_scheduling_slice; -537 :(before "End Primitive Recipe Declarations") -538 NUMBER_OF_INSTRUCTIONS, -539 :(before "End Primitive Recipe Numbers") -540 put(Recipe_ordinal, "number-of-instructions", NUMBER_OF_INSTRUCTIONS); -541 :(before "End Primitive Recipe Checks") -542 case NUMBER_OF_INSTRUCTIONS: { -543 if (SIZE(inst.ingredients) != 1) { -544 raise << maybe(get(Recipe, r).name) << "'number-of-instructions' requires exactly one ingredient, but got '" << to_original_string(inst) << "'\n" << end(); -545 break; -546 } -547 if (!is_mu_number(inst.ingredients.at(0))) { -548 raise << maybe(get(Recipe, r).name) << "first ingredient of 'number-of-instructions' should be a routine id generated by 'start-running', but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); -549 break; -550 } -551 break; -552 } -553 :(before "End Primitive Recipe Implementations") -554 case NUMBER_OF_INSTRUCTIONS: { -555 int id = ingredients.at(0).at(0); -556 int result = -1; -557 for (int i = 0; i < SIZE(Routines); ++i) { -558 if (Routines.at(i)->id == id) { -559 result = Routines.at(i)->instructions_run; -560 break; -561 } -562 } -563 products.resize(1); -564 products.at(0).push_back(result); -565 break; -566 } -567 -568 :(scenario number_of_instructions) -569 def f1 [ -570 10:num/child-id <- start-running f2 -571 { -572 loop-unless 20:num -573 } -574 11:num <- number-of-instructions 10:num -575 ] -576 def f2 [ -577 # 2 instructions worth of work -578 1:num <- copy 34 -579 20:num <- copy 1 -580 ] -581 # f2 runs an extra instruction for the implicit return added by the -582 # fill_in_return_ingredients transform -583 +mem: storing 3 in location 11 -584 -585 :(scenario number_of_instructions_across_multiple_scheduling_intervals) -586 % Scheduling_interval = 1; -587 def f1 [ -588 10:num/child-id <- start-running f2 -589 { -590 loop-unless 20:num -591 } -592 11:num <- number-of-instructions 10:num -593 ] -594 def f2 [ -595 # 4 instructions worth of work -596 1:num <- copy 34 -597 2:num <- copy 1 -598 2:num <- copy 3 -599 20:num <- copy 1 -600 ] -601 # f2 runs an extra instruction for the implicit return added by the -602 # fill_in_return_ingredients transform -603 +mem: storing 5 in location 11 -604 -605 //:: make sure that each routine gets a different alloc to start -606 -607 :(scenario new_concurrent) -608 def f1 [ -609 start-running f2 -610 1:&:num/raw <- new number:type -611 # wait for f2 to complete -612 { -613 loop-unless 4:num/raw -614 } -615 ] -616 def f2 [ -617 2:&:num/raw <- new number:type -618 # hack: assumes scheduler implementation -619 3:bool/raw <- equal 1:&:num/raw, 2:&:num/raw -620 # signal f2 complete -621 4:num/raw <- copy 1 -622 ] -623 +mem: storing 0 in location 3 +181 new_routine->calls.front().ingredient_atoms.push_back(ingredients.at(i)); +182 reagent/*copy*/ ingredient = current_instruction().ingredients.at(i); +183 new_routine->calls.front().ingredients.push_back(ingredient); +184 // End Populate start-running Ingredient +185 } +186 Routines.push_back(new_routine); +187 products.resize(1); +188 products.at(0).push_back(new_routine->id); +189 break; +190 } +191 +192 :(scenario scheduler_runs_single_routine) +193 % Scheduling_interval = 1; +194 def f1 [ +195 1:num <- copy 0 +196 2:num <- copy 0 +197 ] +198 +schedule: f1 +199 +run: {1: "number"} <- copy {0: "literal"} +200 +schedule: f1 +201 +run: {2: "number"} <- copy {0: "literal"} +202 +203 :(scenario scheduler_interleaves_routines) +204 % Scheduling_interval = 1; +205 def f1 [ +206 start-running f2 +207 1:num <- copy 0 +208 2:num <- copy 0 +209 ] +210 def f2 [ +211 3:num <- copy 0 +212 4:num <- copy 0 +213 ] +214 +schedule: f1 +215 +run: start-running {f2: "recipe-literal"} +216 +schedule: f2 +217 +run: {3: "number"} <- copy {0: "literal"} +218 +schedule: f1 +219 +run: {1: "number"} <- copy {0: "literal"} +220 +schedule: f2 +221 +run: {4: "number"} <- copy {0: "literal"} +222 +schedule: f1 +223 +run: {2: "number"} <- copy {0: "literal"} +224 +225 :(scenario start_running_takes_ingredients) +226 def f1 [ +227 start-running f2, 3 +228 # wait for f2 to run +229 { +230 jump-unless 1:num, -1 +231 } +232 ] +233 def f2 [ +234 1:num <- next-ingredient +235 2:num <- add 1:num, 1 +236 ] +237 +mem: storing 4 in location 2 +238 +239 //: type-checking for 'start-running' +240 +241 :(scenario start_running_checks_types) +242 % Hide_errors = true; +243 def f1 [ +244 start-running f2, 3 +245 ] +246 def f2 n:&:num [ +247 ] +248 +error: f1: ingredient 0 has the wrong type at 'start-running f2, 3' +249 +250 // 'start-running' only uses the ingredients of the callee, not its products +251 :(before "End is_indirect_call_with_ingredients Special-cases") +252 if (r == START_RUNNING) return true; +253 +254 //: back to testing 'start-running' +255 +256 :(scenario start_running_returns_routine_id) +257 def f1 [ +258 1:num <- start-running f2 +259 ] +260 def f2 [ +261 12:num <- copy 44 +262 ] +263 +mem: storing 2 in location 1 +264 +265 //: this scenario will require some careful setup in escaped C++ +266 //: (straining our tangle capabilities to near-breaking point) +267 :(scenario scheduler_skips_completed_routines) +268 % recipe_ordinal f1 = load("recipe f1 [\n1:num <- copy 0\n]\n").front(); +269 % recipe_ordinal f2 = load("recipe f2 [\n2:num <- copy 0\n]\n").front(); +270 % Routines.push_back(new routine(f1)); // f1 meant to run +271 % Routines.push_back(new routine(f2)); +272 % Routines.back()->state = COMPLETED; // f2 not meant to run +273 # must have at least one routine without escaping +274 def f3 [ +275 3:num <- copy 0 +276 ] +277 # by interleaving '+' lines with '-' lines, we allow f1 and f3 to run in any order +278 +schedule: f1 +279 +mem: storing 0 in location 1 +280 -schedule: f2 +281 -mem: storing 0 in location 2 +282 +schedule: f3 +283 +mem: storing 0 in location 3 +284 +285 :(scenario scheduler_starts_at_middle_of_routines) +286 % Routines.push_back(new routine(COPY)); +287 % Routines.back()->state = COMPLETED; +288 def f1 [ +289 1:num <- copy 0 +290 2:num <- copy 0 +291 ] +292 +schedule: f1 +293 -run: idle +294 +295 //:: Errors in a routine cause it to terminate. +296 +297 :(scenario scheduler_terminates_routines_after_errors) +298 % Hide_errors = true; +299 % Scheduling_interval = 2; +300 def f1 [ +301 start-running f2 +302 1:num <- copy 0 +303 2:num <- copy 0 +304 ] +305 def f2 [ +306 # divide by 0 twice +307 3:num <- divide-with-remainder 4, 0 +308 4:num <- divide-with-remainder 4, 0 +309 ] +310 # f2 should stop after first divide by 0 +311 +error: f2: divide by zero in '3:num <- divide-with-remainder 4, 0' +312 -error: f2: divide by zero in '4:num <- divide-with-remainder 4, 0' +313 +314 :(after "operator<<(ostream& os, vestigial end)") +315 if (Trace_stream && Trace_stream->curr_label == "error" && Current_routine) { +316 Current_routine->state = COMPLETED; +317 } +318 +319 //:: Routines are marked completed when their parent completes. +320 +321 :(scenario scheduler_kills_orphans) +322 def main [ +323 start-running f1 +324 # f1 never actually runs because its parent completes without waiting for it +325 ] +326 def f1 [ +327 1:num <- copy 0 +328 ] +329 -schedule: f1 +330 +331 :(before "End Scheduler Cleanup") +332 for (int i = 0; i < SIZE(Routines); ++i) { +333 if (Routines.at(i)->state == COMPLETED) continue; +334 if (Routines.at(i)->parent_index < 0) continue; // root thread +335 // structured concurrency: http://250bpm.com/blog:71 +336 if (has_completed_parent(i)) { +337 Routines.at(i)->state = COMPLETED; +338 } +339 } +340 +341 :(code) +342 bool has_completed_parent(int routine_index) { +343 for (int j = routine_index; j >= 0; j = Routines.at(j)->parent_index) { +344 if (Routines.at(j)->state == COMPLETED) +345 return true; +346 } +347 return false; +348 } +349 +350 //:: 'routine-state' can tell if a given routine id is running +351 +352 :(scenario routine_state_test) +353 % Scheduling_interval = 2; +354 def f1 [ +355 1:num/child-id <- start-running f2 +356 12:num <- copy 0 # race condition since we don't care about location 12 +357 # thanks to Scheduling_interval, f2's one instruction runs in between here and completes +358 2:num/state <- routine-state 1:num/child-id +359 ] +360 def f2 [ +361 12:num <- copy 0 +362 # trying to run a second instruction marks routine as completed +363 ] +364 # recipe f2 should be in state COMPLETED +365 +mem: storing 1 in location 2 +366 +367 :(before "End Primitive Recipe Declarations") +368 ROUTINE_STATE, +369 :(before "End Primitive Recipe Numbers") +370 put(Recipe_ordinal, "routine-state", ROUTINE_STATE); +371 :(before "End Primitive Recipe Checks") +372 case ROUTINE_STATE: { +373 if (SIZE(inst.ingredients) != 1) { +374 raise << maybe(get(Recipe, r).name) << "'routine-state' requires exactly one ingredient, but got '" << to_original_string(inst) << "'\n" << end(); +375 break; +376 } +377 if (!is_mu_number(inst.ingredients.at(0))) { +378 raise << maybe(get(Recipe, r).name) << "first ingredient of 'routine-state' should be a routine id generated by 'start-running', but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); +379 break; +380 } +381 break; +382 } +383 :(before "End Primitive Recipe Implementations") +384 case ROUTINE_STATE: { +385 int id = ingredients.at(0).at(0); +386 int result = -1; +387 for (int i = 0; i < SIZE(Routines); ++i) { +388 if (Routines.at(i)->id == id) { +389 result = Routines.at(i)->state; +390 break; +391 } +392 } +393 products.resize(1); +394 products.at(0).push_back(result); +395 break; +396 } +397 +398 //:: miscellaneous helpers +399 +400 :(before "End Primitive Recipe Declarations") +401 STOP, +402 :(before "End Primitive Recipe Numbers") +403 put(Recipe_ordinal, "stop", STOP); +404 :(before "End Primitive Recipe Checks") +405 case STOP: { +406 if (SIZE(inst.ingredients) != 1) { +407 raise << maybe(get(Recipe, r).name) << "'stop' requires exactly one ingredient, but got '" << to_original_string(inst) << "'\n" << end(); +408 break; +409 } +410 if (!is_mu_number(inst.ingredients.at(0))) { +411 raise << maybe(get(Recipe, r).name) << "first ingredient of 'stop' should be a routine id generated by 'start-running', but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); +412 break; +413 } +414 break; +415 } +416 :(before "End Primitive Recipe Implementations") +417 case STOP: { +418 int id = ingredients.at(0).at(0); +419 for (int i = 0; i < SIZE(Routines); ++i) { +420 if (Routines.at(i)->id == id) { +421 Routines.at(i)->state = COMPLETED; +422 break; +423 } +424 } +425 break; +426 } +427 +428 :(before "End Primitive Recipe Declarations") +429 _DUMP_ROUTINES, +430 :(before "End Primitive Recipe Numbers") +431 put(Recipe_ordinal, "$dump-routines", _DUMP_ROUTINES); +432 :(before "End Primitive Recipe Checks") +433 case _DUMP_ROUTINES: { +434 break; +435 } +436 :(before "End Primitive Recipe Implementations") +437 case _DUMP_ROUTINES: { +438 for (int i = 0; i < SIZE(Routines); ++i) { +439 cerr << i << ": " << Routines.at(i)->id << ' ' << Routines.at(i)->state << ' ' << Routines.at(i)->parent_index << '\n'; +440 } +441 break; +442 } +443 +444 //: support for stopping routines after some number of cycles +445 +446 :(scenario routine_discontinues_past_limit) +447 % Scheduling_interval = 2; +448 def f1 [ +449 1:num/child-id <- start-running f2 +450 limit-time 1:num/child-id, 10 +451 # padding loop just to make sure f2 has time to completed +452 2:num <- copy 20 +453 2:num <- subtract 2:num, 1 +454 jump-if 2:num, -2:offset +455 ] +456 def f2 [ +457 jump -1:offset # run forever +458 $print [should never get here], 10/newline +459 ] +460 # f2 terminates +461 +schedule: discontinuing routine 2 +462 +463 :(before "End routine States") +464 DISCONTINUED, +465 :(before "End Scheduler State Transitions") +466 if (Current_routine->limit >= 0) { +467 if (Current_routine->limit <= Scheduling_interval) { +468 trace("schedule") << "discontinuing routine " << Current_routine->id << end(); +469 Current_routine->state = DISCONTINUED; +470 Current_routine->limit = 0; +471 } +472 else { +473 Current_routine->limit -= Scheduling_interval; +474 } +475 } +476 +477 :(before "End Test Teardown") +478 if (Passed && any_routines_with_error()) +479 raise << "some routines died with errors\n" << end(); +480 :(before "End Mu Test Teardown") +481 if (Passed && any_routines_with_error()) +482 raise << Current_scenario->name << ": some routines died with errors\n" << end(); +483 +484 :(code) +485 bool any_routines_with_error() { +486 for (int i = 0; i < SIZE(Routines); ++i) { +487 if (Routines.at(i)->state == DISCONTINUED) +488 return true; +489 } +490 return false; +491 } +492 +493 :(before "End routine Fields") +494 int limit; +495 :(before "End routine Constructor") +496 limit = -1; /* no limit */ +497 +498 :(before "End Primitive Recipe Declarations") +499 LIMIT_TIME, +500 :(before "End Primitive Recipe Numbers") +501 put(Recipe_ordinal, "limit-time", LIMIT_TIME); +502 :(before "End Primitive Recipe Checks") +503 case LIMIT_TIME: { +504 if (SIZE(inst.ingredients) != 2) { +505 raise << maybe(get(Recipe, r).name) << "'limit-time' requires exactly two ingredient, but got '" << to_original_string(inst) << "'\n" << end(); +506 break; +507 } +508 if (!is_mu_number(inst.ingredients.at(0))) { +509 raise << maybe(get(Recipe, r).name) << "first ingredient of 'limit-time' should be a routine id generated by 'start-running', but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); +510 break; +511 } +512 if (!is_mu_number(inst.ingredients.at(1))) { +513 raise << maybe(get(Recipe, r).name) << "second ingredient of 'limit-time' should be a number (of instructions to run for), but got '" << inst.ingredients.at(1).original_string << "'\n" << end(); +514 break; +515 } +516 break; +517 } +518 :(before "End Primitive Recipe Implementations") +519 case LIMIT_TIME: { +520 int id = ingredients.at(0).at(0); +521 for (int i = 0; i < SIZE(Routines); ++i) { +522 if (Routines.at(i)->id == id) { +523 Routines.at(i)->limit = ingredients.at(1).at(0); +524 break; +525 } +526 } +527 break; +528 } +529 +530 :(before "End routine Fields") +531 int instructions_run; +532 :(before "End routine Constructor") +533 instructions_run = 0; +534 :(before "Reset instructions_run_this_scheduling_slice") +535 Current_routine->instructions_run += Current_routine->instructions_run_this_scheduling_slice; +536 :(before "End Primitive Recipe Declarations") +537 NUMBER_OF_INSTRUCTIONS, +538 :(before "End Primitive Recipe Numbers") +539 put(Recipe_ordinal, "number-of-instructions", NUMBER_OF_INSTRUCTIONS); +540 :(before "End Primitive Recipe Checks") +541 case NUMBER_OF_INSTRUCTIONS: { +542 if (SIZE(inst.ingredients) != 1) { +543 raise << maybe(get(Recipe, r).name) << "'number-of-instructions' requires exactly one ingredient, but got '" << to_original_string(inst) << "'\n" << end(); +544 break; +545 } +546 if (!is_mu_number(inst.ingredients.at(0))) { +547 raise << maybe(get(Recipe, r).name) << "first ingredient of 'number-of-instructions' should be a routine id generated by 'start-running', but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); +548 break; +549 } +550 break; +551 } +552 :(before "End Primitive Recipe Implementations") +553 case NUMBER_OF_INSTRUCTIONS: { +554 int id = ingredients.at(0).at(0); +555 int result = -1; +556 for (int i = 0; i < SIZE(Routines); ++i) { +557 if (Routines.at(i)->id == id) { +558 result = Routines.at(i)->instructions_run; +559 break; +560 } +561 } +562 products.resize(1); +563 products.at(0).push_back(result); +564 break; +565 } +566 +567 :(scenario number_of_instructions) +568 def f1 [ +569 10:num/child-id <- start-running f2 +570 { +571 loop-unless 20:num +572 } +573 11:num <- number-of-instructions 10:num +574 ] +575 def f2 [ +576 # 2 instructions worth of work +577 1:num <- copy 34 +578 20:num <- copy 1 +579 ] +580 # f2 runs an extra instruction for the implicit return added by the +581 # fill_in_return_ingredients transform +582 +mem: storing 3 in location 11 +583 +584 :(scenario number_of_instructions_across_multiple_scheduling_intervals) +585 % Scheduling_interval = 1; +586 def f1 [ +587 10:num/child-id <- start-running f2 +588 { +589 loop-unless 20:num +590 } +591 11:num <- number-of-instructions 10:num +592 ] +593 def f2 [ +594 # 4 instructions worth of work +595 1:num <- copy 34 +596 2:num <- copy 1 +597 2:num <- copy 3 +598 20:num <- copy 1 +599 ] +600 # f2 runs an extra instruction for the implicit return added by the +601 # fill_in_return_ingredients transform +602 +mem: storing 5 in location 11 +603 +604 //:: make sure that each routine gets a different alloc to start +605 +606 :(scenario new_concurrent) +607 def f1 [ +608 start-running f2 +609 1:&:num/raw <- new number:type +610 # wait for f2 to complete +611 { +612 loop-unless 4:num/raw +613 } +614 ] +615 def f2 [ +616 2:&:num/raw <- new number:type +617 # hack: assumes scheduler implementation +618 3:bool/raw <- equal 1:&:num/raw, 2:&:num/raw +619 # signal f2 complete +620 4:num/raw <- copy 1 +621 ] +622 +mem: storing 0 in location 3 diff --git a/html/075channel.mu.html b/html/075channel.mu.html index ccbc5178..43180f17 100644 --- a/html/075channel.mu.html +++ b/html/075channel.mu.html @@ -64,521 +64,513 @@ if ('onhashchange' in window) { 1 # Mu synchronizes between routines using channels rather than locks, like 2 # Erlang and Go. 3 # - 4 # Key properties of channels: - 5 # - 6 # a) Writing to a full channel or reading from an empty one will put the - 7 # current routine in 'waiting' state until the operation can be completed. - 8 # - 9 # b) Writing to a channel implicitly performs a deep copy. This prevents - 10 # addresses from being shared between routines, and therefore eliminates all - 11 # possibility of race conditions. - 12 # - 13 # There's still a narrow window for race conditions: the inputs passed in - 14 # to 'start-running'. Pass only channels into routines and you should be fine. - 15 # Any other mutable inputs will require locks. - 16 - 17 scenario channel [ - 18 run [ - 19 local-scope - 20 source:&:source:num, sink:&:sink:num <- new-channel 3/capacity - 21 sink <- write sink, 34 - 22 10:num/raw, 11:bool/raw, source <- read source - 23 ] - 24 memory-should-contain [ - 25 10 <- 34 - 26 11 <- 0 # read was successful - 27 ] - 28 ] - 29 - 30 container channel:_elem [ - 31 lock:bool # inefficient but simple: serialize all reads as well as writes - 32 first-full:num # for write - 33 first-free:num # for read - 34 # A circular buffer contains values from index first-full up to (but not - 35 # including) index first-free. The reader always modifies it at first-full, - 36 # while the writer always modifies it at first-free. - 37 data:&:@:_elem + 4 # The key property of channels: Writing to a full channel or reading from an + 5 # empty one will put the current routine in 'waiting' state until the + 6 # operation can be completed. + 7 # + 8 # Beware of addresses passed into channels. They can cause race conditions. + 9 + 10 scenario channel [ + 11 run [ + 12 local-scope + 13 source:&:source:num, sink:&:sink:num <- new-channel 3/capacity + 14 sink <- write sink, 34 + 15 10:num/raw, 11:bool/raw, source <- read source + 16 ] + 17 memory-should-contain [ + 18 10 <- 34 + 19 11 <- 0 # read was successful + 20 ] + 21 ] + 22 + 23 container channel:_elem [ + 24 lock:bool # inefficient but simple: serialize all reads as well as writes + 25 first-full:num # for write + 26 first-free:num # for read + 27 # A circular buffer contains values from index first-full up to (but not + 28 # including) index first-free. The reader always modifies it at first-full, + 29 # while the writer always modifies it at first-free. + 30 data:&:@:_elem + 31 ] + 32 + 33 # Since channels have two ends, and since it's an error to use either end from + 34 # multiple routines, let's distinguish the ends. + 35 + 36 container source:_elem [ + 37 chan:&:channel:_elem 38 ] 39 - 40 # Since channels have two ends, and since it's an error to use either end from - 41 # multiple routines, let's distinguish the ends. - 42 - 43 container source:_elem [ - 44 chan:&:channel:_elem - 45 ] - 46 - 47 container sink:_elem [ - 48 chan:&:channel:_elem - 49 ] - 50 - 51 def new-channel capacity:num -> in:&:source:_elem, out:&:sink:_elem [ - 52 local-scope - 53 load-inputs - 54 result:&:channel:_elem <- new {(channel _elem): type} - 55 *result <- put *result, first-full:offset, 0 - 56 *result <- put *result, first-free:offset, 0 - 57 capacity <- add capacity, 1 # unused slot for 'full?' below - 58 data:&:@:_elem <- new _elem:type, capacity - 59 *result <- put *result, data:offset, data - 60 in <- new {(source _elem): type} - 61 *in <- put *in, chan:offset, result - 62 out <- new {(sink _elem): type} - 63 *out <- put *out, chan:offset, result - 64 ] - 65 - 66 # write a value to a channel - 67 def write out:&:sink:_elem, val:_elem -> out:&:sink:_elem [ - 68 local-scope - 69 load-inputs - 70 assert out, [write to null channel] - 71 chan:&:channel:_elem <- get *out, chan:offset - 72 <channel-write-initial> - 73 # block until lock is acquired AND queue has room - 74 lock:location <- get-location *chan, lock:offset - 75 #? $print [write], 10/newline - 76 { - 77 #? $print [trying to acquire lock for writing], 10/newline - 78 wait-for-reset-then-set lock - 79 #? $print [lock acquired for writing], 10/newline - 80 full?:bool <- channel-full? chan - 81 break-unless full? - 82 #? $print [but channel is full; relinquishing lock], 10/newline - 83 # channel is full; relinquish lock and give a reader the opportunity to - 84 # create room on it - 85 reset lock - 86 current-routine-is-blocked - 87 switch # avoid spinlocking - 88 loop - 89 } - 90 current-routine-is-unblocked - 91 #? $print [performing write], 10/newline - 92 # store a deep copy of val - 93 circular-buffer:&:@:_elem <- get *chan, data:offset - 94 free:num <- get *chan, first-free:offset - 95 val-copy:_elem <- deep-copy val # on this instruction rests all Mu's concurrency-safety - 96 *circular-buffer <- put-index *circular-buffer, free, val-copy - 97 # mark its slot as filled - 98 free <- add free, 1 - 99 { -100 # wrap free around to 0 if necessary -101 len:num <- length *circular-buffer -102 at-end?:bool <- greater-or-equal free, len -103 break-unless at-end? -104 free <- copy 0 -105 } -106 # write back -107 *chan <- put *chan, first-free:offset, free -108 #? $print [relinquishing lock after writing], 10/newline -109 reset lock -110 ] -111 -112 # read a value from a channel -113 def read in:&:source:_elem -> result:_elem, eof?:bool, in:&:source:_elem [ -114 local-scope -115 load-inputs -116 assert in, [read on null channel] -117 eof? <- copy 0/false # default result -118 chan:&:channel:_elem <- get *in, chan:offset -119 # block until lock is acquired AND queue has data -120 lock:location <- get-location *chan, lock:offset -121 #? $print [read], 10/newline -122 { -123 #? $print [trying to acquire lock for reading], 10/newline -124 wait-for-reset-then-set lock -125 #? $print [lock acquired for reading], 10/newline -126 empty?:bool <- channel-empty? chan -127 break-unless empty? -128 #? $print [but channel is empty; relinquishing lock], 10/newline -129 # channel is empty; relinquish lock and give a writer the opportunity to -130 # add to it -131 reset lock -132 current-routine-is-blocked -133 <channel-read-empty> -134 switch # avoid spinlocking -135 loop -136 } -137 current-routine-is-unblocked -138 # pull result off -139 full:num <- get *chan, first-full:offset -140 circular-buffer:&:@:_elem <- get *chan, data:offset -141 result <- index *circular-buffer, full -142 # clear the slot -143 empty:&:_elem <- new _elem:type -144 *circular-buffer <- put-index *circular-buffer, full, *empty -145 # mark its slot as empty -146 full <- add full, 1 -147 { -148 # wrap full around to 0 if necessary -149 len:num <- length *circular-buffer -150 at-end?:bool <- greater-or-equal full, len -151 break-unless at-end? -152 full <- copy 0 -153 } -154 # write back -155 *chan <- put *chan, first-full:offset, full -156 #? $print [relinquishing lock after reading], 10/newline -157 reset lock -158 ] -159 -160 # todo: create a notion of iterator and iterable so we can read/write whole -161 # aggregates (arrays, lists, ..) of _elems at once. -162 -163 scenario channel-initialization [ -164 run [ -165 local-scope -166 source:&:source:num <- new-channel 3/capacity -167 chan:&:channel:num <- get *source, chan:offset -168 10:num/raw <- get *chan, first-full:offset -169 11:num/raw <- get *chan, first-free:offset -170 ] -171 memory-should-contain [ -172 10 <- 0 # first-full -173 11 <- 0 # first-free -174 ] -175 ] -176 -177 scenario channel-write-increments-free [ -178 local-scope -179 _, sink:&:sink:num <- new-channel 3/capacity -180 run [ -181 sink <- write sink, 34 -182 chan:&:channel:num <- get *sink, chan:offset -183 10:num/raw <- get *chan, first-full:offset -184 11:num/raw <- get *chan, first-free:offset -185 ] -186 memory-should-contain [ -187 10 <- 0 # first-full -188 11 <- 1 # first-free -189 ] -190 ] -191 -192 scenario channel-read-increments-full [ -193 local-scope -194 source:&:source:num, sink:&:sink:num <- new-channel 3/capacity -195 sink <- write sink, 34 -196 run [ -197 _, _, source <- read source -198 chan:&:channel:num <- get *source, chan:offset -199 10:num/raw <- get *chan, first-full:offset -200 11:num/raw <- get *chan, first-free:offset -201 ] -202 memory-should-contain [ -203 10 <- 1 # first-full -204 11 <- 1 # first-free -205 ] -206 ] -207 -208 scenario channel-wrap [ -209 local-scope -210 # channel with just 1 slot -211 source:&:source:num, sink:&:sink:num <- new-channel 1/capacity -212 chan:&:channel:num <- get *source, chan:offset -213 # write and read a value -214 sink <- write sink, 34 -215 _, _, source <- read source -216 run [ -217 # first-free will now be 1 -218 10:num/raw <- get *chan, first-free:offset -219 11:num/raw <- get *chan, first-free:offset -220 # write second value, verify that first-free wraps -221 sink <- write sink, 34 -222 20:num/raw <- get *chan, first-free:offset -223 # read second value, verify that first-full wraps -224 _, _, source <- read source -225 30:num/raw <- get *chan, first-full:offset -226 ] -227 memory-should-contain [ -228 10 <- 1 # first-free after first write -229 11 <- 1 # first-full after first read -230 20 <- 0 # first-free after second write, wrapped -231 30 <- 0 # first-full after second read, wrapped -232 ] -233 ] -234 -235 scenario channel-new-empty-not-full [ -236 run [ -237 local-scope -238 source:&:source:num <- new-channel 3/capacity -239 chan:&:channel:num <- get *source, chan:offset -240 10:bool/raw <- channel-empty? chan -241 11:bool/raw <- channel-full? chan -242 ] -243 memory-should-contain [ -244 10 <- 1 # empty? -245 11 <- 0 # full? -246 ] -247 ] -248 -249 scenario channel-write-not-empty [ -250 local-scope -251 source:&:source:num, sink:&:sink:num <- new-channel 3/capacity -252 chan:&:channel:num <- get *source, chan:offset -253 run [ -254 sink <- write sink, 34 -255 10:bool/raw <- channel-empty? chan -256 11:bool/raw <- channel-full? chan -257 ] -258 memory-should-contain [ -259 10 <- 0 # empty? -260 11 <- 0 # full? -261 ] -262 ] -263 -264 scenario channel-write-full [ -265 local-scope -266 source:&:source:num, sink:&:sink:num <- new-channel 1/capacity -267 chan:&:channel:num <- get *source, chan:offset -268 run [ -269 sink <- write sink, 34 -270 10:bool/raw <- channel-empty? chan -271 11:bool/raw <- channel-full? chan -272 ] -273 memory-should-contain [ -274 10 <- 0 # empty? -275 11 <- 1 # full? -276 ] -277 ] -278 -279 scenario channel-read-not-full [ -280 local-scope -281 source:&:source:num, sink:&:sink:num <- new-channel 1/capacity -282 chan:&:channel:num <- get *source, chan:offset -283 sink <- write sink, 34 -284 run [ -285 _, _, source <- read source -286 10:bool/raw <- channel-empty? chan -287 11:bool/raw <- channel-full? chan -288 ] -289 memory-should-contain [ -290 10 <- 1 # empty? -291 11 <- 0 # full? -292 ] -293 ] -294 -295 scenario channel-clear [ -296 local-scope -297 # create a channel with a few items -298 source:&:source:num, sink:&:sink:num <- new-channel 3/capacity -299 chan:&:channel:num <- get *sink, chan:offset -300 write sink, 30 -301 write sink, 31 -302 write sink, 32 -303 run [ -304 clear source -305 10:bool/raw <- channel-empty? chan -306 ] -307 memory-should-contain [ -308 10 <- 1 # after the call to 'clear', the channel should be empty -309 ] -310 ] -311 -312 def clear in:&:source:_elem -> in:&:source:_elem [ -313 local-scope -314 load-inputs -315 chan:&:channel:_elem <- get *in, chan:offset -316 { -317 empty?:bool <- channel-empty? chan -318 break-if empty? -319 _, _, in <- read in -320 loop -321 } + 40 container sink:_elem [ + 41 chan:&:channel:_elem + 42 ] + 43 + 44 def new-channel capacity:num -> in:&:source:_elem, out:&:sink:_elem [ + 45 local-scope + 46 load-inputs + 47 result:&:channel:_elem <- new {(channel _elem): type} + 48 *result <- put *result, first-full:offset, 0 + 49 *result <- put *result, first-free:offset, 0 + 50 capacity <- add capacity, 1 # unused slot for 'full?' below + 51 data:&:@:_elem <- new _elem:type, capacity + 52 *result <- put *result, data:offset, data + 53 in <- new {(source _elem): type} + 54 *in <- put *in, chan:offset, result + 55 out <- new {(sink _elem): type} + 56 *out <- put *out, chan:offset, result + 57 ] + 58 + 59 # write a value to a channel + 60 def write out:&:sink:_elem, val:_elem -> out:&:sink:_elem [ + 61 local-scope + 62 load-inputs + 63 assert out, [write to null channel] + 64 chan:&:channel:_elem <- get *out, chan:offset + 65 <channel-write-initial> + 66 # block until lock is acquired AND queue has room + 67 lock:location <- get-location *chan, lock:offset + 68 #? $print [write], 10/newline + 69 { + 70 #? $print [trying to acquire lock for writing], 10/newline + 71 wait-for-reset-then-set lock + 72 #? $print [lock acquired for writing], 10/newline + 73 full?:bool <- channel-full? chan + 74 break-unless full? + 75 #? $print [but channel is full; relinquishing lock], 10/newline + 76 # channel is full; relinquish lock and give a reader the opportunity to + 77 # create room on it + 78 reset lock + 79 current-routine-is-blocked + 80 switch # avoid spinlocking + 81 loop + 82 } + 83 current-routine-is-unblocked + 84 #? $print [performing write], 10/newline + 85 # store a deep copy of val + 86 circular-buffer:&:@:_elem <- get *chan, data:offset + 87 free:num <- get *chan, first-free:offset + 88 *circular-buffer <- put-index *circular-buffer, free, val + 89 # mark its slot as filled + 90 free <- add free, 1 + 91 { + 92 # wrap free around to 0 if necessary + 93 len:num <- length *circular-buffer + 94 at-end?:bool <- greater-or-equal free, len + 95 break-unless at-end? + 96 free <- copy 0 + 97 } + 98 # write back + 99 *chan <- put *chan, first-free:offset, free +100 #? $print [relinquishing lock after writing], 10/newline +101 reset lock +102 ] +103 +104 # read a value from a channel +105 def read in:&:source:_elem -> result:_elem, eof?:bool, in:&:source:_elem [ +106 local-scope +107 load-inputs +108 assert in, [read on null channel] +109 eof? <- copy 0/false # default result +110 chan:&:channel:_elem <- get *in, chan:offset +111 # block until lock is acquired AND queue has data +112 lock:location <- get-location *chan, lock:offset +113 #? $print [read], 10/newline +114 { +115 #? $print [trying to acquire lock for reading], 10/newline +116 wait-for-reset-then-set lock +117 #? $print [lock acquired for reading], 10/newline +118 empty?:bool <- channel-empty? chan +119 break-unless empty? +120 #? $print [but channel is empty; relinquishing lock], 10/newline +121 # channel is empty; relinquish lock and give a writer the opportunity to +122 # add to it +123 reset lock +124 current-routine-is-blocked +125 <channel-read-empty> +126 switch # avoid spinlocking +127 loop +128 } +129 current-routine-is-unblocked +130 # pull result off +131 full:num <- get *chan, first-full:offset +132 circular-buffer:&:@:_elem <- get *chan, data:offset +133 result <- index *circular-buffer, full +134 # clear the slot +135 empty:&:_elem <- new _elem:type +136 *circular-buffer <- put-index *circular-buffer, full, *empty +137 # mark its slot as empty +138 full <- add full, 1 +139 { +140 # wrap full around to 0 if necessary +141 len:num <- length *circular-buffer +142 at-end?:bool <- greater-or-equal full, len +143 break-unless at-end? +144 full <- copy 0 +145 } +146 # write back +147 *chan <- put *chan, first-full:offset, full +148 #? $print [relinquishing lock after reading], 10/newline +149 reset lock +150 ] +151 +152 # todo: create a notion of iterator and iterable so we can read/write whole +153 # aggregates (arrays, lists, ..) of _elems at once. +154 +155 scenario channel-initialization [ +156 run [ +157 local-scope +158 source:&:source:num <- new-channel 3/capacity +159 chan:&:channel:num <- get *source, chan:offset +160 10:num/raw <- get *chan, first-full:offset +161 11:num/raw <- get *chan, first-free:offset +162 ] +163 memory-should-contain [ +164 10 <- 0 # first-full +165 11 <- 0 # first-free +166 ] +167 ] +168 +169 scenario channel-write-increments-free [ +170 local-scope +171 _, sink:&:sink:num <- new-channel 3/capacity +172 run [ +173 sink <- write sink, 34 +174 chan:&:channel:num <- get *sink, chan:offset +175 10:num/raw <- get *chan, first-full:offset +176 11:num/raw <- get *chan, first-free:offset +177 ] +178 memory-should-contain [ +179 10 <- 0 # first-full +180 11 <- 1 # first-free +181 ] +182 ] +183 +184 scenario channel-read-increments-full [ +185 local-scope +186 source:&:source:num, sink:&:sink:num <- new-channel 3/capacity +187 sink <- write sink, 34 +188 run [ +189 _, _, source <- read source +190 chan:&:channel:num <- get *source, chan:offset +191 10:num/raw <- get *chan, first-full:offset +192 11:num/raw <- get *chan, first-free:offset +193 ] +194 memory-should-contain [ +195 10 <- 1 # first-full +196 11 <- 1 # first-free +197 ] +198 ] +199 +200 scenario channel-wrap [ +201 local-scope +202 # channel with just 1 slot +203 source:&:source:num, sink:&:sink:num <- new-channel 1/capacity +204 chan:&:channel:num <- get *source, chan:offset +205 # write and read a value +206 sink <- write sink, 34 +207 _, _, source <- read source +208 run [ +209 # first-free will now be 1 +210 10:num/raw <- get *chan, first-free:offset +211 11:num/raw <- get *chan, first-free:offset +212 # write second value, verify that first-free wraps +213 sink <- write sink, 34 +214 20:num/raw <- get *chan, first-free:offset +215 # read second value, verify that first-full wraps +216 _, _, source <- read source +217 30:num/raw <- get *chan, first-full:offset +218 ] +219 memory-should-contain [ +220 10 <- 1 # first-free after first write +221 11 <- 1 # first-full after first read +222 20 <- 0 # first-free after second write, wrapped +223 30 <- 0 # first-full after second read, wrapped +224 ] +225 ] +226 +227 scenario channel-new-empty-not-full [ +228 run [ +229 local-scope +230 source:&:source:num <- new-channel 3/capacity +231 chan:&:channel:num <- get *source, chan:offset +232 10:bool/raw <- channel-empty? chan +233 11:bool/raw <- channel-full? chan +234 ] +235 memory-should-contain [ +236 10 <- 1 # empty? +237 11 <- 0 # full? +238 ] +239 ] +240 +241 scenario channel-write-not-empty [ +242 local-scope +243 source:&:source:num, sink:&:sink:num <- new-channel 3/capacity +244 chan:&:channel:num <- get *source, chan:offset +245 run [ +246 sink <- write sink, 34 +247 10:bool/raw <- channel-empty? chan +248 11:bool/raw <- channel-full? chan +249 ] +250 memory-should-contain [ +251 10 <- 0 # empty? +252 11 <- 0 # full? +253 ] +254 ] +255 +256 scenario channel-write-full [ +257 local-scope +258 source:&:source:num, sink:&:sink:num <- new-channel 1/capacity +259 chan:&:channel:num <- get *source, chan:offset +260 run [ +261 sink <- write sink, 34 +262 10:bool/raw <- channel-empty? chan +263 11:bool/raw <- channel-full? chan +264 ] +265 memory-should-contain [ +266 10 <- 0 # empty? +267 11 <- 1 # full? +268 ] +269 ] +270 +271 scenario channel-read-not-full [ +272 local-scope +273 source:&:source:num, sink:&:sink:num <- new-channel 1/capacity +274 chan:&:channel:num <- get *source, chan:offset +275 sink <- write sink, 34 +276 run [ +277 _, _, source <- read source +278 10:bool/raw <- channel-empty? chan +279 11:bool/raw <- channel-full? chan +280 ] +281 memory-should-contain [ +282 10 <- 1 # empty? +283 11 <- 0 # full? +284 ] +285 ] +286 +287 scenario channel-clear [ +288 local-scope +289 # create a channel with a few items +290 source:&:source:num, sink:&:sink:num <- new-channel 3/capacity +291 chan:&:channel:num <- get *sink, chan:offset +292 write sink, 30 +293 write sink, 31 +294 write sink, 32 +295 run [ +296 clear source +297 10:bool/raw <- channel-empty? chan +298 ] +299 memory-should-contain [ +300 10 <- 1 # after the call to 'clear', the channel should be empty +301 ] +302 ] +303 +304 def clear in:&:source:_elem -> in:&:source:_elem [ +305 local-scope +306 load-inputs +307 chan:&:channel:_elem <- get *in, chan:offset +308 { +309 empty?:bool <- channel-empty? chan +310 break-if empty? +311 _, _, in <- read in +312 loop +313 } +314 ] +315 +316 ## cancelling channels +317 +318 # every channel comes with a boolean signifying if it's been closed +319 # initially this boolean is false +320 container channel:_elem [ +321 closed?:bool 322 ] 323 -324 ## cancelling channels -325 -326 # every channel comes with a boolean signifying if it's been closed -327 # initially this boolean is false -328 container channel:_elem [ -329 closed?:bool -330 ] -331 -332 # a channel can be closed from either the source or the sink -333 # both routines can modify the 'closed?' bit, but they can only ever set it, so this is a benign race -334 def close x:&:source:_elem -> x:&:source:_elem [ -335 local-scope -336 load-inputs -337 chan:&:channel:_elem <- get *x, chan:offset -338 *chan <- put *chan, closed?:offset, 1/true -339 ] -340 def close x:&:sink:_elem -> x:&:sink:_elem [ -341 local-scope -342 load-inputs -343 chan:&:channel:_elem <- get *x, chan:offset -344 *chan <- put *chan, closed?:offset, 1/true -345 ] -346 -347 # once a channel is closed from one side, no further operations are expected from that side -348 # if a channel is closed for reading, -349 # no further writes will be let through -350 # if a channel is closed for writing, -351 # future reads continue until the channel empties, -352 # then the channel is also closed for reading -353 after <channel-write-initial> [ -354 closed?:bool <- get *chan, closed?:offset -355 return-if closed? -356 ] -357 after <channel-read-empty> [ -358 closed?:bool <- get *chan, closed?:offset -359 { -360 break-unless closed? -361 empty-result:&:_elem <- new _elem:type -362 current-routine-is-unblocked -363 return *empty-result, 1/true -364 } -365 ] -366 -367 ## helpers -368 -369 # An empty channel has first-free and first-full both at the same value. -370 def channel-empty? chan:&:channel:_elem -> result:bool [ -371 local-scope -372 load-inputs -373 # return chan.first-full == chan.first-free -374 full:num <- get *chan, first-full:offset -375 free:num <- get *chan, first-free:offset -376 result <- equal full, free -377 ] -378 -379 # A full channel has first-free just before first-full, wasting one slot. -380 # (Other alternatives: https://www.snellman.net/blog/archive/2016-12-13-ring-buffers) -381 def channel-full? chan:&:channel:_elem -> result:bool [ -382 local-scope -383 load-inputs -384 # tmp = chan.first-free + 1 -385 tmp:num <- get *chan, first-free:offset -386 tmp <- add tmp, 1 -387 { -388 # if tmp == chan.capacity, tmp = 0 -389 len:num <- capacity chan -390 at-end?:bool <- greater-or-equal tmp, len -391 break-unless at-end? -392 tmp <- copy 0 -393 } -394 # return chan.first-full == tmp -395 full:num <- get *chan, first-full:offset -396 result <- equal full, tmp -397 ] -398 -399 def capacity chan:&:channel:_elem -> result:num [ -400 local-scope -401 load-inputs -402 q:&:@:_elem <- get *chan, data:offset -403 result <- length *q -404 ] -405 -406 ## helpers for channels of characters in particular -407 -408 def buffer-lines in:&:source:char, buffered-out:&:sink:char -> buffered-out:&:sink:char, in:&:source:char [ -409 local-scope -410 load-inputs -411 # repeat forever -412 eof?:bool <- copy 0/false -413 { -414 line:&:buffer:char <- new-buffer 30 -415 # read characters from 'in' until newline, copy into line -416 { -417 +next-character -418 c:char, eof?:bool, in <- read in -419 break-if eof? -420 # drop a character on backspace -421 { -422 # special-case: if it's a backspace -423 backspace?:bool <- equal c, 8 -424 break-unless backspace? -425 # drop previous character -426 { -427 buffer-length:num <- get *line, length:offset -428 buffer-empty?:bool <- equal buffer-length, 0 -429 break-if buffer-empty? -430 buffer-length <- subtract buffer-length, 1 -431 *line <- put *line, length:offset, buffer-length -432 } -433 # and don't append this one -434 loop +next-character -435 } -436 # append anything else -437 line <- append line, c -438 line-done?:bool <- equal c, 10/newline -439 break-if line-done? -440 loop -441 } -442 # copy line into 'buffered-out' -443 i:num <- copy 0 -444 line-contents:text <- get *line, data:offset -445 max:num <- get *line, length:offset +324 # a channel can be closed from either the source or the sink +325 # both routines can modify the 'closed?' bit, but they can only ever set it, so this is a benign race +326 def close x:&:source:_elem -> x:&:source:_elem [ +327 local-scope +328 load-inputs +329 chan:&:channel:_elem <- get *x, chan:offset +330 *chan <- put *chan, closed?:offset, 1/true +331 ] +332 def close x:&:sink:_elem -> x:&:sink:_elem [ +333 local-scope +334 load-inputs +335 chan:&:channel:_elem <- get *x, chan:offset +336 *chan <- put *chan, closed?:offset, 1/true +337 ] +338 +339 # once a channel is closed from one side, no further operations are expected from that side +340 # if a channel is closed for reading, +341 # no further writes will be let through +342 # if a channel is closed for writing, +343 # future reads continue until the channel empties, +344 # then the channel is also closed for reading +345 after <channel-write-initial> [ +346 closed?:bool <- get *chan, closed?:offset +347 return-if closed? +348 ] +349 after <channel-read-empty> [ +350 closed?:bool <- get *chan, closed?:offset +351 { +352 break-unless closed? +353 empty-result:&:_elem <- new _elem:type +354 current-routine-is-unblocked +355 return *empty-result, 1/true +356 } +357 ] +358 +359 ## helpers +360 +361 # An empty channel has first-free and first-full both at the same value. +362 def channel-empty? chan:&:channel:_elem -> result:bool [ +363 local-scope +364 load-inputs +365 # return chan.first-full == chan.first-free +366 full:num <- get *chan, first-full:offset +367 free:num <- get *chan, first-free:offset +368 result <- equal full, free +369 ] +370 +371 # A full channel has first-free just before first-full, wasting one slot. +372 # (Other alternatives: https://www.snellman.net/blog/archive/2016-12-13-ring-buffers) +373 def channel-full? chan:&:channel:_elem -> result:bool [ +374 local-scope +375 load-inputs +376 # tmp = chan.first-free + 1 +377 tmp:num <- get *chan, first-free:offset +378 tmp <- add tmp, 1 +379 { +380 # if tmp == chan.capacity, tmp = 0 +381 len:num <- capacity chan +382 at-end?:bool <- greater-or-equal tmp, len +383 break-unless at-end? +384 tmp <- copy 0 +385 } +386 # return chan.first-full == tmp +387 full:num <- get *chan, first-full:offset +388 result <- equal full, tmp +389 ] +390 +391 def capacity chan:&:channel:_elem -> result:num [ +392 local-scope +393 load-inputs +394 q:&:@:_elem <- get *chan, data:offset +395 result <- length *q +396 ] +397 +398 ## helpers for channels of characters in particular +399 +400 def buffer-lines in:&:source:char, buffered-out:&:sink:char -> buffered-out:&:sink:char, in:&:source:char [ +401 local-scope +402 load-inputs +403 # repeat forever +404 eof?:bool <- copy 0/false +405 { +406 line:&:buffer:char <- new-buffer 30 +407 # read characters from 'in' until newline, copy into line +408 { +409 +next-character +410 c:char, eof?:bool, in <- read in +411 break-if eof? +412 # drop a character on backspace +413 { +414 # special-case: if it's a backspace +415 backspace?:bool <- equal c, 8 +416 break-unless backspace? +417 # drop previous character +418 { +419 buffer-length:num <- get *line, length:offset +420 buffer-empty?:bool <- equal buffer-length, 0 +421 break-if buffer-empty? +422 buffer-length <- subtract buffer-length, 1 +423 *line <- put *line, length:offset, buffer-length +424 } +425 # and don't append this one +426 loop +next-character +427 } +428 # append anything else +429 line <- append line, c +430 line-done?:bool <- equal c, 10/newline +431 break-if line-done? +432 loop +433 } +434 # copy line into 'buffered-out' +435 i:num <- copy 0 +436 line-contents:text <- get *line, data:offset +437 max:num <- get *line, length:offset +438 { +439 done?:bool <- greater-or-equal i, max +440 break-if done? +441 c:char <- index *line-contents, i +442 buffered-out <- write buffered-out, c +443 i <- add i, 1 +444 loop +445 } 446 { -447 done?:bool <- greater-or-equal i, max -448 break-if done? -449 c:char <- index *line-contents, i -450 buffered-out <- write buffered-out, c -451 i <- add i, 1 -452 loop -453 } -454 { -455 break-unless eof? -456 buffered-out <- close buffered-out -457 return -458 } -459 loop -460 } -461 ] -462 -463 scenario buffer-lines-blocks-until-newline [ -464 run [ -465 local-scope -466 source:&:source:char, sink:&:sink:char <- new-channel 10/capacity -467 _, buffered-stdin:&:sink:char/buffered-stdin <- new-channel 10/capacity -468 buffered-chan:&:channel:char <- get *buffered-stdin, chan:offset -469 empty?:bool <- channel-empty? buffered-chan -470 assert empty?, [ -471 F buffer-lines-blocks-until-newline: channel should be empty after init] -472 # buffer stdin into buffered-stdin, try to read from buffered-stdin -473 buffer-routine:num <- start-running buffer-lines, source, buffered-stdin -474 wait-for-routine-to-block buffer-routine -475 empty? <- channel-empty? buffered-chan -476 assert empty?:bool, [ -477 F buffer-lines-blocks-until-newline: channel should be empty after buffer-lines bring-up] -478 # write 'a' -479 sink <- write sink, 97/a -480 restart buffer-routine -481 wait-for-routine-to-block buffer-routine -482 empty? <- channel-empty? buffered-chan -483 assert empty?:bool, [ -484 F buffer-lines-blocks-until-newline: channel should be empty after writing 'a'] -485 # write 'b' -486 sink <- write sink, 98/b -487 restart buffer-routine -488 wait-for-routine-to-block buffer-routine -489 empty? <- channel-empty? buffered-chan -490 assert empty?:bool, [ -491 F buffer-lines-blocks-until-newline: channel should be empty after writing 'b'] -492 # write newline -493 sink <- write sink, 10/newline -494 restart buffer-routine -495 wait-for-routine-to-block buffer-routine -496 empty? <- channel-empty? buffered-chan -497 data-emitted?:bool <- not empty? -498 assert data-emitted?, [ -499 F buffer-lines-blocks-until-newline: channel should contain data after writing newline] -500 trace 1, [test], [reached end] -501 ] -502 trace-should-contain [ -503 test: reached end -504 ] -505 ] -506 -507 def drain source:&:source:char -> result:text, source:&:source:char [ -508 local-scope -509 load-inputs -510 buf:&:buffer:char <- new-buffer 30 -511 { -512 c:char, done?:bool <- read source -513 break-if done? -514 buf <- append buf, c -515 loop -516 } -517 result <- buffer-to-array buf -518 ] +447 break-unless eof? +448 buffered-out <- close buffered-out +449 return +450 } +451 loop +452 } +453 ] +454 +455 scenario buffer-lines-blocks-until-newline [ +456 run [ +457 local-scope +458 source:&:source:char, sink:&:sink:char <- new-channel 10/capacity +459 _, buffered-stdin:&:sink:char/buffered-stdin <- new-channel 10/capacity +460 buffered-chan:&:channel:char <- get *buffered-stdin, chan:offset +461 empty?:bool <- channel-empty? buffered-chan +462 assert empty?, [ +463 F buffer-lines-blocks-until-newline: channel should be empty after init] +464 # buffer stdin into buffered-stdin, try to read from buffered-stdin +465 buffer-routine:num <- start-running buffer-lines, source, buffered-stdin +466 wait-for-routine-to-block buffer-routine +467 empty? <- channel-empty? buffered-chan +468 assert empty?:bool, [ +469 F buffer-lines-blocks-until-newline: channel should be empty after buffer-lines bring-up] +470 # write 'a' +471 sink <- write sink, 97/a +472 restart buffer-routine +473 wait-for-routine-to-block buffer-routine +474 empty? <- channel-empty? buffered-chan +475 assert empty?:bool, [ +476 F buffer-lines-blocks-until-newline: channel should be empty after writing 'a'] +477 # write 'b' +478 sink <- write sink, 98/b +479 restart buffer-routine +480 wait-for-routine-to-block buffer-routine +481 empty? <- channel-empty? buffered-chan +482 assert empty?:bool, [ +483 F buffer-lines-blocks-until-newline: channel should be empty after writing 'b'] +484 # write newline +485 sink <- write sink, 10/newline +486 restart buffer-routine +487 wait-for-routine-to-block buffer-routine +488 empty? <- channel-empty? buffered-chan +489 data-emitted?:bool <- not empty? +490 assert data-emitted?, [ +491 F buffer-lines-blocks-until-newline: channel should contain data after writing newline] +492 trace 1, [test], [reached end] +493 ] +494 trace-should-contain [ +495 test: reached end +496 ] +497 ] +498 +499 def drain source:&:source:char -> result:text, source:&:source:char [ +500 local-scope +501 load-inputs +502 buf:&:buffer:char <- new-buffer 30 +503 { +504 c:char, done?:bool <- read source +505 break-if done? +506 buf <- append buf, c +507 loop +508 } +509 result <- buffer-to-array buf +510 ] diff --git a/html/081print.mu.html b/html/081print.mu.html index e33a35a2..889eab31 100644 --- a/html/081print.mu.html +++ b/html/081print.mu.html @@ -181,7 +181,7 @@ if ('onhashchange' in window) { 119 # (handle special cases exactly like in the real screen) 120 width:num <- get *screen, num-columns:offset 121 height:num <- get *screen, num-rows:offset -122 capacity:num <- multiply width, height +122 capacity:num <- multiply width, height 123 row:num <- get *screen, cursor-row:offset 124 column:num <- get *screen, cursor-column:offset 125 buf:&:@:screen-cell <- get *screen, data:offset @@ -305,8 +305,8 @@ if ('onhashchange' in window) { 243 loop 244 } 245 # top-idx now same as next-top-idx; wrap around if necessary -246 capacity:num <- multiply width, height -247 _, top-idx <- divide-with-remainder, top-idx, capacity +246 capacity:num <- multiply width, height +247 _, top-idx <- divide-with-remainder, top-idx, capacity 248 *screen <- put *screen, top-idx:offset, top-idx 249 ] 250 @@ -322,8 +322,8 @@ if ('onhashchange' in window) { 260 } 261 result <- multiply width, row 262 result <- add result, column, top-idx -263 capacity:num <- multiply width, height -264 _, result <- divide-with-remainder result, capacity +263 capacity:num <- multiply width, height +264 _, result <- divide-with-remainder result, capacity 265 ] 266 267 scenario print-character-at-top-left [ diff --git a/html/084console.mu.html b/html/084console.mu.html index 2a1feec8..7928454d 100644 --- a/html/084console.mu.html +++ b/html/084console.mu.html @@ -130,7 +130,7 @@ if ('onhashchange' in window) { 70 return c, 1/found, 0/quit 71 ] 72 - 73 def send-keys-to-channel console:&:console, chan:&:sink:char, screen:&:screen -> console:&:console, chan:&:sink:char, screen:&:screen [ + 73 def send-keys-to-channel console:&:console, chan:&:sink:char, screen:&:screen -> console:&:console, chan:&:sink:char, screen:&:screen [ 74 local-scope 75 load-inputs 76 { @@ -139,7 +139,7 @@ if ('onhashchange' in window) { 79 break-if quit? 80 assert c, [invalid event, expected text] 81 screen <- print screen, c - 82 chan <- write chan, c + 82 chan <- write chan, c 83 loop 84 } 85 chan <- close chan diff --git a/html/088file.mu.html b/html/088file.mu.html index 17c78532..6f3a124d 100644 --- a/html/088file.mu.html +++ b/html/088file.mu.html @@ -78,7 +78,7 @@ if ('onhashchange' in window) { 18 contents:text 19 ] 20 - 21 def start-reading resources:&:resources, filename:text -> contents:&:source:char, error?:bool [ + 21 def start-reading resources:&:resources, filename:text -> contents:&:source:char, error?:bool [ 22 local-scope 23 load-inputs 24 error? <- copy 0/false @@ -91,18 +91,18 @@ if ('onhashchange' in window) { 31 # real file system 32 file:num <- $open-file-for-reading filename 33 return-unless file, 0/contents, 1/error? - 34 contents:&:source:char, sink:&:sink:char <- new-channel 30 - 35 start-running receive-from-file file, sink + 34 contents:&:source:char, sink:&:sink:char <- new-channel 30 + 35 start-running receive-from-file file, sink 36 ] 37 38 def slurp resources:&:resources, filename:text -> contents:text, error?:bool [ 39 local-scope 40 load-inputs - 41 source:&:source:char, error?:bool <- start-reading resources, filename + 41 source:&:source:char, error?:bool <- start-reading resources, filename 42 return-if error?, 0/contents 43 buf:&:buffer:char <- new-buffer 30/capacity 44 { - 45 c:char, done?:bool, source <- read source + 45 c:char, done?:bool, source <- read source 46 break-if done? 47 buf <- append buf, c 48 loop @@ -110,7 +110,7 @@ if ('onhashchange' in window) { 50 contents <- buffer-to-array buf 51 ] 52 - 53 def start-reading-from-fake-resource resources:&:resources, resource:text -> contents:&:source:char, error?:bool [ + 53 def start-reading-from-fake-resource resources:&:resources, resource:text -> contents:&:source:char, error?:bool [ 54 local-scope 55 load-inputs 56 error? <- copy 0/no-error @@ -125,28 +125,28 @@ if ('onhashchange' in window) { 65 curr-resource:text <- get tmp, name:offset 66 found?:bool <- equal resource, curr-resource 67 loop-unless found? - 68 contents:&:source:char, sink:&:sink:char <- new-channel 30 + 68 contents:&:source:char, sink:&:sink:char <- new-channel 30 69 curr-contents:text <- get tmp, contents:offset - 70 start-running receive-from-text curr-contents, sink + 70 start-running receive-from-text curr-contents, sink 71 return 72 } 73 return 0/not-found, 1/error 74 ] 75 - 76 def receive-from-file file:num, sink:&:sink:char -> sink:&:sink:char [ + 76 def receive-from-file file:num, sink:&:sink:char -> sink:&:sink:char [ 77 local-scope 78 load-inputs 79 { 80 c:char, eof?:bool <- $read-from-file file 81 break-if eof? - 82 sink <- write sink, c + 82 sink <- write sink, c 83 loop 84 } - 85 sink <- close sink + 85 sink <- close sink 86 file <- $close-file file 87 ] 88 - 89 def receive-from-text contents:text, sink:&:sink:char -> sink:&:sink:char [ + 89 def receive-from-text contents:text, sink:&:sink:char -> sink:&:sink:char [ 90 local-scope 91 load-inputs 92 i:num <- copy 0 @@ -155,22 +155,22 @@ if ('onhashchange' in window) { 95 done?:bool <- greater-or-equal i, len 96 break-if done? 97 c:char <- index *contents, i - 98 sink <- write sink, c + 98 sink <- write sink, c 99 i <- add i, 1 100 loop 101 } -102 sink <- close sink +102 sink <- close sink 103 ] 104 -105 def start-writing resources:&:resources, filename:text -> sink:&:sink:char, routine-id:num, error?:bool [ +105 def start-writing resources:&:resources, filename:text -> sink:&:sink:char, routine-id:num, error?:bool [ 106 local-scope 107 load-inputs 108 error? <- copy 0/false -109 source:&:source:char, sink:&:sink:char <- new-channel 30 +109 source:&:source:char, sink:&:sink:char <- new-channel 30 110 { 111 break-unless resources 112 # fake file system -113 routine-id <- start-running transmit-to-fake-resource resources, filename, source +113 routine-id <- start-running transmit-to-fake-resource resources, filename, source 114 return 115 } 116 # real file system @@ -181,7 +181,7 @@ if ('onhashchange' in window) { 121 msg:text <- append [no such file: ] filename 122 assert file, msg 123 } -124 routine-id <- start-running transmit-to-file file, source +124 routine-id <- start-running transmit-to-file file, source 125 ] 126 127 def dump resources:&:resources, filename:text, contents:text -> resources:&:resources, error?:bool [ @@ -189,7 +189,7 @@ if ('onhashchange' in window) { 129 load-inputs 130 # todo: really create an empty file 131 return-unless contents, resources, 0/no-error -132 sink-file:&:sink:char, write-routine:num, error?:bool <- start-writing resources, filename +132 sink-file:&:sink:char, write-routine:num, error?:bool <- start-writing resources, filename 133 return-if error? 134 i:num <- copy 0 135 len:num <- length *contents @@ -197,7 +197,7 @@ if ('onhashchange' in window) { 137 done?:bool <- greater-or-equal i, len 138 break-if done? 139 c:char <- index *contents, i -140 sink-file <- write sink-file, c +140 sink-file <- write sink-file, c 141 i <- add i, 1 142 loop 143 } @@ -207,11 +207,11 @@ if ('onhashchange' in window) { 147 wait-for-routine write-routine 148 ] 149 -150 def transmit-to-file file:num, source:&:source:char -> source:&:source:char [ +150 def transmit-to-file file:num, source:&:source:char -> source:&:source:char [ 151 local-scope 152 load-inputs 153 { -154 c:char, done?:bool, source <- read source +154 c:char, done?:bool, source <- read source 155 break-if done? 156 $write-to-file file, c 157 loop @@ -219,7 +219,7 @@ if ('onhashchange' in window) { 159 file <- $close-file file 160 ] 161 -162 def transmit-to-fake-resource resources:&:resources, filename:text, source:&:source:char -> resources:&:resources, source:&:source:char [ +162 def transmit-to-fake-resource resources:&:resources, filename:text, source:&:source:char -> resources:&:resources, source:&:source:char [ 163 local-scope 164 load-inputs 165 lock:location <- get-location *resources, lock:offset @@ -227,7 +227,7 @@ if ('onhashchange' in window) { 167 # compute new file contents 168 buf:&:buffer:char <- new-buffer 30 169 { -170 c:char, done?:bool, source <- read source +170 c:char, done?:bool, source <- read source 171 break-if done? 172 buf <- append buf, c 173 loop diff --git a/html/090scenario_filesystem_test.mu.html b/html/090scenario_filesystem_test.mu.html index 847489f1..917665b1 100644 --- a/html/090scenario_filesystem_test.mu.html +++ b/html/090scenario_filesystem_test.mu.html @@ -64,7 +64,7 @@ if ('onhashchange' in window) { 7 |xyz| 8 ] 9 ] -10 contents:&:source:char <- start-reading resources, [a] +10 contents:&:source:char <- start-reading resources, [a] 11 1:char/raw <- read contents 12 2:char/raw <- read contents 13 3:char/raw <- read contents @@ -83,10 +83,10 @@ if ('onhashchange' in window) { 26 local-scope 27 assume-resources [ 28 ] -29 sink:&:sink:char, writer:num/routine <- start-writing resources, [a] -30 sink <- write sink, 120/x -31 sink <- write sink, 121/y -32 close sink +29 sink:&:sink:char, writer:num/routine <- start-writing resources, [a] +30 sink <- write sink, 120/x +31 sink <- write sink, 121/y +32 close sink 33 wait-for-routine writer 34 contents-read-back:text <- slurp resources, [a] 35 10:bool/raw <- equal contents-read-back, [xy] @@ -102,10 +102,10 @@ if ('onhashchange' in window) { 45 |abc| 46 ] 47 ] -48 sink:&:sink:char, writer:num/routine <- start-writing resources, [b] -49 sink <- write sink, 120/x -50 sink <- write sink, 121/y -51 close sink +48 sink:&:sink:char, writer:num/routine <- start-writing resources, [b] +49 sink <- write sink, 120/x +50 sink <- write sink, 121/y +51 close sink 52 wait-for-routine writer 53 contents-read-back:text <- slurp resources, [b] 54 10:bool/raw <- equal contents-read-back, [xy] @@ -119,10 +119,10 @@ if ('onhashchange' in window) { 62 assume-resources [ 63 [a] <- [] 64 ] -65 sink:&:sink:char, writer:num/routine <- start-writing resources, [a] -66 sink <- write sink, 120/x -67 sink <- write sink, 121/y -68 close sink +65 sink:&:sink:char, writer:num/routine <- start-writing resources, [a] +66 sink <- write sink, 120/x +67 sink <- write sink, 121/y +68 close sink 69 wait-for-routine writer 70 contents-read-back:text <- slurp resources, [a] 71 10:bool/raw <- equal contents-read-back, [xy] @@ -139,10 +139,10 @@ if ('onhashchange' in window) { 82 |bcd| 83 ] 84 ] -85 sink:&:sink:char, writer:num/routine <- start-writing resources, [a] -86 sink <- write sink, 120/x -87 sink <- write sink, 121/y -88 close sink +85 sink:&:sink:char, writer:num/routine <- start-writing resources, [a] +86 sink <- write sink, 120/x +87 sink <- write sink, 121/y +88 close sink 89 wait-for-routine writer 90 contents-read-back:text <- slurp resources, [a] 91 10:bool/raw <- equal contents-read-back, [xy] diff --git a/html/092socket.mu.html b/html/092socket.mu.html index 427daf3d..c4f4b080 100644 --- a/html/092socket.mu.html +++ b/html/092socket.mu.html @@ -75,8 +75,8 @@ if ('onhashchange' in window) { 14 F - example-server-test: $open-server-socket failed] 15 handler-routine:number <- start-running serve-one-request socket, example-handler 16 ] - 17 source:&:source:char <- start-reading-from-network 0/real-resources, [localhost/], port - 18 response:text <- drain source + 17 source:&:source:char <- start-reading-from-network 0/real-resources, [localhost/], port + 18 response:text <- drain source 19 10:@:char/raw <- copy *response 20 memory-should-contain [ 21 10:array:character <- [abc] @@ -101,9 +101,9 @@ if ('onhashchange' in window) { 40 ] 41 ] 42 run [ - 43 source:&:source:char <- start-reading-from-network resources, [example.com/] + 43 source:&:source:char <- start-reading-from-network resources, [example.com/] 44 ] - 45 contents:text <- drain source + 45 contents:text <- drain source 46 10:@:char/raw <- copy *contents 47 memory-should-contain [ 48 10:array:character <- [abc @@ -119,15 +119,15 @@ if ('onhashchange' in window) { 58 session:num <- $accept socket 59 assert session, [ 60 F - example-server-test: $accept failed] - 61 contents:&:source:char, sink:&:sink:char <- new-channel 30 - 62 start-running receive-from-socket session, sink - 63 query:text <- drain contents + 61 contents:&:source:char, sink:&:sink:char <- new-channel 30 + 62 start-running receive-from-socket session, sink + 63 query:text <- drain contents 64 response:text <- call request-handler, query 65 write-to-socket session, response 66 session <- $close-socket session 67 ] 68 - 69 def start-reading-from-network resources:&:resources, uri:text -> contents:&:source:char [ + 69 def start-reading-from-network resources:&:resources, uri:text -> contents:&:source:char [ 70 local-scope 71 load-inputs 72 { @@ -147,8 +147,8 @@ if ('onhashchange' in window) { 86 assert socket, [contents] 87 req:text <- interpolate [GET _ HTTP/1.1], path 88 request-socket socket, req - 89 contents:&:source:char, sink:&:sink:char <- new-channel 10000 - 90 start-running receive-from-client-socket-and-close socket, sink + 89 contents:&:source:char, sink:&:sink:char <- new-channel 10000 + 90 start-running receive-from-client-socket-and-close socket, sink 91 ] 92 93 def request-socket socket:num, s:text -> socket:num [ @@ -162,7 +162,7 @@ if ('onhashchange' in window) { 101 $write-to-socket socket, 10/lf 102 ] 103 -104 def receive-from-socket socket:num, sink:&:sink:char -> sink:&:sink:char, socket:num [ +104 def receive-from-socket socket:num, sink:&:sink:char -> sink:&:sink:char, socket:num [ 105 local-scope 106 load-inputs 107 { @@ -172,7 +172,7 @@ if ('onhashchange' in window) { 111 break-if error 112 { 113 break-unless found? -114 sink <- write sink, c +114 sink <- write sink, c 115 } 116 { 117 break-if found? @@ -180,13 +180,13 @@ if ('onhashchange' in window) { 119 } 120 loop 121 } -122 sink <- close sink +122 sink <- close sink 123 ] 124 -125 def receive-from-client-socket-and-close socket:num, sink:&:sink:char -> sink:&:sink:char, socket:num [ +125 def receive-from-client-socket-and-close socket:num, sink:&:sink:char -> sink:&:sink:char, socket:num [ 126 local-scope 127 load-inputs -128 sink <- receive-from-socket socket, sink +128 sink <- receive-from-socket socket, sink 129 socket <- $close-socket socket 130 ] 131 diff --git a/html/channel.mu.html b/html/channel.mu.html index f69af8e5..fdb5c2dc 100644 --- a/html/channel.mu.html +++ b/html/channel.mu.html @@ -59,7 +59,7 @@ if ('onhashchange' in window) {
  1 # example program: communicating between routines using channels
  2 
- 3 def producer sink:&:sink:char -> sink:&:sink:char [
+ 3 def producer sink:&:sink:char -> sink:&:sink:char [
  4   # produce characters 1 to 5 on a channel
  5   local-scope
  6   load-inputs
@@ -71,20 +71,20 @@ if ('onhashchange' in window) {
 12     # other threads might get between these prints
 13     $print [produce: ], n, [ 
 14 ]
-15     sink <- write sink, n
+15     sink <- write sink, n
 16     n <- add n, 1
 17     loop
 18   }
-19   close sink
+19   close sink
 20 ]
 21 
-22 def consumer source:&:source:char -> source:&:source:char [
+22 def consumer source:&:source:char -> source:&:source:char [
 23   # consume and print integers from a channel
 24   local-scope
 25   load-inputs
 26   {
 27     # read an integer from the channel
-28     n:char, eof?:bool, source <- read source
+28     n:char, eof?:bool, source <- read source
 29     break-if eof?
 30     # other threads might get between these prints
 31     $print [consume: ], n:char, [ 
@@ -95,10 +95,10 @@ if ('onhashchange' in window) {
 36 
 37 def main [
 38   local-scope
-39   source:&:source:char, sink:&:sink:char <- new-channel 3/capacity
+39   source:&:source:char, sink:&:sink:char <- new-channel 3/capacity
 40   # create two background 'routines' that communicate by a channel
-41   routine1:num <- start-running producer, sink
-42   routine2:num <- start-running consumer, source
+41   routine1:num <- start-running producer, sink
+42   routine2:num <- start-running consumer, source
 43   wait-for-routine routine1
 44   wait-for-routine routine2
 45 ]
diff --git a/html/chessboard.mu.html b/html/chessboard.mu.html
index 1d479289..2efbb576 100644
--- a/html/chessboard.mu.html
+++ b/html/chessboard.mu.html
@@ -134,11 +134,11 @@ if ('onhashchange' in window) {
  71   load-inputs
  72   board:board <- initial-position
  73   # hook up stdin
- 74   stdin-in:&:source:char, stdin-out:&:sink:char <- new-channel 10/capacity
+ 74   stdin-in:&:source:char, stdin-out:&:sink:char <- new-channel 10/capacity
  75   start-running send-keys-to-channel, console, stdin-out, screen
  76   # buffer lines in stdin
- 77   buffered-stdin-in:&:source:char, buffered-stdin-out:&:sink:char <- new-channel 10/capacity
- 78   start-running buffer-lines, stdin-in, buffered-stdin-out
+ 77   buffered-stdin-in:&:source:char, buffered-stdin-out:&:sink:char <- new-channel 10/capacity
+ 78   start-running buffer-lines, stdin-in, buffered-stdin-out
  79   {
  80     print screen, [Stupid text-mode chessboard. White pieces in uppercase; black pieces in lowercase. No checking for legal moves.
  81 ]
@@ -155,7 +155,7 @@ if ('onhashchange' in window) {
  92       screen <- print screen, [move: ]
  93       m:&:move, quit:bool, error:bool <- read-move buffered-stdin-in, screen
  94       break-if quit, +quit
- 95       buffered-stdin-in <- clear buffered-stdin-in  # cleanup after error. todo: test this?
+ 95       buffered-stdin-in <- clear buffered-stdin-in  # cleanup after error. todo: test this?
  96       loop-if error
  97     }
  98     board <- make-move board, m
@@ -308,7 +308,7 @@ if ('onhashchange' in window) {
 245 ]
 246 
 247 # prints only error messages to screen
-248 def read-move stdin:&:source:char, screen:&:screen -> result:&:move, quit?:bool, error?:bool, stdin:&:source:char, screen:&:screen [
+248 def read-move stdin:&:source:char, screen:&:screen -> result:&:move, quit?:bool, error?:bool, stdin:&:source:char, screen:&:screen [
 249   local-scope
 250   load-inputs
 251   from-file:num, quit?:bool, error?:bool <- read-file stdin, screen
@@ -336,7 +336,7 @@ if ('onhashchange' in window) {
 273 ]
 274 
 275 # valid values for file: 0-7
-276 def read-file stdin:&:source:char, screen:&:screen -> file:num, quit:bool, error:bool, stdin:&:source:char, screen:&:screen [
+276 def read-file stdin:&:source:char, screen:&:screen -> file:num, quit:bool, error:bool, stdin:&:source:char, screen:&:screen [
 277   local-scope
 278   load-inputs
 279   c:char, eof?:bool, stdin <- read stdin
@@ -374,7 +374,7 @@ if ('onhashchange' in window) {
 311 ]
 312 
 313 # valid values for rank: 0-7
-314 def read-rank stdin:&:source:char, screen:&:screen -> rank:num, quit?:bool, error?:bool, stdin:&:source:char, screen:&:screen [
+314 def read-rank stdin:&:source:char, screen:&:screen -> rank:num, quit?:bool, error?:bool, stdin:&:source:char, screen:&:screen [
 315   local-scope
 316   load-inputs
 317   c:char, eof?:bool, stdin <- read stdin
@@ -412,7 +412,7 @@ if ('onhashchange' in window) {
 349 
 350 # read a character from the given channel and check that it's what we expect
 351 # return true on error
-352 def expect-from-channel stdin:&:source:char, expected:char, screen:&:screen -> result:bool, stdin:&:source:char, screen:&:screen [
+352 def expect-from-channel stdin:&:source:char, expected:char, screen:&:screen -> result:bool, stdin:&:source:char, screen:&:screen [
 353   local-scope
 354   load-inputs
 355   c:char, eof?:bool, stdin <- read stdin
@@ -428,8 +428,8 @@ if ('onhashchange' in window) {
 365 scenario read-move-blocking [
 366   local-scope
 367   assume-screen 20/width, 2/height
-368   source:&:source:char, sink:&:sink:char <- new-channel 2/capacity
-369   read-move-routine:num/routine <- start-running read-move, source, screen
+368   source:&:source:char, sink:&:sink:char <- new-channel 2/capacity
+369   read-move-routine:num/routine <- start-running read-move, source, screen
 370   run [
 371     # 'read-move' is waiting for keypress
 372     wait-for-routine-to-block read-move-routine
@@ -438,7 +438,7 @@ if ('onhashchange' in window) {
 375     assert waiting?, [ 
 376 F read-move-blocking: routine failed to pause after coming up (before any keys were pressed)]
 377     # press 'a'
-378     sink <- write sink, 97/a
+378     sink <- write sink, 97/a
 379     restart read-move-routine
 380     # 'read-move' still waiting for keypress
 381     wait-for-routine-to-block read-move-routine
@@ -447,7 +447,7 @@ if ('onhashchange' in window) {
 384     assert waiting?, [ 
 385 F read-move-blocking: routine failed to pause after rank 'a']
 386     # press '2'
-387     sink <- write sink, 50/'2'
+387     sink <- write sink, 50/'2'
 388     restart read-move-routine
 389     # 'read-move' still waiting for keypress
 390     wait-for-routine-to-block read-move-routine
@@ -456,7 +456,7 @@ if ('onhashchange' in window) {
 393     assert waiting?, [ 
 394 F read-move-blocking: routine failed to pause after file 'a2']
 395     # press '-'
-396     sink <- write sink, 45/'-'
+396     sink <- write sink, 45/'-'
 397     restart read-move-routine
 398     # 'read-move' still waiting for keypress
 399     wait-for-routine-to-block read-move-routine
@@ -465,7 +465,7 @@ if ('onhashchange' in window) {
 402     assert waiting?, [ 
 403 F read-move-blocking: routine failed to pause after hyphen 'a2-']
 404     # press 'a'
-405     sink <- write sink, 97/a
+405     sink <- write sink, 97/a
 406     restart read-move-routine
 407     # 'read-move' still waiting for keypress
 408     wait-for-routine-to-block read-move-routine
@@ -474,7 +474,7 @@ if ('onhashchange' in window) {
 411     assert waiting?, [ 
 412 F read-move-blocking: routine failed to pause after rank 'a2-a']
 413     # press '4'
-414     sink <- write sink, 52/'4'
+414     sink <- write sink, 52/'4'
 415     restart read-move-routine
 416     # 'read-move' still waiting for keypress
 417     wait-for-routine-to-block read-move-routine
@@ -483,7 +483,7 @@ if ('onhashchange' in window) {
 420     assert waiting?, [ 
 421 F read-move-blocking: routine failed to pause after file 'a2-a4']
 422     # press 'newline'
-423     sink <- write sink, 10  # newline
+423     sink <- write sink, 10  # newline
 424     restart read-move-routine
 425     # 'read-move' now completes
 426     wait-for-routine-to-block read-move-routine
@@ -501,8 +501,8 @@ if ('onhashchange' in window) {
 438 scenario read-move-quit [
 439   local-scope
 440   assume-screen 20/width, 2/height
-441   source:&:source:char, sink:&:sink:char <- new-channel 2/capacity
-442   read-move-routine:num <- start-running read-move, source, screen
+441   source:&:source:char, sink:&:sink:char <- new-channel 2/capacity
+442   read-move-routine:num <- start-running read-move, source, screen
 443   run [
 444     # 'read-move' is waiting for keypress
 445     wait-for-routine-to-block read-move-routine
@@ -511,7 +511,7 @@ if ('onhashchange' in window) {
 448     assert waiting?, [ 
 449 F read-move-quit: routine failed to pause after coming up (before any keys were pressed)]
 450     # press 'q'
-451     sink <- write sink, 113/q
+451     sink <- write sink, 113/q
 452     restart read-move-routine
 453     # 'read-move' completes
 454     wait-for-routine-to-block read-move-routine
@@ -529,8 +529,8 @@ if ('onhashchange' in window) {
 466 scenario read-move-illegal-file [
 467   local-scope
 468   assume-screen 20/width, 2/height
-469   source:&:source:char, sink:&:sink:char <- new-channel 2/capacity
-470   read-move-routine:num <- start-running read-move, source, screen
+469   source:&:source:char, sink:&:sink:char <- new-channel 2/capacity
+470   read-move-routine:num <- start-running read-move, source, screen
 471   run [
 472     # 'read-move' is waiting for keypress
 473     wait-for-routine-to-block read-move-routine
@@ -538,7 +538,7 @@ if ('onhashchange' in window) {
 475     waiting?:bool <- not-equal read-move-state, 2/discontinued
 476     assert waiting?, [ 
 477 F read-move-illegal-file: routine failed to pause after coming up (before any keys were pressed)]
-478     sink <- write sink, 50/'2'
+478     sink <- write sink, 50/'2'
 479     restart read-move-routine
 480     wait-for-routine-to-block read-move-routine
 481   ]
@@ -551,8 +551,8 @@ if ('onhashchange' in window) {
 488 scenario read-move-illegal-rank [
 489   local-scope
 490   assume-screen 20/width, 2/height
-491   source:&:source:char, sink:&:sink:char <- new-channel 2/capacity
-492   read-move-routine:num <- start-running read-move, source, screen
+491   source:&:source:char, sink:&:sink:char <- new-channel 2/capacity
+492   read-move-routine:num <- start-running read-move, source, screen
 493   run [
 494     # 'read-move' is waiting for keypress
 495     wait-for-routine-to-block read-move-routine
@@ -560,8 +560,8 @@ if ('onhashchange' in window) {
 497     waiting?:bool <- not-equal read-move-state, 2/discontinued
 498     assert waiting?, [ 
 499 F read-move-illegal-rank: routine failed to pause after coming up (before any keys were pressed)]
-500     sink <- write sink, 97/a
-501     sink <- write sink, 97/a
+500     sink <- write sink, 97/a
+501     sink <- write sink, 97/a
 502     restart read-move-routine
 503     wait-for-routine-to-block read-move-routine
 504   ]
@@ -574,8 +574,8 @@ if ('onhashchange' in window) {
 511 scenario read-move-empty [
 512   local-scope
 513   assume-screen 20/width, 2/height
-514   source:&:source:char, sink:&:sink:char <- new-channel 2/capacity
-515   read-move-routine:num <- start-running read-move, source, screen
+514   source:&:source:char, sink:&:sink:char <- new-channel 2/capacity
+515   read-move-routine:num <- start-running read-move, source, screen
 516   run [
 517     # 'read-move' is waiting for keypress
 518     wait-for-routine-to-block read-move-routine
@@ -583,8 +583,8 @@ if ('onhashchange' in window) {
 520     waiting?:bool <- not-equal read-move-state, 2/discontinued
 521     assert waiting?, [ 
 522 F read-move-empty: routine failed to pause after coming up (before any keys were pressed)]
-523     sink <- write sink, 10/newline
-524     sink <- write sink, 97/a
+523     sink <- write sink, 10/newline
+524     sink <- write sink, 97/a
 525     restart read-move-routine
 526     wait-for-routine-to-block read-move-routine
 527   ]
diff --git a/html/filesystem.mu.html b/html/filesystem.mu.html
index 8a6ec191..56ea3e96 100644
--- a/html/filesystem.mu.html
+++ b/html/filesystem.mu.html
@@ -64,12 +64,12 @@ if ('onhashchange' in window) {
  5 
  6 def main [
  7   local-scope
- 8   source-file:&:source:char <- start-reading 0/real-filesystem, [/tmp/mu-x]
- 9   sink-file:&:sink:char, write-routine:num <- start-writing 0/real-filesystem, [/tmp/mu-y]
+ 8   source-file:&:source:char <- start-reading 0/real-filesystem, [/tmp/mu-x]
+ 9   sink-file:&:sink:char, write-routine:num <- start-writing 0/real-filesystem, [/tmp/mu-y]
 10   {
 11     c:char, done?:bool, source-file <- read source-file
 12     break-if done?
-13     sink-file <- write sink-file, c
+13     sink-file <- write sink-file, c
 14     loop
 15   }
 16   close sink-file
diff --git a/html/http-client.mu.html b/html/http-client.mu.html
index d44e5f97..a8f5abfb 100644
--- a/html/http-client.mu.html
+++ b/html/http-client.mu.html
@@ -62,7 +62,7 @@ if ('onhashchange' in window) {
  3 def main [
  4   local-scope
  5   $print [aaa] 10/newline
- 6   google:&:source:char <- start-reading-from-network 0/real-resources, [google.com/]
+ 6   google:&:source:char <- start-reading-from-network 0/real-resources, [google.com/]
  7   $print [bbb] 10/newline
  8   n:num <- copy 0
  9   buf:&:buffer:char <- new-buffer 30
diff --git a/html/http-server.mu.html b/html/http-server.mu.html
index b8ff0d97..01243ab0 100644
--- a/html/http-server.mu.html
+++ b/html/http-server.mu.html
@@ -71,9 +71,9 @@ if ('onhashchange' in window) {
 13   $print [Mu socket creation returned ], socket, 10/newline
 14   return-unless socket
 15   session:num <- $accept socket
-16   contents:&:source:char, sink:&:sink:char <- new-channel 30
-17   sink <- start-running receive-from-socket session, sink
-18   query:text <- drain contents
+16   contents:&:source:char, sink:&:sink:char <- new-channel 30
+17   sink <- start-running receive-from-socket session, sink
+18   query:text <- drain contents
 19   $print [Done reading from socket.], 10/newline
 20   write-to-socket session, [HTTP/1.0 200 OK
 21 Content-type: text/plain
diff --git a/html/real-files.mu.html b/html/real-files.mu.html
index c72cb608..e8d9da2d 100644
--- a/html/real-files.mu.html
+++ b/html/real-files.mu.html
@@ -69,7 +69,7 @@ if ('onhashchange' in window) {
 12   f <- $close-file f
 13   $print [file after closing: ], f, 10/newline
 14   f <- $open-file-for-writing [/tmp/mu-y]
-15   $print [file to write to: ], f, 10/newline
+15   $print [file to write to: ], f, 10/newline
 16   $write-to-file f, c
 17   f <- $close-file f
 18 ]
-- 
cgit 1.4.1-2-gfad0