diff options
author | Kartik K. Agaram <vc@akkartik.com> | 2015-10-28 18:19:41 -0700 |
---|---|---|
committer | Kartik K. Agaram <vc@akkartik.com> | 2015-10-28 18:26:05 -0700 |
commit | 70f70118f468b51ac14b7e992b0ec941c3a50d4d (patch) | |
tree | 07749a54af09d9bfbeb93b7b52dc4a175ed6a886 | |
parent | b69daf785df8dee56f851ce9d6dd38d7779a04ca (diff) | |
download | mu-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.cc | 2 | ||||
-rw-r--r-- | 011load.cc | 15 | ||||
-rw-r--r-- | 021check_instruction.cc | 2 | ||||
-rw-r--r-- | 036call_reply.cc | 2 | ||||
-rw-r--r-- | 044space.cc | 4 | ||||
-rw-r--r-- | 052tangle.cc | 6 | ||||
-rw-r--r-- | 056recipe_header.cc | 111 | ||||
-rw-r--r-- | 072scenario_screen.cc | 2 | ||||
-rw-r--r-- | 075scenario_console.cc | 3 | ||||
-rw-r--r-- | mu.vim | 1 |
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 |