about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2015-10-28 18:19:41 -0700
committerKartik K. Agaram <vc@akkartik.com>2015-10-28 18:26:05 -0700
commit70f70118f468b51ac14b7e992b0ec941c3a50d4d (patch)
tree07749a54af09d9bfbeb93b7b52dc4a175ed6a886
parentb69daf785df8dee56f851ce9d6dd38d7779a04ca (diff)
downloadmu-70f70118f468b51ac14b7e992b0ec941c3a50d4d.tar.gz
2306 - recipe headers
Once a student has gotten used to recipes and ingredients using the
staged 'next-ingredient' approach there's no reason to avoid
conventional function headers. As an added bonus we can now:

a) check that all 'reply' instructions in a recipe are consistent
b) deduce what to reply without needing to say so everytime
c) start thinking about type parameters for recipes (generic functions!)
-rw-r--r--010vm.cc2
-rw-r--r--011load.cc15
-rw-r--r--021check_instruction.cc2
-rw-r--r--036call_reply.cc2
-rw-r--r--044space.cc4
-rw-r--r--052tangle.cc6
-rw-r--r--056recipe_header.cc111
-rw-r--r--072scenario_screen.cc2
-rw-r--r--075scenario_console.cc3
-rw-r--r--mu.vim1
10 files changed, 132 insertions, 16 deletions
diff --git a/010vm.cc b/010vm.cc
index 47a2d01c..08da15a3 100644
--- a/010vm.cc
+++ b/010vm.cc
@@ -38,6 +38,7 @@ struct instruction {
   // End instruction Fields
   instruction();
   void clear();
+  bool is_clear();
   string to_string() const;
 };
 
@@ -222,6 +223,7 @@ instruction::instruction() :is_label(false), operation(IDLE) {
   // End instruction Constructor
 }
 void instruction::clear() { is_label=false; label.clear(); operation=IDLE; ingredients.clear(); products.clear(); }
+bool instruction::is_clear() { return !is_label && operation == IDLE; }
 
 // Reagents have the form <name>:<type>:<type>:.../<property>/<property>/...
 reagent::reagent(string s) :original_string(s), value(0), initialized(false), type(NULL) {
diff --git a/011load.cc b/011load.cc
index 7c7bded0..3e1766f9 100644
--- a/011load.cc
+++ b/011load.cc
@@ -52,27 +52,26 @@ long long int slurp_recipe(istream& in) {
       raise << "redefining recipe " << Recipe[Recipe_ordinal[recipe_name]].name << "\n" << end();
     Recipe.erase(Recipe_ordinal[recipe_name]);
   }
-  // todo: save user-defined recipes to mu's memory
-  Recipe[Recipe_ordinal[recipe_name]] = slurp_body(in);
-  Recipe[Recipe_ordinal[recipe_name]].name = recipe_name;
+  recipe& result = Recipe[Recipe_ordinal[recipe_name]];
+  result.name = recipe_name;
+  slurp_body(in, result);
   // track added recipes because we may need to undo them in tests; see below
   recently_added_recipes.push_back(Recipe_ordinal[recipe_name]);
   return Recipe_ordinal[recipe_name];
 }
 
-recipe slurp_body(istream& in) {
+void slurp_body(istream& in, recipe& result) {
   in >> std::noskipws;
-  recipe result;
   skip_whitespace(in);
   if (in.get() != '[')
     raise_error << "recipe body must begin with '['\n" << end();
   skip_whitespace_and_comments(in);
   instruction curr;
   while (next_instruction(in, &curr)) {
-    // End Rewrite Instruction(curr)
-    result.steps.push_back(curr);
+    // End Rewrite Instruction(curr, recipe result)
+    if (!curr.is_clear())
+      result.steps.push_back(curr);
   }
-  return result;
 }
 
 bool next_instruction(istream& in, instruction* curr) {
diff --git a/021check_instruction.cc b/021check_instruction.cc
index afece2bf..6df7a2d1 100644
--- a/021check_instruction.cc
+++ b/021check_instruction.cc
@@ -88,7 +88,7 @@ bool types_match(reagent lhs, reagent rhs) {
 // (trees perform the same check recursively on each subtree)
 bool types_match(type_tree* lhs, type_tree* rhs) {
   if (!lhs) return true;
-  if (rhs->value == 0) {
+  if (!rhs || rhs->value == 0) {
     if (lhs->value == Type_ordinal["array"]) return false;
     if (lhs->value == Type_ordinal["address"]) return false;
     return size_of(rhs) == size_of(lhs);
diff --git a/036call_reply.cc b/036call_reply.cc
index 8a4653ca..4f96a38b 100644
--- a/036call_reply.cc
+++ b/036call_reply.cc
@@ -165,7 +165,7 @@ recipe test1 [
 ]
 +mem: storing 34 in location 1
 
-:(before "End Rewrite Instruction(curr)")
+:(before "End Rewrite Instruction(curr, recipe result)")
 // rewrite `reply-if a, b, c, ...` to
 //   ```
 //   jump-unless a, 1:offset
diff --git a/044space.cc b/044space.cc
index ed074336..56b494d5 100644
--- a/044space.cc
+++ b/044space.cc
@@ -117,7 +117,7 @@ if (x.name == "number-of-locals")
 :(before "End is_special_name Cases")
 if (s == "number-of-locals") return true;
 
-:(before "End Rewrite Instruction(curr)")
+:(before "End Rewrite Instruction(curr, recipe result)")
 // rewrite `new-default-space` to
 //   `default-space:address:array:location <- new location:type, number-of-locals:literal`
 // where N is Name[recipe][""]
@@ -162,7 +162,7 @@ try_reclaim_locals();
 
 //: now 'local-scope' is identical to 'new-default-space' except that we'll
 //: reclaim the default-space when the routine exits
-:(before "End Rewrite Instruction(curr)")
+:(before "End Rewrite Instruction(curr, recipe result)")
 if (curr.name == "local-scope") {
   rewrite_default_space_instruction(curr);
 }
diff --git a/052tangle.cc b/052tangle.cc
index 2c393a31..7995e501 100644
--- a/052tangle.cc
+++ b/052tangle.cc
@@ -35,7 +35,8 @@ Fragments_used.clear();
 :(before "End Command Handlers")
 else if (command == "before") {
   string label = next_word(in);
-  recipe tmp = slurp_body(in);
+  recipe tmp;
+  slurp_body(in, tmp);
   if (is_waypoint(label))
     Before_fragments[label].steps.insert(Before_fragments[label].steps.end(), tmp.steps.begin(), tmp.steps.end());
   else
@@ -43,7 +44,8 @@ else if (command == "before") {
 }
 else if (command == "after") {
   string label = next_word(in);
-  recipe tmp = slurp_body(in);
+  recipe tmp;
+  slurp_body(in, tmp);
   if (is_waypoint(label))
     After_fragments[label].steps.insert(After_fragments[label].steps.begin(), tmp.steps.begin(), tmp.steps.end());
   else
diff --git a/056recipe_header.cc b/056recipe_header.cc
new file mode 100644
index 00000000..37855908
--- /dev/null
+++ b/056recipe_header.cc
@@ -0,0 +1,111 @@
+//: Advanced notation for the common/easy case where a recipe takes some fixed
+//: number of ingredients and yields some fixed number of products.
+
+:(scenario recipe_with_header)
+recipe main [
+  1:number/raw <- add2 3, 5
+]
+recipe add2 x:number, y:number -> z:number [
+  local-scope
+  load-ingredients
+  z:number <- add x, y
+  reply z
+]
++mem: storing 8 in location 1
+
+//: When loading recipes save any header.
+
+:(before "End recipe Fields")
+vector<reagent> ingredients;
+vector<reagent> products;
+
+:(before "slurp_body(in, result);" following "long long int slurp_recipe(istream& in)")
+skip_whitespace(in);
+if (in.peek() != '[') {
+  load_recipe_header(in, result);
+}
+
+:(code)
+void load_recipe_header(istream& in, recipe& result) {
+  while (in.peek() != '[') {
+    string s = next_word(in);
+    if (s == "->") break;
+    result.ingredients.push_back(reagent(s));
+    skip_whitespace(in);
+  }
+  while (in.peek() != '[') {
+    string s = next_word(in);
+    result.products.push_back(reagent(s));
+    skip_whitespace(in);
+  }
+}
+
+//: Now rewrite 'load-ingredients' to instructions to create all reagents in
+//: the header.
+
+:(before "End Rewrite Instruction(curr, recipe result)")
+if (curr.name == "load-ingredients") {
+  curr.clear();
+  for (long long int i = 0; i < SIZE(result.ingredients); ++i) {
+    curr.operation = Recipe_ordinal["next-ingredient"];
+    curr.name = "next-ingredient";
+    curr.products.push_back(result.ingredients.at(i));
+    result.steps.push_back(curr);
+    curr.clear();
+  }
+}
+
+:(scenarios transform)
+:(scenario recipe_headers_are_checked)
+% Hide_warnings = true;
+recipe add2 x:number, y:number -> z:number [
+  local-scope
+  load-ingredients
+  z:address:number <- copy 0/raw
+  reply z
+]
++warn: add2: replied with the wrong type at 'reply z'
+
+:(before "End One-time Setup")
+  Transform.push_back(check_header_products);
+
+:(code)
+void check_header_products(const recipe_ordinal r) {
+  const recipe& rr = Recipe[r];
+  if (rr.products.empty()) return;
+  for (long long int i = 0; i < SIZE(rr.steps); ++i) {
+    const instruction& inst = rr.steps.at(i);
+    if (inst.operation != REPLY) continue;
+    if (SIZE(rr.products) != SIZE(inst.ingredients)) {
+      raise << maybe(rr.name) << "tried to reply the wrong number of products in '" << inst.to_string() << "'\n" << end();
+    }
+    for (long long int i = 0; i < SIZE(rr.products); ++i) {
+      if (!types_match(rr.products.at(i), inst.ingredients.at(i))) {
+        raise << maybe(rr.name) << "replied with the wrong type at '" << inst.to_string() << "'\n" << end();
+      }
+    }
+  }
+}
+
+//: One final convenience: no need to say what to return if the information is
+//: in the header.
+
+:(scenarios run)
+:(scenario reply_based_on_header)
+recipe main [
+  1:number/raw <- add2 3, 5
+]
+recipe add2 x:number, y:number -> z:number [
+  local-scope
+  load-ingredients
+  z:number <- add x, y
+  reply
+]
++mem: storing 8 in location 1
+
+:(before "End Rewrite Instruction(curr, recipe result)")
+if (curr.name == "reply" && curr.ingredients.empty()) {
+  for (long long int i = 0; i < SIZE(result.products); ++i) {
+    curr.ingredients.push_back(result.products.at(i));
+  }
+}
diff --git a/072scenario_screen.cc b/072scenario_screen.cc
index f9536bc5..11151eac 100644
--- a/072scenario_screen.cc
+++ b/072scenario_screen.cc
@@ -137,7 +137,7 @@ const long long int SCREEN = Next_predefined_global_for_scenarios++;
 :(before "End Special Scenario Variable Names(r)")
 Name[r]["screen"] = SCREEN;
 
-:(before "End Rewrite Instruction(curr)")
+:(before "End Rewrite Instruction(curr, recipe result)")
 // rewrite `assume-screen width, height` to
 // `screen:address:screen <- new-fake-screen width, height`
 if (curr.name == "assume-screen") {
diff --git a/075scenario_console.cc b/075scenario_console.cc
index f8e3e1a1..47cb5f29 100644
--- a/075scenario_console.cc
+++ b/075scenario_console.cc
@@ -50,7 +50,8 @@ case ASSUME_CONSOLE: {
 case ASSUME_CONSOLE: {
   // create a temporary recipe just for parsing; it won't contain valid instructions
   istringstream in("[" + current_instruction().ingredients.at(0).name + "]");
-  recipe r = slurp_body(in);
+  recipe r;
+  slurp_body(in, r);
   long long int num_events = count_events(r);
   // initialize the events like in new-fake-console
   long long int size = num_events*size_of_event() + /*space for length*/1;
diff --git a/mu.vim b/mu.vim
index 72255940..bb77b50d 100644
--- a/mu.vim
+++ b/mu.vim
@@ -62,6 +62,7 @@ syntax match muGlobal %[^ ]\+:global/\?[^ ,]*% | highlight link muGlobal Special
 syntax keyword muControl reply reply-if reply-unless jump jump-if jump-unless loop loop-if loop-unless break break-if break-unless current-continuation continue-from create-delimited-continuation reply-delimited-continuation | highlight muControl ctermfg=3
 " common keywords
 syntax keyword muRecipe recipe recipe! before after | highlight muRecipe ctermfg=208
+syntax match muRecipe " -> "
 syntax keyword muScenario scenario | highlight muScenario ctermfg=34
 syntax keyword muData container exclusive-container | highlight muData ctermfg=226