about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--030container.cc180
-rw-r--r--031address.cc3
-rw-r--r--033exclusive_container.cc139
-rw-r--r--034call.cc4
-rw-r--r--058shape_shifting_container.cc132
5 files changed, 440 insertions, 18 deletions
diff --git a/030container.cc b/030container.cc
index 60b38830..07e9718e 100644
--- a/030container.cc
+++ b/030container.cc
@@ -647,8 +647,7 @@ void check_invalid_types(type_tree* type, const string& block, const string& nam
   check_invalid_types(type->right, block, name);
 }
 
-//:: Construct types out of their constituent fields. Doesn't currently do
-//:: type-checking but *does* match sizes.
+//:: Construct types out of their constituent fields.
 
 :(scenario merge)
 container foo [
@@ -668,6 +667,7 @@ MERGE,
 put(Recipe_ordinal, "merge", MERGE);
 :(before "End Primitive Recipe Checks")
 case MERGE: {
+  // type-checking in a separate transform below
   break;
 }
 :(before "End Primitive Recipe Implementations")
@@ -678,3 +678,179 @@ case MERGE: {
       products.at(0).push_back(ingredients.at(i).at(j));
   break;
 }
+
+//: type-check 'merge' to avoid interpreting numbers as addresses
+
+:(scenario merge_check)
+% Hide_errors = true;
+recipe main [
+  1:point <- merge 3, 4
+]
+$error: 0
+
+:(scenario merge_check_missing_element)
+% Hide_errors = true;
+recipe main [
+  1:point <- merge 3
+]
++error: main: too few ingredients in '1:point <- merge 3'
+
+:(scenario merge_check_extra_element)
+% Hide_errors = true;
+recipe main [
+  1:point <- merge 3, 4, 5
+]
++error: main: too many ingredients in '1:point <- merge 3, 4, 5'
+
+//: We want to avoid causing memory corruption, but other than that we want to
+//: be flexible in how we construct containers of containers. It should be
+//: equally easy to define a container out of primitives or intermediate
+//: container fields.
+
+:(scenario merge_check_recursive_containers)
+% Hide_errors = true;
+recipe main [
+  1:point <- merge 3, 4
+  1:point-number <- merge 1:point, 5
+]
+$error: 0
+
+:(scenario merge_check_recursive_containers_2)
+% Hide_errors = true;
+recipe main [
+  1:point <- merge 3, 4
+  2:point-number <- merge 1:point
+]
++error: main: too few ingredients in '2:point-number <- merge 1:point'
+
+:(scenario merge_check_recursive_containers_3)
+% Hide_errors = true;
+recipe main [
+  1:point-number <- merge 3, 4, 5
+]
+$error: 0
+
+:(scenario merge_check_recursive_containers_4)
+% Hide_errors = true;
+recipe main [
+  1:point-number <- merge 3, 4
+]
++error: main: too few ingredients in '1:point-number <- merge 3, 4'
+
+//: Since a container can be merged in several ways, we need to be able to
+//: backtrack through different possibilities. Later we'll allow creating
+//: exclusive containers which contain just one of rather than all of their
+//: elements. That will also require backtracking capabilities. Here's the
+//: state we need to maintain for backtracking:
+
+:(before "End Types")
+struct merge_check_point {
+  reagent container;
+  long long int container_element_index;
+  merge_check_point(const reagent& c, long long int i) :container(c), container_element_index(i) {}
+};
+
+struct merge_check_state {
+  stack<merge_check_point> data;
+};
+
+:(before "End Checks")
+Transform.push_back(check_merge_calls);
+:(code)
+void check_merge_calls(const recipe_ordinal r) {
+  const recipe& caller = get(Recipe, r);
+  trace(9991, "transform") << "--- type-check merge instructions in recipe " << caller.name << end();
+  for (long long int i = 0; i < SIZE(caller.steps); ++i) {
+    const instruction& inst = caller.steps.at(i);
+    if (inst.name != "merge") continue;
+    if (SIZE(inst.products) != 1) {
+      raise_error << maybe(caller.name) << "'merge' should yield a single product in '" << inst.to_string() << "'\n" << end();
+      continue;
+    }
+    reagent product = inst.products.at(0);
+    // Update product While Type-checking Merge
+    type_ordinal product_type = product.type->value;
+    if (product_type == 0 || !contains_key(Type, product_type)) {
+      raise_error << maybe(caller.name) << "'merge' should yield a container in '" << inst.to_string() << "'\n" << end();
+      continue;
+    }
+    const type_info& info = get(Type, product_type);
+    if (info.kind != CONTAINER && info.kind != EXCLUSIVE_CONTAINER) {
+      raise_error << maybe(caller.name) << "'merge' should yield a container in '" << inst.to_string() << "'\n" << end();
+      continue;
+    }
+    check_merge_call(inst.ingredients, product, caller, inst);
+  }
+}
+
+void check_merge_call(const vector<reagent>& ingredients, const reagent& product, const recipe& caller, const instruction& inst) {
+  long long int ingredient_index = 0;
+  merge_check_state state;
+  state.data.push(merge_check_point(product, 0));
+  while (true) {
+    assert(!state.data.empty());
+    trace(9999, "transform") << ingredient_index << " vs " << SIZE(ingredients) << end();
+    if (ingredient_index >= SIZE(ingredients)) {
+      raise_error << maybe(caller.name) << "too few ingredients in '" << inst.to_string() << "'\n" << end();
+      return;
+    }
+    reagent& container = state.data.top().container;
+    type_info& container_info = get(Type, container.type->value);
+    switch (container_info.kind) {
+      case CONTAINER: {
+        reagent expected_ingredient = element_type(container, state.data.top().container_element_index);
+        trace(9999, "transform") << "checking container " << debug_string(container) << " || " << debug_string(expected_ingredient) << " vs ingredient " << ingredient_index << end();
+        // if the current element is the ingredient we expect, move on to the next element/ingredient
+        if (types_coercible(expected_ingredient, ingredients.at(ingredient_index))) {
+          ++ingredient_index;
+          ++state.data.top().container_element_index;
+          while (state.data.top().container_element_index >= SIZE(get(Type, state.data.top().container.type->value).elements)) {
+            state.data.pop();
+            if (state.data.empty()) {
+              if (ingredient_index < SIZE(ingredients))
+                raise_error << maybe(caller.name) << "too many ingredients in '" << inst.to_string() << "'\n" << end();
+              return;
+            }
+            ++state.data.top().container_element_index;
+          }
+        }
+        // if not, maybe it's a field of the current element
+        else {
+          // no change to ingredient_index
+          state.data.push(merge_check_point(expected_ingredient, 0));
+        }
+        break;
+      }
+      // End valid_merge Cases
+      default: {
+        if (!types_coercible(container, ingredients.at(ingredient_index))) {
+          raise_error << maybe(caller.name) << "incorrect type of ingredient " << ingredient_index << " in '" << inst.to_string() << "'\n" << end();
+          return;
+        }
+        ++ingredient_index;
+        do {
+          state.data.pop();
+          if (state.data.empty()) {
+            if (ingredient_index < SIZE(ingredients))
+              raise_error << maybe(caller.name) << "too many ingredients in '" << inst.to_string() << "'\n" << end();
+            return;
+          }
+          ++state.data.top().container_element_index;
+        } while (state.data.top().container_element_index >= SIZE(get(Type, state.data.top().container.type->value).elements));
+      }
+    }
+  }
+  // never gets here
+  assert(false);
+}
+
+:(scenario merge_check_product)
+% Hide_errors = true;
+recipe main [
+  1:number <- merge 3
+]
++error: main: 'merge' should yield a container in '1:number <- merge 3'
+
+:(before "End Includes")
+#include <stack>
+using std::stack;
diff --git a/031address.cc b/031address.cc
index 3fe289dd..5284dea1 100644
--- a/031address.cc
+++ b/031address.cc
@@ -84,6 +84,9 @@ recipe foo [
 :(after "bool is_mu_number(reagent r)")
   if (!canonize_type(r)) return false;
 
+:(after "Update product While Type-checking Merge")
+if (!canonize_type(product)) continue;
+
 :(code)
 bool canonize_type(reagent& r) {
   while (has_property(r, "lookup")) {
diff --git a/033exclusive_container.cc b/033exclusive_container.cc
index 46dd2e64..1d63f593 100644
--- a/033exclusive_container.cc
+++ b/033exclusive_container.cc
@@ -145,6 +145,7 @@ const reagent variant_type(const reagent& canonized_base, long long int tag) {
   assert(info.kind == EXCLUSIVE_CONTAINER);
   reagent element;
   element.type = new type_tree(*info.elements.at(tag));
+  element.properties.at(0).second = new string_tree(*info.element_type_names.at(tag));
   // End variant_type Special-cases
   return element;
 }
@@ -195,6 +196,144 @@ recipe main [
 +mem: storing 1 in location 4
 +mem: storing 34 in location 5
 
+//: type-checking for 'merge' on exclusive containers
+
+:(scenario merge_handles_exclusive_container)
+% Hide_errors = true;
+exclusive-container foo [
+  x:number
+  y:bar
+]
+container bar [
+  z:number
+]
+recipe main [
+  1:foo <- merge 0/x, 34
+]
++mem: storing 0 in location 1
++mem: storing 34 in location 2
+$error: 0
+
+:(scenario merge_requires_literal_tag_for_exclusive_container)
+% Hide_errors = true;
+exclusive-container foo [
+  x:number
+  y:bar
+]
+container bar [
+  z:number
+]
+recipe main [
+  local-scope
+  1:number <- copy 0
+  2:foo <- merge 1:number, 34
+]
++error: main: ingredient 0 of 'merge' should be a literal, for the tag of exclusive-container foo
+
+:(before "End valid_merge Cases")
+case EXCLUSIVE_CONTAINER: {
+  assert(state.data.top().container_element_index == 0);
+  trace(9999, "transform") << "checking exclusive container " << debug_string(container) << " vs ingredient " << ingredient_index << end();
+  if (!is_literal(ingredients.at(ingredient_index))) {
+    raise_error << maybe(caller.name) << "ingredient " << ingredient_index << " of 'merge' should be a literal, for the tag of exclusive-container " << container_info.name << '\n' << end();
+    return;
+  }
+  reagent ingredient = ingredients.at(ingredient_index);  // unnecessary copy just to keep this function from modifying caller
+  populate_value(ingredient);
+  if (ingredient.value >= SIZE(container_info.elements)) {
+    raise_error << maybe(caller.name) << "invalid tag at " << ingredient_index << " for " << container_info.name << " in '" << inst.to_string() << '\n' << end();
+    return;
+  }
+  reagent variant = variant_type(container, ingredient.value);
+  trace(9999, "transform") << "tag: " << ingredient.value << end();
+  // replace union with its variant
+  state.data.pop();
+  state.data.push(merge_check_point(variant, 0));
+  ++ingredient_index;
+  break;
+}
+
+:(scenario merge_check_container_containing_exclusive_container)
+% Hide_errors = true;
+container foo [
+  x:number
+  y:bar
+]
+exclusive-container bar [
+  x:number
+  y:number
+]
+recipe main [
+  1:foo <- merge 23, 1/y, 34
+]
++mem: storing 23 in location 1
++mem: storing 1 in location 2
++mem: storing 34 in location 3
+$error: 0
+
+:(scenario merge_check_container_containing_exclusive_container_2)
+% Hide_errors = true;
+container foo [
+  x:number
+  y:bar
+]
+exclusive-container bar [
+  x:number
+  y:number
+]
+recipe main [
+  1:foo <- merge 23, 1/y, 34, 35
+]
++error: main: too many ingredients in '1:foo <- merge 23, 1/y, 34, 35'
+
+:(scenario merge_check_exclusive_container_containing_container)
+% Hide_errors = true;
+exclusive-container foo [
+  x:number
+  y:bar
+]
+container bar [
+  x:number
+  y:number
+]
+recipe main [
+  1:foo <- merge 1/y, 23, 34
+]
++mem: storing 1 in location 1
++mem: storing 23 in location 2
++mem: storing 34 in location 3
+$error: 0
+
+:(scenario merge_check_exclusive_container_containing_container_2)
+% Hide_errors = true;
+exclusive-container foo [
+  x:number
+  y:bar
+]
+container bar [
+  x:number
+  y:number
+]
+recipe main [
+  1:foo <- merge 0/x, 23
+]
+$error: 0
+
+:(scenario merge_check_exclusive_container_containing_container_3)
+% Hide_errors = true;
+exclusive-container foo [
+  x:number
+  y:bar
+]
+container bar [
+  x:number
+  y:number
+]
+recipe main [
+  1:foo <- merge 1/y, 23
+]
++error: main: too few ingredients in '1:foo <- merge 1/y, 23'
+
 //: Since the different variants of an exclusive-container might have
 //: different sizes, relax the size mismatch check for 'merge' instructions.
 :(before "End size_mismatch(x) Cases")
diff --git a/034call.cc b/034call.cc
index ff004dc1..f9ba5def 100644
--- a/034call.cc
+++ b/034call.cc
@@ -165,7 +165,3 @@ while (current_step_index() >= SIZE(Current_routine->steps())) {
   // todo: fail if no products returned
   ++current_step_index();
 }
-
-:(before "End Includes")
-#include <stack>
-using std::stack;
diff --git a/058shape_shifting_container.cc b/058shape_shifting_container.cc
index 2fa8f497..e4822cc0 100644
--- a/058shape_shifting_container.cc
+++ b/058shape_shifting_container.cc
@@ -112,22 +112,27 @@ if (t.elements.at(i)->value >= START_TYPE_INGREDIENTS) {
 :(code)
 // shape-shifting version of size_of
 long long int size_of_type_ingredient(const type_tree* element_template, const type_tree* rest_of_use) {
+  type_tree* element_type = type_ingredient(element_template, rest_of_use);
+  if (!element_type) return 0;
+  long long int result = size_of(element_type);
+  delete element_type;
+  return result;
+}
+
+type_tree* type_ingredient(const type_tree* element_template, const type_tree* rest_of_use) {
   long long int type_ingredient_index = element_template->value - START_TYPE_INGREDIENTS;
   const type_tree* curr = rest_of_use;
-  if (!curr) return 0;
+  if (!curr) return NULL;
   while (type_ingredient_index > 0) {
     --type_ingredient_index;
     curr = curr->right;
-    if (!curr) return 0;
+    if (!curr) return NULL;
   }
   assert(curr);
   assert(!curr->left);  // unimplemented
-  if (!contains_key(Type, curr->value)) return 0;
+  if (!contains_key(Type, curr->value)) return NULL;
   trace(9999, "type") << "type deduced to be " << get(Type, curr->value).name << "$" << end();
-  type_tree tmp(curr->value);
-  if (curr->right)
-    tmp.right = new type_tree(*curr->right);
-  return size_of(&tmp);
+  return new type_tree(*curr);
 }
 
 :(scenario get_on_shape_shifting_container)
@@ -169,7 +174,7 @@ container foo:_t [
   y:number
 ]
 recipe main [
-  1:foo:address:point <- merge 34, 48  # unsafe
+  1:foo:address:point <- merge 34/unsafe, 48
   2:address:point <- get 1:foo:address:point, x:offset
 ]
 +mem: storing 34 in location 2
@@ -178,7 +183,7 @@ recipe main [
 if (contains_type_ingredient(element)) {
   if (!canonized_base.type->right)
     raise_error << "illegal type '" << debug_string(canonized_base.type) << "' seems to be missing a type ingredient or three\n" << end();
-  replace_type_ingredient(element.type, canonized_base.type->right);
+  replace_type_ingredient(element.type, element.properties.at(0).second, canonized_base.type->right, canonized_base.properties.at(0).second->right);
 }
 
 :(code)
@@ -192,7 +197,7 @@ bool contains_type_ingredient(const type_tree* type) {
   return contains_type_ingredient(type->left) || contains_type_ingredient(type->right);
 }
 
-void replace_type_ingredient(type_tree* element_type, const type_tree* callsite_type) {
+void replace_type_ingredient(type_tree* element_type, string_tree* element_type_name, const type_tree* callsite_type, const string_tree* callsite_type_name) {
   if (!callsite_type) return;  // error but it's already been raised above
   if (!element_type) return;
   if (element_type->value >= START_TYPE_INGREDIENTS) {
@@ -200,14 +205,21 @@ void replace_type_ingredient(type_tree* element_type, const type_tree* callsite_
       raise_error << "illegal type '" << debug_string(callsite_type) << "' seems to be missing a type ingredient or three\n" << end();
       return;
     }
-    const type_tree* replacement = nth_type(callsite_type, element_type->value-START_TYPE_INGREDIENTS);
+    const long long int type_ingredient_index = element_type->value-START_TYPE_INGREDIENTS;
+    const type_tree* replacement = nth_type(callsite_type, type_ingredient_index);
     element_type->value = replacement->value;
     assert(!element_type->left);  // since value is set
     element_type->left = replacement->left ? new type_tree(*replacement->left) : NULL;
     assert(!element_type->right);  // unsupported
     element_type->right = replacement->right ? new type_tree(*replacement->right) : NULL;
+    const string_tree* replacement_name = nth_type_name(callsite_type_name, type_ingredient_index);
+    element_type_name->value = replacement_name->value;
+    assert(!element_type_name->left);  // since value is set
+    element_type_name->left = replacement_name->left ? new string_tree(*replacement_name->left) : NULL;
+    assert(!element_type_name->right);  // unsupported
+    element_type_name->right = replacement_name->right ? new string_tree(*replacement_name->right) : NULL;
   }
-  replace_type_ingredient(element_type->right, callsite_type);
+  replace_type_ingredient(element_type->right, element_type_name->right, callsite_type, callsite_type_name);
 }
 
 const type_tree* nth_type(const type_tree* base, long long int n) {
@@ -216,6 +228,12 @@ const type_tree* nth_type(const type_tree* base, long long int n) {
   return nth_type(base->right, n-1);
 }
 
+const string_tree* nth_type_name(const string_tree* base, long long int n) {
+  assert(n >= 0);
+  if (n == 0) return base;
+  return nth_type_name(base->right, n-1);
+}
+
 bool has_nth_type(const type_tree* base, long long int n) {
   assert(n >= 0);
   if (base == NULL) return false;
@@ -272,3 +290,93 @@ recipe main [
   2:number <- get 1:bar, 1:offset
 ]
 +mem: storing 17 in location 2
+
+//: 'merge' on shape-shifting containers
+
+:(scenario merge_check_shape_shifting_container_containing_exclusive_container)
+% Hide_errors = true;
+container foo:_elem [
+  x:number
+  y:_elem
+]
+exclusive-container bar [
+  x:number
+  y:number
+]
+recipe main [
+  1:foo:bar <- merge 23, 1/y, 34
+]
++mem: storing 23 in location 1
++mem: storing 1 in location 2
++mem: storing 34 in location 3
+$error: 0
+
+:(scenario merge_check_shape_shifting_container_containing_exclusive_container_2)
+% Hide_errors = true;
+container foo:_elem [
+  x:number
+  y:_elem
+]
+exclusive-container bar [
+  x:number
+  y:number
+]
+recipe main [
+  1:foo:bar <- merge 23, 1/y, 34, 35
+]
++error: main: too many ingredients in '1:foo:bar <- merge 23, 1/y, 34, 35'
+
+:(scenario merge_check_shape_shifting_exclusive_container_containing_container)
+% Hide_errors = true;
+exclusive-container foo:_elem [
+  x:number
+  y:_elem
+]
+container bar [
+  x:number
+  y:number
+]
+recipe main [
+  1:foo:bar <- merge 1/y, 23, 34
+]
++mem: storing 1 in location 1
++mem: storing 23 in location 2
++mem: storing 34 in location 3
+$error: 0
+
+:(scenario merge_check_shape_shifting_exclusive_container_containing_container_2)
+% Hide_errors = true;
+exclusive-container foo:_elem [
+  x:number
+  y:_elem
+]
+container bar [
+  x:number
+  y:number
+]
+recipe main [
+  1:foo:bar <- merge 0/x, 23
+]
+$error: 0
+
+:(scenario merge_check_shape_shifting_exclusive_container_containing_container_3)
+% Hide_errors = true;
+exclusive-container foo:_elem [
+  x:number
+  y:_elem
+]
+container bar [
+  x:number
+  y:number
+]
+recipe main [
+  1:foo:bar <- merge 1/y, 23
+]
++error: main: too few ingredients in '1:foo:bar <- merge 1/y, 23'
+
+:(before "End variant_type Special-cases")
+if (contains_type_ingredient(element)) {
+  if (!canonized_base.type->right)
+    raise_error << "illegal type '" << debug_string(canonized_base.type) << "' seems to be missing a type ingredient or three\n" << end();
+  replace_type_ingredient(element.type, element.properties.at(0).second, canonized_base.type->right, canonized_base.properties.at(0).second->right);
+}