about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--030container.cc3
-rw-r--r--057shape_shifting_container.cc63
-rw-r--r--072channel.mu3
3 files changed, 61 insertions, 8 deletions
diff --git a/030container.cc b/030container.cc
index 3398e9ec..90f9d713 100644
--- a/030container.cc
+++ b/030container.cc
@@ -436,11 +436,12 @@ void skip_bracket(istream& in, string message) {
 }
 
 :(scenarios run)
-:(scenario container_define_twice)
+:(scenario container_extend)
 container foo [
   x:number
 ]
 
+# add to previous definition
 container foo [
   y:number
 ]
diff --git a/057shape_shifting_container.cc b/057shape_shifting_container.cc
index 036302af..e57edc1f 100644
--- a/057shape_shifting_container.cc
+++ b/057shape_shifting_container.cc
@@ -53,6 +53,25 @@ def main [
 ]
 $error: 0
 
+:(scenario shape_shifting_container_extend)
+container foo:_a [
+  x:_a
+]
+container foo:_a [
+  y:_a
+]
+$error: 0
+
+:(scenario shape_shifting_container_extend_error)
+% Hide_errors = true;
+container foo:_a [
+  x:_a
+]
+container foo:_b [
+  y:_b
+]
++error: headers of container 'foo' must use identical type ingredients
+
 :(before "End Globals")
 // We'll use large type ordinals to mean "the following type of the variable".
 const int START_TYPE_INGREDIENTS = 2000;
@@ -70,26 +89,60 @@ if (!info.type_ingredient_names.empty()) continue;
 :(before "End container Name Refinements")
 if (name.find(':') != string::npos) {
   trace(9999, "parse") << "container has type ingredients; parsing" << end();
-  read_type_ingredients(name);
+  if (!read_type_ingredients(name, command)) {
+    // error; skip rest of the container definition and continue
+    slurp_balanced_bracket(in);
+    return;
+  }
 }
 
 :(code)
-void read_type_ingredients(string& name) {
+bool read_type_ingredients(string& name, const string& command) {
   string save_name = name;
   istringstream in(save_name);
   name = slurp_until(in, ':');
+  map<string, type_ordinal> type_ingredient_names;
+  if (!slurp_type_ingredients(in, type_ingredient_names)) {
+    return false;
+  }
+  if (contains_key(Type_ordinal, name)
+      && contains_key(Type, get(Type_ordinal, name))) {
+    const type_info& info = get(Type, get(Type_ordinal, name));
+    // we've already seen this container; make sure type ingredients match
+    if (!type_ingredients_match(type_ingredient_names, info.type_ingredient_names)) {
+      raise << "headers of " << command << " '" << name << "' must use identical type ingredients\n" << end();
+      return false;
+    }
+    return true;
+  }
+  // we haven't seen this container before
   if (!contains_key(Type_ordinal, name) || get(Type_ordinal, name) == 0)
     put(Type_ordinal, name, Next_type_ordinal++);
   type_info& info = get_or_insert(Type, get(Type_ordinal, name));
+  info.type_ingredient_names.swap(type_ingredient_names);
+  return true;
+}
+
+bool slurp_type_ingredients(istream& in, map<string, type_ordinal>& out) {
   int next_type_ordinal = START_TYPE_INGREDIENTS;
   while (has_data(in)) {
     string curr = slurp_until(in, ':');
-    if (info.type_ingredient_names.find(curr) != info.type_ingredient_names.end()) {
+    if (out.find(curr) != out.end()) {
       raise << "can't repeat type ingredient names in a single container definition: " << curr << '\n' << end();
-      return;
+      return false;
     }
-    put(info.type_ingredient_names, curr, next_type_ordinal++);
+    put(out, curr, next_type_ordinal++);
+  }
+  return true;
+}
+
+bool type_ingredients_match(const map<string, type_ordinal>& a, const map<string, type_ordinal>& b) {
+  if (SIZE(a) != SIZE(b)) return false;
+  for (map<string, type_ordinal>::const_iterator p = a.begin(); p != a.end(); ++p) {
+    if (!contains_key(b, p->first)) return false;
+    if (p->second != get(b, p->first)) return false;
   }
+  return true;
 }
 
 :(before "End insert_container Special-cases")
diff --git a/072channel.mu b/072channel.mu
index 8565cd34..993581ab 100644
--- a/072channel.mu
+++ b/072channel.mu
@@ -256,8 +256,7 @@ scenario channel-read-not-full [
 
 # every channel comes with a boolean signifying if it's been closed
 # initially this boolean is false
-# todo: can't yet include type ingredients when extending containers
-container channel [
+container channel:_elem [
   closed?:boolean
 ]