about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--010vm.cc4
-rw-r--r--012transform.cc12
-rw-r--r--020run.cc1
-rw-r--r--030container.cc72
4 files changed, 68 insertions, 21 deletions
diff --git a/010vm.cc b/010vm.cc
index 1f33c9cf..eec9e2ff 100644
--- a/010vm.cc
+++ b/010vm.cc
@@ -171,7 +171,9 @@ struct type_info {
   kind_of_type kind;
   vector<reagent> elements;
   // End type_info Fields
-  type_info() :kind(PRIMITIVE) {}
+  type_info() :kind(PRIMITIVE) {
+    // End type_info Constructor
+  }
 };
 
 enum primitive_recipes {
diff --git a/012transform.cc b/012transform.cc
index d3b4384e..b5b6e4fe 100644
--- a/012transform.cc
+++ b/012transform.cc
@@ -56,6 +56,18 @@ void transform_all() {
   // End transform_all
 }
 
+//: Even though a run will involve many calls to transform_all() for tests,
+//: our logical model is to load all code, then transform all code, then run.
+//: If you load new code that should cause already-transformed recipes to
+//: change, that's not supported. To help detect such situations and raise
+//: helpful errors we track a count of the number of calls made to
+//: transform_all().
+:(before "End Globals")
+int Num_calls_to_transform_all = 0;
+:(after "void transform_all()")
+  ++Num_calls_to_transform_all;
+
+:(code)
 void parse_int_reagents() {
   trace(9991, "transform") << "--- parsing any uninitialized reagents as integers" << end();
   for (map<recipe_ordinal, recipe>::iterator p = Recipe.begin(); p != Recipe.end(); ++p) {
diff --git a/020run.cc b/020run.cc
index 7764adc9..a94eb668 100644
--- a/020run.cc
+++ b/020run.cc
@@ -170,6 +170,7 @@ if (!Run_tests && contains_key(Recipe_ordinal, "main") && contains_key(Recipe, g
 //?   Trace_file = "interactive";
 //?   START_TRACING_UNTIL_END_OF_SCOPE;
   trace(9990, "run") << "=== Starting to run" << end();
+  assert(Num_calls_to_transform_all == 1);
   run_main(argc, argv);
   teardown();
 }
diff --git a/030container.cc b/030container.cc
index 90f9d713..38682e96 100644
--- a/030container.cc
+++ b/030container.cc
@@ -379,11 +379,38 @@ container bar [
 +parse:   element: {x: "number"}
 +parse:   element: {y: "number"}
 
+//: if a container is defined again, the new fields add to the original definition
+:(scenarios run)
+:(scenario container_extend)
+container foo [
+  x:number
+]
+# add to previous definition
+container foo [
+  y:number
+]
+def main [
+  1:number <- copy 34
+  2:number <- copy 35
+  3:number <- get 1:foo, 0:offset
+  4:number <- get 1:foo, 1:offset
+]
++mem: storing 34 in location 3
++mem: storing 35 in location 4
+
 :(before "End Command Handlers")
 else if (command == "container") {
   insert_container(command, CONTAINER, in);
 }
 
+//: Even though we allow containers to be extended, we don't allow this after
+//: a call to transform_all. But we do want to detect this situation and raise
+//: an error. This field will help us raise such errors.
+:(before "End type_info Fields")
+int Num_calls_to_transform_all_at_first_definition;
+:(before "End type_info Constructor")
+Num_calls_to_transform_all_at_first_definition = -1;
+
 :(code)
 void insert_container(const string& command, kind_of_type kind, istream& in) {
   skip_whitespace_but_not_newline(in);
@@ -397,6 +424,15 @@ void insert_container(const string& command, kind_of_type kind, istream& in) {
   trace(9999, "parse") << "type number: " << get(Type_ordinal, name) << end();
   skip_bracket(in, "'container' must begin with '['");
   type_info& info = get_or_insert(Type, get(Type_ordinal, name));
+  if (info.Num_calls_to_transform_all_at_first_definition == -1) {
+    // initial definition of this container
+    info.Num_calls_to_transform_all_at_first_definition = Num_calls_to_transform_all;
+  }
+  else if (info.Num_calls_to_transform_all_at_first_definition != Num_calls_to_transform_all) {
+    // extension after transform_all
+    raise << "there was a call to transform_all() between the definition of container " << name << " and a subsequent extension. This is not supported, since any recipes that used " << name << " values have already been transformed and 'frozen'.\n" << end();
+    return;
+  }
   info.name = name;
   info.kind = kind;
   while (has_data(in)) {
@@ -435,26 +471,6 @@ void skip_bracket(istream& in, string message) {
     raise << message << '\n' << end();
 }
 
-:(scenarios run)
-:(scenario container_extend)
-container foo [
-  x:number
-]
-
-# add to previous definition
-container foo [
-  y:number
-]
-
-def main [
-  1:number <- copy 34
-  2:number <- copy 35
-  3:number <- get 1:foo, 0:offset
-  4:number <- get 1:foo, 1:offset
-]
-+mem: storing 34 in location 3
-+mem: storing 35 in location 4
-
 //: ensure scenarios are consistent by always starting them at the same type
 //: number.
 :(before "End Setup")  //: for tests
@@ -462,6 +478,22 @@ Next_type_ordinal = 1000;
 :(before "End Test Run Initialization")
 assert(Next_type_ordinal < 1000);
 
+:(code)
+void test_error_on_transform_all_between_container_definition_and_extension() {
+  // define a container
+  run("container foo [\n"
+      "  a:number\n"
+      "]\n");
+  // try to extend the container after transform
+  transform_all();
+  CHECK_TRACE_DOESNT_CONTAIN_ERROR();
+  Hide_errors = true;
+  run("container foo [\n"
+      "  b:number\n"
+      "]\n");
+  CHECK_TRACE_CONTAINS_ERROR();
+}
+
 //:: Allow container definitions anywhere in the codebase, but complain if you
 //:: can't find a definition at the end.