about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2015-10-26 20:00:38 -0700
committerKartik K. Agaram <vc@akkartik.com>2015-10-26 20:02:18 -0700
commitae256ea13efc77cc767a658c6f61b12cd7461e21 (patch)
tree18dbcb26423b1d17d0639995c4bd29ec8f3d5c17
parent7bba6e7bb7fd7bfdfc71626a34a08cb96a084b74 (diff)
downloadmu-ae256ea13efc77cc767a658c6f61b12cd7461e21.tar.gz
2283 - represent each /property as a tree
-rw-r--r--010vm.cc143
-rw-r--r--011load.cc9
-rw-r--r--013literal_string.cc7
-rw-r--r--014literal_noninteger.cc3
-rw-r--r--021check_instruction.cc6
-rw-r--r--031address.cc4
-rw-r--r--032array.cc8
-rw-r--r--036call_reply.cc6
-rw-r--r--037recipe.cc2
-rw-r--r--043new.cc6
-rw-r--r--044space.cc6
-rw-r--r--045space_surround.cc4
-rw-r--r--046closure_name.cc20
-rw-r--r--047global.cc2
-rw-r--r--048check_type_by_name.cc4
-rw-r--r--050scenario.cc2
-rw-r--r--052tangle.cc2
-rw-r--r--053continuation.cc4
-rw-r--r--054dilated_reagent.cc6
19 files changed, 150 insertions, 94 deletions
diff --git a/010vm.cc b/010vm.cc
index d0a5fee5..3b4dae26 100644
--- a/010vm.cc
+++ b/010vm.cc
@@ -48,7 +48,7 @@ struct instruction {
 // properties besides types, but we're getting ahead of ourselves.
 struct reagent {
   string original_string;
-  vector<pair<string, vector<string> > > properties;
+  vector<pair<string, string_tree*> > properties;
   string name;
   double value;
   bool initialized;
@@ -76,13 +76,27 @@ struct type_tree {
   ~type_tree();
   type_tree(const type_tree& old);
   // simple: type ordinal
-  type_tree(type_ordinal v) :value(v), left(NULL), right(NULL) {}
+  explicit type_tree(type_ordinal v) :value(v), left(NULL), right(NULL) {}
   // intermediate: list of type ordinals
   type_tree(type_ordinal v, type_tree* r) :value(v), left(NULL), right(r) {}
   // advanced: tree containing type ordinals
   type_tree(type_tree* l, type_tree* r) :value(0), left(l), right(r) {}
 };
 
+struct string_tree {
+  string value;
+  string_tree* left;
+  string_tree* right;
+  ~string_tree();
+  string_tree(const string_tree& old);
+  // simple: flat string
+  explicit string_tree(string v) :value(v), left(NULL), right(NULL) {}
+  // intermediate: list of strings
+  string_tree(string v, string_tree* r) :value(v), left(NULL), right(r) {}
+  // advanced: tree containing strings
+  string_tree(string_tree* l, string_tree* r) :left(l), right(r) {}
+};
+
 :(before "End Globals")
 // Locations refer to a common 'memory'. Each location can store a number.
 map<long long int, double> Memory;
@@ -218,39 +232,59 @@ reagent::reagent(string s) :original_string(s), value(0), initialized(false), ty
   while (!in.eof()) {
     istringstream row(slurp_until(in, '/'));
     row >> std::noskipws;
-    string name = slurp_until(row, ':');
-    vector<string> values;
-    while (!row.eof())
-      values.push_back(slurp_until(row, ':'));
-    properties.push_back(pair<string, vector<string> >(name, values));
+    string key = slurp_until(row, ':');
+    string_tree* value = parse_property_list(row);
+    properties.push_back(pair<string, string_tree*>(key, value));
   }
   // structures for the first row of properties: name and list of types
   name = properties.at(0).first;
-  type_tree** curr_type = &type;
-  for (long long int i = 0; i < SIZE(properties.at(0).second); ++i) {
-    string type = properties.at(0).second.at(i);
-    if (Type_ordinal.find(type) == Type_ordinal.end()
-        // types can contain integers, like for array sizes
-        && !is_integer(type)) {
-      Type_ordinal[type] = Next_type_ordinal++;
-    }
-    *curr_type = new type_tree(Type_ordinal[type]);
-    curr_type = &(*curr_type)->right;
-  }
+  type = new_type_tree(properties.at(0).second);
   if (is_integer(name) && type == NULL) {
     type = new type_tree(0);
-    properties.at(0).second.push_back("literal");
+    assert(!properties.at(0).second);
+    properties.at(0).second = new string_tree("literal");
   }
   if (name == "_" && type == NULL) {
     type = new type_tree(0);
-    properties.at(0).second.push_back("dummy");
+    assert(!properties.at(0).second);
+    properties.at(0).second = new string_tree("dummy");
   }
   // End Parsing reagent
 }
 
+string_tree* parse_property_list(istream& in) {
+  skip_whitespace(in);
+  if (in.eof()) return NULL;
+  string_tree* result = new string_tree(slurp_until(in, ':'));
+  result->right = parse_property_list(in);
+  return result;
+}
+
+type_tree* new_type_tree(const string_tree* properties) {
+  if (!properties) return NULL;
+  type_tree* result = new type_tree(0);
+  if (!properties->value.empty()) {
+    const string& type_name = properties->value;
+    if (Type_ordinal.find(type_name) == Type_ordinal.end()
+        // types can contain integers, like for array sizes
+        && !is_integer(type_name)) {
+      Type_ordinal[type_name] = Next_type_ordinal++;
+    }
+    result->value = Type_ordinal[type_name];
+  }
+  result->left = new_type_tree(properties->left);
+  result->right = new_type_tree(properties->right);
+  return result;
+}
+
 //: avoid memory leaks for the type tree
 
 reagent::reagent(const reagent& old) :original_string(old.original_string), properties(old.properties), name(old.name), value(old.value), initialized(old.initialized) {
+  properties.clear();
+  for (long long int i = 0; i < SIZE(old.properties); ++i) {
+    properties.push_back(pair<string, string_tree*>(old.properties.at(i).first,
+                                                    old.properties.at(i).second ? new string_tree(*old.properties.at(i).second) : NULL));
+  }
   type = old.type ? new type_tree(*old.type) : NULL;
 }
 
@@ -259,29 +293,45 @@ type_tree::type_tree(const type_tree& old) :value(old.value) {
   right = old.right ? new type_tree(*old.right) : NULL;
 }
 
+string_tree::string_tree(const string_tree& old) {  // :value(old.value) {
+  value = old.value;
+  left = old.left ? new string_tree(*old.left) : NULL;
+  right = old.right ? new string_tree(*old.right) : NULL;
+}
+
 reagent& reagent::operator=(const reagent& old) {
   original_string = old.original_string;
-  properties = old.properties;
+  properties.clear();
+  for (long long int i = 0; i < SIZE(old.properties); ++i) {
+    properties.push_back(pair<string, string_tree*>(old.properties.at(i).first, old.properties.at(i).second ? new string_tree(*old.properties.at(i).second) : NULL));
+  }
   name = old.name;
   value = old.value;
   initialized = old.initialized;
-  type = old.type? new type_tree(*old.type) : NULL;
+  type = old.type ? new type_tree(*old.type) : NULL;
   return *this;
 }
 
 reagent::~reagent() {
+  for (long long int i = 0; i < SIZE(properties); ++i) {
+    if (properties.at(i).second) delete properties.at(i).second;
+  }
   delete type;
 }
 type_tree::~type_tree() {
   delete left;
   delete right;
 }
+string_tree::~string_tree() {
+  delete left;
+  delete right;
+}
 
 reagent::reagent() :value(0), initialized(false), type(NULL) {
   // The first property is special, so ensure we always have it.
   // Other properties can be pushed back, but the first must always be
   // assigned to.
-  properties.push_back(pair<string, vector<string> >("", vector<string>()));
+  properties.push_back(pair<string, string_tree*>("", NULL));
 }
 
 string reagent::to_string() const {
@@ -291,26 +341,35 @@ string reagent::to_string() const {
     for (long long int i = 0; i < SIZE(properties); ++i) {
       if (i > 0) out << ", ";
       out << "\"" << properties.at(i).first << "\": ";
-      if (properties.at(i).second.empty()) {
-        out << "\"\"";
-        continue;
-      }
-      if (SIZE(properties.at(i).second) == 1) {
-        out << "\"" << properties.at(i).second.at(0) << "\"";
-        continue;
-      }
-      out << "<";
-      for (long long int j = 0; j < SIZE(properties.at(i).second); ++j) {
-        if (j > 0) out << " : ";
-        out << "\"" << properties.at(i).second.at(j) << "\"";
-      }
-      out << ">";
+      dump_property(properties.at(i).second, out);
     }
     out << "}";
   }
   return out.str();
 }
 
+void dump_property(const string_tree* property, ostringstream& out) {
+  if (!property) {
+    out << "<>";
+    return;
+  }
+  if (!property->left && !property->right) {
+    out << '"' << property->value << '"';
+    return;
+  }
+  out << "<";
+  if (property->left)
+    dump_property(property->left, out);
+  else
+    out << '"' << property->value << '"';
+  out << " : ";
+  if (property->right)
+    dump_property(property->right, out);
+  else
+    out << " : <>";
+  out << ">";
+}
+
 string dump_types(const reagent& x) {
   ostringstream out;
   dump_types(x.type, out);
@@ -371,12 +430,12 @@ bool has_property(reagent x, string name) {
   return false;
 }
 
-vector<string> property(const reagent& r, const string& name) {
+string_tree* property(const reagent& r, const string& name) {
   for (long long int p = /*skip name:type*/1; p != SIZE(r.properties); ++p) {
     if (r.properties.at(p).first == name)
       return r.properties.at(p).second;
   }
-  return vector<string>();
+  return NULL;
 }
 
 void dump_memory() {
@@ -394,6 +453,12 @@ void dump_recipe(const string& recipe_name) {
   cout << "]\n";
 }
 
+void skip_whitespace(istream& in) {
+  while (!in.eof() && isspace(in.peek()) && in.peek() != '\n') {
+    in.get();
+  }
+}
+
 :(before "End Types")
 struct no_scientific {
   double x;
diff --git a/011load.cc b/011load.cc
index 19756fc1..620d6f40 100644
--- a/011load.cc
+++ b/011load.cc
@@ -186,12 +186,6 @@ void slurp_word(istream& in, ostream& out) {
   }
 }
 
-void skip_whitespace(istream& in) {
-  while (!in.eof() && isspace(in.peek()) && in.peek() != '\n') {
-    in.get();
-  }
-}
-
 void skip_whitespace_and_comments(istream& in) {
   while (true) {
     if (in.eof()) break;
@@ -246,6 +240,7 @@ for (long long int i = 0; i < SIZE(recently_added_recipes); ++i) {
 // Clear Other State For recently_added_recipes
 recently_added_recipes.clear();
 
+:(code)
 :(scenario parse_comment_outside_recipe)
 # this comment will be dropped by the tangler, so we need a dummy recipe to stop that
 recipe f1 [ ]
@@ -350,7 +345,7 @@ recipe main [
 recipe main [
   1:number:address/lookup <- copy 23
 ]
-+parse:   product: {"1": <"number" : "address">, "lookup": ""}
++parse:   product: {"1": <"number" : "address">, "lookup": <>}
 
 //: this test we can't represent with a scenario
 :(code)
diff --git a/013literal_string.cc b/013literal_string.cc
index 9f5d003e..53654e5b 100644
--- a/013literal_string.cc
+++ b/013literal_string.cc
@@ -112,8 +112,7 @@ if (s.at(0) == '[') {
   strip_last(s);
   name = s;
   type = new type_tree(0);
-  properties.push_back(pair<string, vector<string> >(name, vector<string>()));
-  properties.back().second.push_back("literal-string");
+  properties.push_back(pair<string, string_tree*>(name, new string_tree("literal-string")));
   return;
 }
 
@@ -126,7 +125,7 @@ if (s.at(0) == '[') {
 
 :(code)
 bool is_literal_string(const reagent& x) {
-  return !x.properties.at(0).second.empty() && x.properties.at(0).second.at(0) == "literal-string";
+  return x.properties.at(0).second && x.properties.at(0).second->value == "literal-string";
 }
 
 string emit_literal_string(string name) {
@@ -172,7 +171,7 @@ recipe main [
 ]
 +parse: instruction: copy
 +parse:   ingredient: {"abc": "literal-string"}
-+parse:   product: {"1": <"address" : "array" : "character">}
++parse:   product: {"1": <"address" : <"array" : "character">>}
 # no other ingredients
 $parse: 3
 
diff --git a/014literal_noninteger.cc b/014literal_noninteger.cc
index 17a3e895..497b4676 100644
--- a/014literal_noninteger.cc
+++ b/014literal_noninteger.cc
@@ -11,8 +11,7 @@ recipe main [
 if (is_noninteger(s)) {
   name = s;
   type = new type_tree(0);
-  properties.push_back(pair<string, vector<string> >(name, vector<string>()));
-  properties.back().second.push_back("literal-number");
+  properties.push_back(pair<string, string_tree*>(name, new string_tree("literal-number")));
   set_value(to_double(s));
   return;
 }
diff --git a/021check_instruction.cc b/021check_instruction.cc
index e51424f2..2fdaad60 100644
--- a/021check_instruction.cc
+++ b/021check_instruction.cc
@@ -111,8 +111,8 @@ bool is_mu_address(reagent r) {
 bool is_mu_number(reagent r) {
   if (!r.type) return false;
   if (is_literal(r))
-    return r.properties.at(0).second.at(0) == "literal-number"
-        || r.properties.at(0).second.at(0) == "literal";
+    return r.properties.at(0).second->value == "literal-number"
+        || r.properties.at(0).second->value == "literal";
   if (r.type->value == Type_ordinal["character"]) return true;  // permit arithmetic on unicode code points
   return r.type->value == Type_ordinal["number"];
 }
@@ -120,7 +120,7 @@ bool is_mu_number(reagent r) {
 bool is_mu_scalar(reagent r) {
   if (!r.type) return false;
   if (is_literal(r))
-    return r.properties.at(0).second.empty() || r.properties.at(0).second.at(0) != "literal-string";
+    return !r.properties.at(0).second || r.properties.at(0).second->value != "literal-string";
   if (is_mu_array(r)) return false;
   return size_of(r) == 1;
 }
diff --git a/031address.cc b/031address.cc
index 7be3b54d..27d7fb43 100644
--- a/031address.cc
+++ b/031address.cc
@@ -95,7 +95,7 @@ void drop_address_from_type(reagent& r) {
 }
 
 void drop_one_lookup(reagent& r) {
-  for (vector<pair<string, vector<string> > >::iterator p = r.properties.begin(); p != r.properties.end(); ++p) {
+  for (vector<pair<string, string_tree*> >::iterator p = r.properties.begin(); p != r.properties.end(); ++p) {
     if (p->first == "lookup") {
       r.properties.erase(p);
       return;
@@ -161,7 +161,7 @@ recipe main [
 {
   while (!name.empty() && name.at(0) == '*') {
     name.erase(0, 1);
-    properties.push_back(pair<string, vector<string> >("lookup", vector<string>()));
+    properties.push_back(pair<string, string_tree*>("lookup", NULL));
   }
   if (name.empty())
     raise_error << "illegal name " << original_string << '\n' << end();
diff --git a/032array.cc b/032array.cc
index 9a8a0115..b544df38 100644
--- a/032array.cc
+++ b/032array.cc
@@ -34,12 +34,12 @@ case CREATE_ARRAY: {
     break;
   }
   // 'create-array' will need to check properties rather than types
-  if (SIZE(product.properties.at(0).second) <= 2) {
+  if (!product.properties.at(0).second || !product.properties.at(0).second->right || !product.properties.at(0).second->right->right) {
     raise_error << maybe(Recipe[r].name) << "create array of what size? " << inst.to_string() << '\n' << end();
     break;
   }
-  if (!is_integer(product.properties.at(0).second.at(2))) {
-    raise_error << maybe(Recipe[r].name) << "'create-array' product should specify size of array after its element type, but got " << product.properties.at(0).second.at(2) << '\n' << end();
+  if (!is_integer(product.properties.at(0).second->right->right->value)) {
+    raise_error << maybe(Recipe[r].name) << "'create-array' product should specify size of array after its element type, but got " << product.properties.at(0).second->right->right->value << '\n' << end();
     break;
   }
   break;
@@ -49,7 +49,7 @@ case CREATE_ARRAY: {
   reagent product = current_instruction().products.at(0);
   canonize(product);
   long long int base_address = product.value;
-  long long int array_size= to_integer(product.properties.at(0).second.at(2));
+  long long int array_size = to_integer(product.properties.at(0).second->right->right->value);
   // initialize array size, so that size_of will work
   Memory[base_address] = array_size;  // in array elements
   long long int size = size_of(product);  // in locations
diff --git a/036call_reply.cc b/036call_reply.cc
index e2e34c06..98a1eb33 100644
--- a/036call_reply.cc
+++ b/036call_reply.cc
@@ -53,12 +53,12 @@ case REPLY: {
   for (long long int i = 0; i < SIZE(caller_instruction.products); ++i) {
     trace(Primitive_recipe_depth, "run") << "result " << i << " is " << to_string(ingredients.at(i)) << end();
     if (has_property(reply_inst.ingredients.at(i), "same-as-ingredient")) {
-      vector<string> tmp = property(reply_inst.ingredients.at(i), "same-as-ingredient");
-      if (SIZE(tmp) != 1) {
+      string_tree* tmp = property(reply_inst.ingredients.at(i), "same-as-ingredient");
+      if (!tmp || tmp->right) {
         raise_error << maybe(current_recipe_name()) << "'same-as-ingredient' metadata should take exactly one value in " << reply_inst.to_string() << '\n' << end();
         goto finish_reply;
       }
-      long long int ingredient_index = to_integer(tmp.at(0));
+      long long int ingredient_index = to_integer(tmp->value);
       if (ingredient_index >= SIZE(caller_instruction.ingredients))
         raise_error << maybe(current_recipe_name()) << "'same-as-ingredient' metadata overflows ingredients in: " << caller_instruction.to_string() << '\n' << end();
       if (!is_dummy(caller_instruction.products.at(i)) && caller_instruction.products.at(i).value != caller_instruction.ingredients.at(ingredient_index).value)
diff --git a/037recipe.cc b/037recipe.cc
index 23cfb5b2..b3f4d90f 100644
--- a/037recipe.cc
+++ b/037recipe.cc
@@ -31,7 +31,7 @@ type_ordinal recipe_ordinal = Type_ordinal["recipe-ordinal"] = Next_type_ordinal
 Type[recipe_ordinal].name = "recipe-ordinal";
 
 :(before "End Reagent-parsing Exceptions")
-if (!r.properties.at(0).second.empty() && r.properties.at(0).second.at(0) == "recipe") {
+if (r.properties.at(0).second && r.properties.at(0).second->value == "recipe") {
   r.set_value(Recipe_ordinal[r.name]);
   return;
 }
diff --git a/043new.cc b/043new.cc
index d33d685a..34ccf18b 100644
--- a/043new.cc
+++ b/043new.cc
@@ -319,8 +319,8 @@ recipe main [
 :(before "End NEW Transform Special-cases")
   if (!inst.ingredients.empty()
       && !inst.ingredients.at(0).properties.empty()
-      && !inst.ingredients.at(0).properties.at(0).second.empty()
-      && inst.ingredients.at(0).properties.at(0).second.at(0) == "literal-string") {
+      && inst.ingredients.at(0).properties.at(0).second
+      && inst.ingredients.at(0).properties.at(0).second->value == "literal-string") {
     // skip transform
     inst.ingredients.at(0).initialized = true;
     goto end_new_transform;
@@ -437,5 +437,5 @@ string read_mu_string(long long int address) {
 }
 
 bool is_mu_type_literal(reagent r) {
-  return is_literal(r) && !r.properties.empty() && !r.properties.at(0).second.empty() && r.properties.at(0).second.at(0) == "type";
+  return is_literal(r) && !r.properties.empty() && r.properties.at(0).second && r.properties.at(0).second->value == "type";
 }
diff --git a/044space.cc b/044space.cc
index 53d9b802..598c8847 100644
--- a/044space.cc
+++ b/044space.cc
@@ -56,7 +56,7 @@ void absolutize(reagent& x) {
     raise_error << current_instruction().to_string() << ": reagent not initialized: " << x.original_string << '\n' << end();
   }
   x.set_value(address(x.value, space_base(x)));
-  x.properties.push_back(pair<string, vector<string> >("raw", vector<string>()));
+  x.properties.push_back(pair<string, string_tree*>("raw", NULL));
   assert(is_raw(x));
 }
 
@@ -77,7 +77,7 @@ recipe main [
 +mem: storing 35 in location 9
 
 :(after "reagent tmp" following "case GET:")
-tmp.properties.push_back(pair<string, vector<string> >("raw", vector<string>()));
+tmp.properties.push_back(pair<string, string_tree*>("raw", NULL));
 
 //:: fix 'index'
 
@@ -97,7 +97,7 @@ recipe main [
 +mem: storing 35 in location 9
 
 :(after "reagent tmp" following "case INDEX:")
-tmp.properties.push_back(pair<string, vector<string> >("raw", vector<string>()));
+tmp.properties.push_back(pair<string, string_tree*>("raw", NULL));
 
 //:: convenience operation to automatically deduce the amount of space to
 //:: allocate in a default space with names
diff --git a/045space_surround.cc b/045space_surround.cc
index a29ad67c..b55c5e04 100644
--- a/045space_surround.cc
+++ b/045space_surround.cc
@@ -41,9 +41,9 @@ long long int space_base(const reagent& x, long long int space_index, long long
 long long int space_index(const reagent& x) {
   for (long long int i = /*skip name:type*/1; i < SIZE(x.properties); ++i) {
     if (x.properties.at(i).first == "space") {
-      if (SIZE(x.properties.at(i).second) != 1)
+      if (!x.properties.at(i).second || x.properties.at(i).second->right)
         raise_error << maybe(current_recipe_name()) << "/space metadata should take exactly one value in " << x.original_string << '\n' << end();
-      return to_integer(x.properties.at(i).second.at(0));
+      return to_integer(x.properties.at(i).second->value);
     }
   }
   return 0;
diff --git a/046closure_name.cc b/046closure_name.cc
index 80ef44e8..69847fd5 100644
--- a/046closure_name.cc
+++ b/046closure_name.cc
@@ -57,13 +57,13 @@ void collect_surrounding_spaces(const recipe_ordinal r) {
         raise_error << "slot 0 should always have type address:array:location, but is " << inst.products.at(j).to_string() << '\n' << end();
         continue;
       }
-      vector<string> s = property(inst.products.at(j), "names");
-      if (s.empty()) {
+      string_tree* s = property(inst.products.at(j), "names");
+      if (!s) {
         raise_error << "slot 0 requires a /names property in recipe " << Recipe[r].name << end();
         continue;
       }
-      if (SIZE(s) > 1) raise_error << "slot 0 should have a single value in /names, but got " << inst.products.at(j).to_string() << '\n' << end();
-      string surrounding_recipe_name = s.at(0);
+      if (s->right) raise_error << "slot 0 should have a single value in /names, but got " << inst.products.at(j).to_string() << '\n' << end();
+      const string& surrounding_recipe_name = s->value;
       if (Surrounding_space.find(r) != Surrounding_space.end()
           && Surrounding_space[r] != Recipe_ordinal[surrounding_recipe_name]) {
         raise_error << "recipe " << Recipe[r].name << " can have only one 'surrounding' recipe but has " << Recipe[Surrounding_space[r]].name << " and " << surrounding_recipe_name << '\n' << end();
@@ -84,9 +84,9 @@ long long int lookup_name(const reagent& x, const recipe_ordinal default_recipe)
     if (Name[default_recipe].empty()) raise_error << "name not found: " << x.name << '\n' << end();
     return Name[default_recipe][x.name];
   }
-  vector<string> p = property(x, "space");
-  if (SIZE(p) != 1) raise_error << "/space property should have exactly one (non-negative integer) value\n" << end();
-  long long int n = to_integer(p.at(0));
+  string_tree* p = property(x, "space");
+  if (!p || p->right) raise_error << "/space property should have exactly one (non-negative integer) value\n" << end();
+  long long int n = to_integer(p->value);
   assert(n >= 0);
   recipe_ordinal surrounding_recipe = lookup_surrounding_recipe(default_recipe, n);
   set<recipe_ordinal> done;
@@ -127,12 +127,12 @@ recipe_ordinal lookup_surrounding_recipe(const recipe_ordinal r, long long int n
 :(replace{} "bool already_transformed(const reagent& r, const map<string, long long int>& names)")
 bool already_transformed(const reagent& r, const map<string, long long int>& names) {
   if (has_property(r, "space")) {
-    vector<string> p = property(r, "space");
-    if (SIZE(p) != 1) {
+    string_tree* p = property(r, "space");
+    if (!p || p->right) {
       raise_error << "/space property should have exactly one (non-negative integer) value in " << r.original_string << '\n' << end();
       return false;
     }
-    if (p.at(0) != "0") return true;
+    if (p->value != "0") return true;
   }
   return names.find(r.name) != names.end();
 }
diff --git a/047global.cc b/047global.cc
index 496a6f3e..b080b90d 100644
--- a/047global.cc
+++ b/047global.cc
@@ -77,7 +77,7 @@ $error: 0
 bool is_global(const reagent& x) {
   for (long long int i = /*skip name:type*/1; i < SIZE(x.properties); ++i) {
     if (x.properties.at(i).first == "space")
-      return !x.properties.at(i).second.empty() && x.properties.at(i).second.at(0) == "global";
+      return x.properties.at(i).second && x.properties.at(i).second->value == "global";
   }
   return false;
 }
diff --git a/048check_type_by_name.cc b/048check_type_by_name.cc
index 7e5c478b..e6bedbc9 100644
--- a/048check_type_by_name.cc
+++ b/048check_type_by_name.cc
@@ -56,8 +56,8 @@ void deduce_missing_type(map<string, type_tree*>& metadata, reagent& x) {
   if (x.type) return;
   if (metadata.find(x.name) == metadata.end()) return;
   x.type = new type_tree(*metadata[x.name]);
-  assert(x.properties.at(0).second.empty());
-  x.properties.at(0).second.push_back("as-before");
+  assert(!x.properties.at(0).second);
+  x.properties.at(0).second = new string_tree("as-before");
 }
 
 :(scenario transform_fills_in_missing_types_in_product)
diff --git a/050scenario.cc b/050scenario.cc
index bf3b75e7..c2730274 100644
--- a/050scenario.cc
+++ b/050scenario.cc
@@ -305,7 +305,7 @@ void check_memory(const string& s) {
 
 void check_type(const string& lhs, istream& in) {
   reagent x(lhs);
-  if (x.properties.at(0).second.at(0) == "string") {
+  if (x.properties.at(0).second->value == "string") {
     x.set_value(to_integer(x.name));
     skip_whitespace_and_comments(in);
     string _assign = next_word(in);
diff --git a/052tangle.cc b/052tangle.cc
index 08c5f790..2c393a31 100644
--- a/052tangle.cc
+++ b/052tangle.cc
@@ -119,7 +119,7 @@ void append_fragment(vector<instruction>& base, const vector<instruction>& patch
     for (long long int j = 0; j < SIZE(inst.ingredients); ++j) {
       reagent& x = inst.ingredients.at(j);
       if (!is_literal(x)) continue;
-      if (x.properties.at(0).second.at(0) == "label" && jump_targets.find(x.name) != jump_targets.end())
+      if (x.properties.at(0).second->value == "label" && jump_targets.find(x.name) != jump_targets.end())
         x.name = prefix+x.name;
     }
     base.push_back(inst);
diff --git a/053continuation.cc b/053continuation.cc
index a66e5401..080eb736 100644
--- a/053continuation.cc
+++ b/053continuation.cc
@@ -258,8 +258,8 @@ call_stack::iterator find_reset(call_stack& c) {
 //: overload 'call' for continuations
 :(after "Begin Call")
   if (!current_instruction().ingredients.at(0).properties.empty()
-      && !current_instruction().ingredients.at(0).properties.at(0).second.empty()
-      && current_instruction().ingredients.at(0).properties.at(0).second.at(0) == "continuation") {
+      && current_instruction().ingredients.at(0).properties.at(0).second
+      && current_instruction().ingredients.at(0).properties.at(0).second->value == "continuation") {
     // copy multiple calls on to current call stack
     assert(scalar(ingredients.at(0)));
     if (Delimited_continuation.find(ingredients.at(0).at(0)) == Delimited_continuation.end()) {
diff --git a/054dilated_reagent.cc b/054dilated_reagent.cc
index af7e5820..70217ee4 100644
--- a/054dilated_reagent.cc
+++ b/054dilated_reagent.cc
@@ -80,13 +80,11 @@ if (s.at(0) == '{') {
     string key = next_dilated_word(in);
     if (key.empty()) continue;
     string value = next_dilated_word(in);
-    vector<string> values;
-    values.push_back(value);
-    properties.push_back(pair<string, vector<string> >(key, values));
+    properties.push_back(pair<string, string_tree*>(key, new string_tree(value)));
   }
   // structures for the first row of properties
   name = properties.at(0).first;
-  string type_name = properties.at(0).second.at(0);
+  string type_name = properties.at(0).second->value;
   if (Type_ordinal.find(type_name) == Type_ordinal.end()) {
       // this type can't be an integer
     Type_ordinal[type_name] = Next_type_ordinal++;