From 7452d0525ecfc12c11fe005c31b089a5b4d7a102 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Thu, 15 Mar 2018 23:29:08 -0700 Subject: 4228 --- html/072recipe.cc.html | 1061 +++++++++++++++++++++++++++--------------------- 1 file changed, 609 insertions(+), 452 deletions(-) (limited to 'html/072recipe.cc.html') diff --git a/html/072recipe.cc.html b/html/072recipe.cc.html index 30bfd731..9717739c 100644 --- a/html/072recipe.cc.html +++ b/html/072recipe.cc.html @@ -64,462 +64,619 @@ if ('onhashchange' in window) { 1 //: So far we've been calling a fixed recipe in each instruction, but we'd 2 //: also like to make the recipe a variable, pass recipes to "higher-order" 3 //: recipes, return recipes from recipes and so on. - 4 //: - 5 //: todo: support storing shape-shifting recipes into recipe variables and calling them - 6 - 7 :(scenario call_literal_recipe) - 8 def main [ - 9 1:num <- call f, 34 - 10 ] - 11 def f x:num -> y:num [ - 12 local-scope - 13 load-ingredients - 14 y <- copy x - 15 ] - 16 +mem: storing 34 in location 1 - 17 - 18 :(before "End Mu Types Initialization") - 19 put(Type_ordinal, "recipe-literal", 0); - 20 // 'recipe' variables can store recipe-literal - 21 type_ordinal recipe = put(Type_ordinal, "recipe", Next_type_ordinal++); - 22 get_or_insert(Type, recipe).name = "recipe"; - 23 - 24 :(after "Deduce Missing Type(x, caller)") - 25 if (!x.type) - 26 try_initialize_recipe_literal(x, caller); - 27 :(before "Type Check in Type-ingredient-aware check_or_set_types_by_name") - 28 if (!x.type) - 29 try_initialize_recipe_literal(x, variant); - 30 :(code) - 31 void try_initialize_recipe_literal(reagent& x, const recipe& caller) { - 32 if (x.type) return; - 33 if (!contains_key(Recipe_ordinal, x.name)) return; - 34 if (contains_reagent_with_non_recipe_literal_type(caller, x.name)) return; - 35 x.type = new type_tree("recipe-literal"); - 36 x.set_value(get(Recipe_ordinal, x.name)); - 37 } - 38 bool contains_reagent_with_non_recipe_literal_type(const recipe& caller, const string& name) { - 39 for (int i = 0; i < SIZE(caller.steps); ++i) { - 40 const instruction& inst = caller.steps.at(i); - 41 for (int i = 0; i < SIZE(inst.ingredients); ++i) - 42 if (is_matching_non_recipe_literal(inst.ingredients.at(i), name)) return true; - 43 for (int i = 0; i < SIZE(inst.products); ++i) - 44 if (is_matching_non_recipe_literal(inst.products.at(i), name)) return true; - 45 } - 46 return false; - 47 } - 48 bool is_matching_non_recipe_literal(const reagent& x, const string& name) { - 49 if (x.name != name) return false; - 50 if (!x.type) return false; - 51 return !x.type->atom || x.type->name != "recipe-literal"; - 52 } - 53 - 54 //: It's confusing to use variable names that are also recipe names. Always - 55 //: assume variable types override recipe literals. - 56 :(scenario error_on_recipe_literal_used_as_a_variable) - 57 % Hide_errors = true; - 58 def main [ - 59 local-scope - 60 a:bool <- equal break 0 - 61 break:bool <- copy 0 - 62 ] - 63 +error: main: missing type for 'break' in 'a:bool <- equal break, 0' - 64 - 65 :(before "End Primitive Recipe Declarations") - 66 CALL, - 67 :(before "End Primitive Recipe Numbers") - 68 put(Recipe_ordinal, "call", CALL); - 69 :(before "End Primitive Recipe Checks") - 70 case CALL: { - 71 if (inst.ingredients.empty()) { - 72 raise << maybe(get(Recipe, r).name) << "'call' requires at least one ingredient (the recipe to call)\n" << end(); - 73 break; - 74 } - 75 if (!is_mu_recipe(inst.ingredients.at(0))) { - 76 raise << maybe(get(Recipe, r).name) << "first ingredient of 'call' should be a recipe, but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); - 77 break; - 78 } - 79 break; - 80 } - 81 :(before "End Primitive Recipe Implementations") - 82 case CALL: { - 83 // Begin Call - 84 if (Trace_stream) { - 85 ++Trace_stream->callstack_depth; - 86 trace("trace") << "indirect 'call': incrementing callstack depth to " << Trace_stream->callstack_depth << end(); - 87 assert(Trace_stream->callstack_depth < 9000); // 9998-101 plus cushion - 88 } - 89 if (!ingredients.at(0).at(0)) { - 90 raise << maybe(current_recipe_name()) << "tried to call empty recipe in '" << to_string(current_instruction()) << "'" << end(); - 91 break; - 92 } - 93 const call& caller_frame = current_call(); - 94 instruction/*copy*/ call_instruction = to_instruction(caller_frame); - 95 call_instruction.operation = ingredients.at(0).at(0); - 96 call_instruction.ingredients.erase(call_instruction.ingredients.begin()); - 97 Current_routine->calls.push_front(call(ingredients.at(0).at(0))); - 98 ingredients.erase(ingredients.begin()); // drop the callee - 99 finish_call_housekeeping(call_instruction, ingredients); -100 // not done with caller -101 write_products = false; -102 fall_through_to_next_instruction = false; -103 break; -104 } -105 -106 :(scenario call_variable) -107 def main [ -108 {1: (recipe number -> number)} <- copy f -109 2:num <- call {1: (recipe number -> number)}, 34 -110 ] -111 def f x:num -> y:num [ -112 local-scope -113 load-ingredients -114 y <- copy x -115 ] -116 +mem: storing 34 in location 2 -117 -118 :(scenario call_literal_recipe_repeatedly) -119 def main [ -120 1:num <- call f, 34 -121 1:num <- call f, 35 -122 ] -123 def f x:num -> y:num [ -124 local-scope -125 load-ingredients -126 y <- copy x -127 ] -128 +mem: storing 34 in location 1 -129 +mem: storing 35 in location 1 -130 -131 :(scenario call_shape_shifting_recipe) -132 def main [ -133 1:num <- call f, 34 -134 ] -135 def f x:_elem -> y:_elem [ -136 local-scope -137 load-ingredients -138 y <- copy x -139 ] -140 +mem: storing 34 in location 1 -141 -142 :(scenario call_shape_shifting_recipe_inside_shape_shifting_recipe) -143 def main [ -144 1:num <- f 34 -145 ] -146 def f x:_elem -> y:_elem [ -147 local-scope -148 load-ingredients -149 y <- call g x -150 ] -151 def g x:_elem -> y:_elem [ -152 local-scope -153 load-ingredients -154 y <- copy x -155 ] -156 +mem: storing 34 in location 1 -157 -158 :(scenario call_shape_shifting_recipe_repeatedly_inside_shape_shifting_recipe) -159 def main [ -160 1:num <- f 34 -161 ] -162 def f x:_elem -> y:_elem [ -163 local-scope -164 load-ingredients -165 y <- call g x -166 y <- call g x -167 ] -168 def g x:_elem -> y:_elem [ -169 local-scope -170 load-ingredients -171 y <- copy x -172 ] -173 +mem: storing 34 in location 1 + 4 + 5 :(scenario call_literal_recipe) + 6 def main [ + 7 1:num <- call f, 34 + 8 ] + 9 def f x:num -> y:num [ + 10 local-scope + 11 load-ingredients + 12 y <- copy x + 13 ] + 14 +mem: storing 34 in location 1 + 15 + 16 :(before "End Mu Types Initialization") + 17 put(Type_ordinal, "recipe-literal", 0); + 18 // 'recipe' variables can store recipe-literal + 19 type_ordinal recipe = put(Type_ordinal, "recipe", Next_type_ordinal++); + 20 get_or_insert(Type, recipe).name = "recipe"; + 21 + 22 :(after "Deduce Missing Type(x, caller)") + 23 if (!x.type) + 24 try_initialize_recipe_literal(x, caller); + 25 :(before "Type Check in Type-ingredient-aware check_or_set_types_by_name") + 26 if (!x.type) + 27 try_initialize_recipe_literal(x, variant); + 28 :(code) + 29 void try_initialize_recipe_literal(reagent& x, const recipe& caller) { + 30 if (x.type) return; + 31 if (!contains_key(Recipe_ordinal, x.name)) return; + 32 if (contains_reagent_with_non_recipe_literal_type(caller, x.name)) return; + 33 x.type = new type_tree("recipe-literal"); + 34 x.set_value(get(Recipe_ordinal, x.name)); + 35 } + 36 bool contains_reagent_with_non_recipe_literal_type(const recipe& caller, const string& name) { + 37 for (int i = 0; i < SIZE(caller.steps); ++i) { + 38 const instruction& inst = caller.steps.at(i); + 39 for (int i = 0; i < SIZE(inst.ingredients); ++i) + 40 if (is_matching_non_recipe_literal(inst.ingredients.at(i), name)) return true; + 41 for (int i = 0; i < SIZE(inst.products); ++i) + 42 if (is_matching_non_recipe_literal(inst.products.at(i), name)) return true; + 43 } + 44 return false; + 45 } + 46 bool is_matching_non_recipe_literal(const reagent& x, const string& name) { + 47 if (x.name != name) return false; + 48 if (!x.type) return false; + 49 return !x.type->atom || x.type->name != "recipe-literal"; + 50 } + 51 + 52 //: It's confusing to use variable names that are also recipe names. Always + 53 //: assume variable types override recipe literals. + 54 :(scenario error_on_recipe_literal_used_as_a_variable) + 55 % Hide_errors = true; + 56 def main [ + 57 local-scope + 58 a:bool <- equal break 0 + 59 break:bool <- copy 0 + 60 ] + 61 +error: main: missing type for 'break' in 'a:bool <- equal break, 0' + 62 + 63 :(before "End Primitive Recipe Declarations") + 64 CALL, + 65 :(before "End Primitive Recipe Numbers") + 66 put(Recipe_ordinal, "call", CALL); + 67 :(before "End Primitive Recipe Checks") + 68 case CALL: { + 69 if (inst.ingredients.empty()) { + 70 raise << maybe(get(Recipe, r).name) << "'call' requires at least one ingredient (the recipe to call)\n" << end(); + 71 break; + 72 } + 73 if (!is_mu_recipe(inst.ingredients.at(0))) { + 74 raise << maybe(get(Recipe, r).name) << "first ingredient of 'call' should be a recipe, but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); + 75 break; + 76 } + 77 break; + 78 } + 79 :(before "End Primitive Recipe Implementations") + 80 case CALL: { + 81 // Begin Call + 82 if (Trace_stream) { + 83 ++Trace_stream->callstack_depth; + 84 trace("trace") << "indirect 'call': incrementing callstack depth to " << Trace_stream->callstack_depth << end(); + 85 assert(Trace_stream->callstack_depth < 9000); // 9998-101 plus cushion + 86 } + 87 if (!ingredients.at(0).at(0)) { + 88 raise << maybe(current_recipe_name()) << "tried to call empty recipe in '" << to_string(current_instruction()) << "'" << end(); + 89 break; + 90 } + 91 const call& caller_frame = current_call(); + 92 instruction/*copy*/ call_instruction = to_instruction(caller_frame); + 93 call_instruction.operation = ingredients.at(0).at(0); + 94 call_instruction.ingredients.erase(call_instruction.ingredients.begin()); + 95 Current_routine->calls.push_front(call(ingredients.at(0).at(0))); + 96 ingredients.erase(ingredients.begin()); // drop the callee + 97 finish_call_housekeeping(call_instruction, ingredients); + 98 // not done with caller + 99 write_products = false; +100 fall_through_to_next_instruction = false; +101 break; +102 } +103 +104 :(scenario call_variable) +105 def main [ +106 {1: (recipe number -> number)} <- copy f +107 2:num <- call {1: (recipe number -> number)}, 34 +108 ] +109 def f x:num -> y:num [ +110 local-scope +111 load-ingredients +112 y <- copy x +113 ] +114 +mem: storing 34 in location 2 +115 +116 :(scenario call_literal_recipe_repeatedly) +117 def main [ +118 1:num <- call f, 34 +119 1:num <- call f, 35 +120 ] +121 def f x:num -> y:num [ +122 local-scope +123 load-ingredients +124 y <- copy x +125 ] +126 +mem: storing 34 in location 1 +127 +mem: storing 35 in location 1 +128 +129 :(scenario call_shape_shifting_recipe) +130 def main [ +131 1:num <- call f, 34 +132 ] +133 def f x:_elem -> y:_elem [ +134 local-scope +135 load-ingredients +136 y <- copy x +137 ] +138 +mem: storing 34 in location 1 +139 +140 :(scenario call_shape_shifting_recipe_inside_shape_shifting_recipe) +141 def main [ +142 1:num <- f 34 +143 ] +144 def f x:_elem -> y:_elem [ +145 local-scope +146 load-ingredients +147 y <- call g x +148 ] +149 def g x:_elem -> y:_elem [ +150 local-scope +151 load-ingredients +152 y <- copy x +153 ] +154 +mem: storing 34 in location 1 +155 +156 :(scenario call_shape_shifting_recipe_repeatedly_inside_shape_shifting_recipe) +157 def main [ +158 1:num <- f 34 +159 ] +160 def f x:_elem -> y:_elem [ +161 local-scope +162 load-ingredients +163 y <- call g x +164 y <- call g x +165 ] +166 def g x:_elem -> y:_elem [ +167 local-scope +168 load-ingredients +169 y <- copy x +170 ] +171 +mem: storing 34 in location 1 +172 +173 //:: check types for 'call' instructions 174 -175 //:: check types for 'call' instructions -176 -177 :(scenario call_check_literal_recipe) -178 % Hide_errors = true; -179 def main [ -180 1:num <- call f, 34 -181 ] -182 def f x:point -> y:point [ -183 local-scope -184 load-ingredients -185 y <- copy x -186 ] -187 +error: main: ingredient 0 has the wrong type at '1:num <- call f, 34' -188 +error: main: product 0 has the wrong type at '1:num <- call f, 34' -189 -190 :(scenario call_check_variable_recipe) -191 % Hide_errors = true; -192 def main [ -193 {1: (recipe point -> point)} <- copy f -194 2:num <- call {1: (recipe point -> point)}, 34 -195 ] -196 def f x:point -> y:point [ -197 local-scope -198 load-ingredients -199 y <- copy x -200 ] -201 +error: main: ingredient 0 has the wrong type at '2:num <- call {1: (recipe point -> point)}, 34' -202 +error: main: product 0 has the wrong type at '2:num <- call {1: (recipe point -> point)}, 34' -203 -204 :(before "End resolve_ambiguous_call(r, index, inst, caller_recipe) Special-cases") -205 if (inst.name == "call" && !inst.ingredients.empty() && is_recipe_literal(inst.ingredients.at(0))) { -206 resolve_indirect_ambiguous_call(r, index, inst, caller_recipe); -207 return; -208 } -209 :(code) -210 bool is_recipe_literal(const reagent& x) { -211 return x.type && x.type->atom && x.type->name == "recipe-literal"; -212 } -213 void resolve_indirect_ambiguous_call(const recipe_ordinal r, int index, instruction& inst, const recipe& caller_recipe) { -214 instruction inst2; -215 inst2.name = inst.ingredients.at(0).name; -216 for (int i = /*skip recipe*/1; i < SIZE(inst.ingredients); ++i) -217 inst2.ingredients.push_back(inst.ingredients.at(i)); -218 for (int i = 0; i < SIZE(inst.products); ++i) -219 inst2.products.push_back(inst.products.at(i)); -220 resolve_ambiguous_call(r, index, inst2, caller_recipe); -221 inst.ingredients.at(0).name = inst2.name; -222 inst.ingredients.at(0).set_value(get(Recipe_ordinal, inst2.name)); -223 } -224 -225 :(after "Transform.push_back(check_instruction)") -226 Transform.push_back(check_indirect_calls_against_header); // idempotent -227 :(code) -228 void check_indirect_calls_against_header(const recipe_ordinal r) { -229 trace(9991, "transform") << "--- type-check 'call' instructions inside recipe " << get(Recipe, r).name << end(); -230 const recipe& caller = get(Recipe, r); -231 for (int i = 0; i < SIZE(caller.steps); ++i) { -232 const instruction& inst = caller.steps.at(i); -233 if (!is_indirect_call(inst.operation)) continue; -234 if (inst.ingredients.empty()) continue; // error raised above -235 const reagent& callee = inst.ingredients.at(0); -236 if (!is_mu_recipe(callee)) continue; // error raised above -237 const recipe callee_header = is_literal(callee) ? get(Recipe, callee.value) : from_reagent(inst.ingredients.at(0)); -238 if (!callee_header.has_header) continue; -239 if (is_indirect_call_with_ingredients(inst.operation)) { -240 for (long int i = /*skip callee*/1; i < min(SIZE(inst.ingredients), SIZE(callee_header.ingredients)+/*skip callee*/1); ++i) { -241 if (!types_coercible(callee_header.ingredients.at(i-/*skip callee*/1), inst.ingredients.at(i))) -242 raise << maybe(caller.name) << "ingredient " << i-/*skip callee*/1 << " has the wrong type at '" << to_original_string(inst) << "'\n" << end(); -243 } -244 } -245 if (is_indirect_call_with_products(inst.operation)) { -246 for (long int i = 0; i < min(SIZE(inst.products), SIZE(callee_header.products)); ++i) { -247 if (is_dummy(inst.products.at(i))) continue; -248 if (!types_coercible(callee_header.products.at(i), inst.products.at(i))) -249 raise << maybe(caller.name) << "product " << i << " has the wrong type at '" << to_original_string(inst) << "'\n" << end(); -250 } -251 } -252 } -253 } -254 -255 bool is_indirect_call(const recipe_ordinal r) { -256 return is_indirect_call_with_ingredients(r) || is_indirect_call_with_products(r); -257 } -258 -259 bool is_indirect_call_with_ingredients(const recipe_ordinal r) { -260 if (r == CALL) return true; -261 // End is_indirect_call_with_ingredients Special-cases -262 return false; -263 } -264 bool is_indirect_call_with_products(const recipe_ordinal r) { -265 if (r == CALL) return true; -266 // End is_indirect_call_with_products Special-cases -267 return false; -268 } -269 -270 recipe from_reagent(const reagent& r) { -271 assert(r.type); -272 recipe result_header; // will contain only ingredients and products, nothing else -273 result_header.has_header = true; -274 // Begin Reagent->Recipe(r, recipe_header) -275 if (r.type->atom) { -276 assert(r.type->name == "recipe"); -277 return result_header; -278 } -279 const type_tree* root_type = r.type->atom ? r.type : r.type->left; -280 assert(root_type->atom); -281 assert(root_type->name == "recipe"); -282 const type_tree* curr = r.type->right; -283 for (/*nada*/; curr && !curr->atom; curr = curr->right) { -284 if (curr->left->atom && curr->left->name == "->") { -285 curr = curr->right; // skip delimiter -286 goto read_products; -287 } -288 result_header.ingredients.push_back(next_recipe_reagent(curr->left)); -289 } -290 if (curr) { -291 assert(curr->atom); -292 result_header.ingredients.push_back(next_recipe_reagent(curr)); -293 return result_header; // no products -294 } -295 read_products: -296 for (/*nada*/; curr && !curr->atom; curr = curr->right) -297 result_header.products.push_back(next_recipe_reagent(curr->left)); -298 if (curr) { -299 assert(curr->atom); -300 result_header.products.push_back(next_recipe_reagent(curr)); -301 } -302 return result_header; -303 } -304 -305 :(before "End Unit Tests") -306 void test_from_reagent_atomic() { -307 reagent a("{f: recipe}"); -308 recipe r_header = from_reagent(a); -309 CHECK(r_header.ingredients.empty()); -310 CHECK(r_header.products.empty()); -311 } -312 void test_from_reagent_non_atomic() { -313 reagent a("{f: (recipe number -> number)}"); -314 recipe r_header = from_reagent(a); -315 CHECK_EQ(SIZE(r_header.ingredients), 1); -316 CHECK_EQ(SIZE(r_header.products), 1); -317 } -318 void test_from_reagent_reads_ingredient_at_end() { -319 reagent a("{f: (recipe number number)}"); -320 recipe r_header = from_reagent(a); -321 CHECK_EQ(SIZE(r_header.ingredients), 2); -322 CHECK(r_header.products.empty()); -323 } -324 void test_from_reagent_reads_sole_ingredient_at_end() { -325 reagent a("{f: (recipe number)}"); -326 recipe r_header = from_reagent(a); -327 CHECK_EQ(SIZE(r_header.ingredients), 1); -328 CHECK(r_header.products.empty()); -329 } -330 -331 :(code) -332 reagent next_recipe_reagent(const type_tree* curr) { -333 if (!curr->left) return reagent("recipe:"+curr->name); -334 reagent result; -335 result.name = "recipe"; -336 result.type = new type_tree(*curr); -337 return result; -338 } -339 -340 bool is_mu_recipe(const reagent& r) { -341 if (!r.type) return false; -342 if (r.type->atom) { -343 // End is_mu_recipe Atom Cases(r) -344 return r.type->name == "recipe-literal"; -345 } -346 return r.type->left->atom && r.type->left->name == "recipe"; -347 } -348 -349 :(scenario copy_typecheck_recipe_variable) -350 % Hide_errors = true; -351 def main [ -352 3:num <- copy 34 # abc def -353 {1: (recipe number -> number)} <- copy f # store literal in a matching variable -354 {2: (recipe boolean -> boolean)} <- copy {1: (recipe number -> number)} # mismatch between recipe variables +175 :(scenario call_check_literal_recipe) +176 % Hide_errors = true; +177 def main [ +178 1:num <- call f, 34 +179 ] +180 def f x:point -> y:point [ +181 local-scope +182 load-ingredients +183 y <- copy x +184 ] +185 +error: main: ingredient 0 has the wrong type at '1:num <- call f, 34' +186 +error: main: product 0 has the wrong type at '1:num <- call f, 34' +187 +188 :(scenario call_check_variable_recipe) +189 % Hide_errors = true; +190 def main [ +191 {1: (recipe point -> point)} <- copy f +192 2:num <- call {1: (recipe point -> point)}, 34 +193 ] +194 def f x:point -> y:point [ +195 local-scope +196 load-ingredients +197 y <- copy x +198 ] +199 +error: main: ingredient 0 has the wrong type at '2:num <- call {1: (recipe point -> point)}, 34' +200 +error: main: product 0 has the wrong type at '2:num <- call {1: (recipe point -> point)}, 34' +201 +202 :(before "End resolve_ambiguous_call(r, index, inst, caller_recipe) Special-cases") +203 if (inst.name == "call" && !inst.ingredients.empty() && is_recipe_literal(inst.ingredients.at(0))) { +204 resolve_indirect_ambiguous_call(r, index, inst, caller_recipe); +205 return; +206 } +207 :(code) +208 bool is_recipe_literal(const reagent& x) { +209 return x.type && x.type->atom && x.type->name == "recipe-literal"; +210 } +211 void resolve_indirect_ambiguous_call(const recipe_ordinal r, int index, instruction& inst, const recipe& caller_recipe) { +212 instruction inst2; +213 inst2.name = inst.ingredients.at(0).name; +214 for (int i = /*skip recipe*/1; i < SIZE(inst.ingredients); ++i) +215 inst2.ingredients.push_back(inst.ingredients.at(i)); +216 for (int i = 0; i < SIZE(inst.products); ++i) +217 inst2.products.push_back(inst.products.at(i)); +218 resolve_ambiguous_call(r, index, inst2, caller_recipe); +219 inst.ingredients.at(0).name = inst2.name; +220 inst.ingredients.at(0).set_value(get(Recipe_ordinal, inst2.name)); +221 } +222 +223 :(after "Transform.push_back(check_instruction)") +224 Transform.push_back(check_indirect_calls_against_header); // idempotent +225 :(code) +226 void check_indirect_calls_against_header(const recipe_ordinal r) { +227 trace(9991, "transform") << "--- type-check 'call' instructions inside recipe " << get(Recipe, r).name << end(); +228 const recipe& caller = get(Recipe, r); +229 for (int i = 0; i < SIZE(caller.steps); ++i) { +230 const instruction& inst = caller.steps.at(i); +231 if (!is_indirect_call(inst.operation)) continue; +232 if (inst.ingredients.empty()) continue; // error raised above +233 const reagent& callee = inst.ingredients.at(0); +234 if (!is_mu_recipe(callee)) continue; // error raised above +235 const recipe callee_header = is_literal(callee) ? get(Recipe, callee.value) : from_reagent(inst.ingredients.at(0)); +236 if (!callee_header.has_header) continue; +237 if (is_indirect_call_with_ingredients(inst.operation)) { +238 for (long int i = /*skip callee*/1; i < min(SIZE(inst.ingredients), SIZE(callee_header.ingredients)+/*skip callee*/1); ++i) { +239 if (!types_coercible(callee_header.ingredients.at(i-/*skip callee*/1), inst.ingredients.at(i))) +240 raise << maybe(caller.name) << "ingredient " << i-/*skip callee*/1 << " has the wrong type at '" << to_original_string(inst) << "'\n" << end(); +241 } +242 } +243 if (is_indirect_call_with_products(inst.operation)) { +244 for (long int i = 0; i < min(SIZE(inst.products), SIZE(callee_header.products)); ++i) { +245 if (is_dummy(inst.products.at(i))) continue; +246 if (!types_coercible(callee_header.products.at(i), inst.products.at(i))) +247 raise << maybe(caller.name) << "product " << i << " has the wrong type at '" << to_original_string(inst) << "'\n" << end(); +248 } +249 } +250 } +251 } +252 +253 bool is_indirect_call(const recipe_ordinal r) { +254 return is_indirect_call_with_ingredients(r) || is_indirect_call_with_products(r); +255 } +256 +257 bool is_indirect_call_with_ingredients(const recipe_ordinal r) { +258 if (r == CALL) return true; +259 // End is_indirect_call_with_ingredients Special-cases +260 return false; +261 } +262 bool is_indirect_call_with_products(const recipe_ordinal r) { +263 if (r == CALL) return true; +264 // End is_indirect_call_with_products Special-cases +265 return false; +266 } +267 +268 recipe from_reagent(const reagent& r) { +269 assert(r.type); +270 recipe result_header; // will contain only ingredients and products, nothing else +271 result_header.has_header = true; +272 // Begin Reagent->Recipe(r, recipe_header) +273 if (r.type->atom) { +274 assert(r.type->name == "recipe"); +275 return result_header; +276 } +277 const type_tree* root_type = r.type->atom ? r.type : r.type->left; +278 assert(root_type->atom); +279 assert(root_type->name == "recipe"); +280 const type_tree* curr = r.type->right; +281 for (/*nada*/; curr && !curr->atom; curr = curr->right) { +282 if (curr->left->atom && curr->left->name == "->") { +283 curr = curr->right; // skip delimiter +284 goto read_products; +285 } +286 result_header.ingredients.push_back(next_recipe_reagent(curr->left)); +287 } +288 if (curr) { +289 assert(curr->atom); +290 result_header.ingredients.push_back(next_recipe_reagent(curr)); +291 return result_header; // no products +292 } +293 read_products: +294 for (/*nada*/; curr && !curr->atom; curr = curr->right) +295 result_header.products.push_back(next_recipe_reagent(curr->left)); +296 if (curr) { +297 assert(curr->atom); +298 result_header.products.push_back(next_recipe_reagent(curr)); +299 } +300 return result_header; +301 } +302 +303 :(before "End Unit Tests") +304 void test_from_reagent_atomic() { +305 reagent a("{f: recipe}"); +306 recipe r_header = from_reagent(a); +307 CHECK(r_header.ingredients.empty()); +308 CHECK(r_header.products.empty()); +309 } +310 void test_from_reagent_non_atomic() { +311 reagent a("{f: (recipe number -> number)}"); +312 recipe r_header = from_reagent(a); +313 CHECK_EQ(SIZE(r_header.ingredients), 1); +314 CHECK_EQ(SIZE(r_header.products), 1); +315 } +316 void test_from_reagent_reads_ingredient_at_end() { +317 reagent a("{f: (recipe number number)}"); +318 recipe r_header = from_reagent(a); +319 CHECK_EQ(SIZE(r_header.ingredients), 2); +320 CHECK(r_header.products.empty()); +321 } +322 void test_from_reagent_reads_sole_ingredient_at_end() { +323 reagent a("{f: (recipe number)}"); +324 recipe r_header = from_reagent(a); +325 CHECK_EQ(SIZE(r_header.ingredients), 1); +326 CHECK(r_header.products.empty()); +327 } +328 +329 :(code) +330 reagent next_recipe_reagent(const type_tree* curr) { +331 if (!curr->left) return reagent("recipe:"+curr->name); +332 return reagent(new type_tree(*curr)); +333 } +334 +335 bool is_mu_recipe(const reagent& r) { +336 if (!r.type) return false; +337 if (r.type->atom) { +338 // End is_mu_recipe Atom Cases(r) +339 return r.type->name == "recipe-literal"; +340 } +341 return r.type->left->atom && r.type->left->name == "recipe"; +342 } +343 +344 :(scenario copy_typecheck_recipe_variable) +345 % Hide_errors = true; +346 def main [ +347 3:num <- copy 34 # abc def +348 {1: (recipe number -> number)} <- copy f # store literal in a matching variable +349 {2: (recipe boolean -> boolean)} <- copy {1: (recipe number -> number)} # mismatch between recipe variables +350 ] +351 def f x:num -> y:num [ +352 local-scope +353 load-ingredients +354 y <- copy x 355 ] -356 def f x:num -> y:num [ -357 local-scope -358 load-ingredients -359 y <- copy x -360 ] -361 +error: main: can't copy '{1: (recipe number -> number)}' to '{2: (recipe boolean -> boolean)}'; types don't match -362 -363 :(scenario copy_typecheck_recipe_variable_2) -364 % Hide_errors = true; -365 def main [ -366 {1: (recipe number -> number)} <- copy f # mismatch with a recipe literal +356 +error: main: can't copy '{1: (recipe number -> number)}' to '{2: (recipe boolean -> boolean)}'; types don't match +357 +358 :(scenario copy_typecheck_recipe_variable_2) +359 % Hide_errors = true; +360 def main [ +361 {1: (recipe number -> number)} <- copy f # mismatch with a recipe literal +362 ] +363 def f x:bool -> y:bool [ +364 local-scope +365 load-ingredients +366 y <- copy x 367 ] -368 def f x:bool -> y:bool [ -369 local-scope -370 load-ingredients -371 y <- copy x -372 ] -373 +error: main: can't copy 'f' to '{1: (recipe number -> number)}'; types don't match -374 -375 :(before "End Matching Types For Literal(to)") -376 if (is_mu_recipe(to)) { -377 if (!contains_key(Recipe, from.value)) { -378 raise << "trying to store recipe " << from.name << " into " << to_string(to) << " but there's no such recipe\n" << end(); -379 return false; -380 } -381 const recipe& rrhs = get(Recipe, from.value); -382 const recipe& rlhs = from_reagent(to); -383 for (long int i = 0; i < min(SIZE(rlhs.ingredients), SIZE(rrhs.ingredients)); ++i) { -384 if (!types_match(rlhs.ingredients.at(i), rrhs.ingredients.at(i))) -385 return false; -386 } -387 for (long int i = 0; i < min(SIZE(rlhs.products), SIZE(rrhs.products)); ++i) { -388 if (!types_match(rlhs.products.at(i), rrhs.products.at(i))) -389 return false; -390 } -391 return true; -392 } -393 -394 :(scenario call_variable_compound_ingredient) -395 def main [ -396 {1: (recipe (address number) -> number)} <- copy f -397 2:&:num <- copy 0 -398 3:num <- call {1: (recipe (address number) -> number)}, 2:&:num +368 +error: main: can't copy 'f' to '{1: (recipe number -> number)}'; types don't match +369 +370 :(before "End Matching Types For Literal(to)") +371 if (is_mu_recipe(to)) { +372 if (!contains_key(Recipe, from.value)) { +373 raise << "trying to store recipe " << from.name << " into " << to_string(to) << " but there's no such recipe\n" << end(); +374 return false; +375 } +376 const recipe& rrhs = get(Recipe, from.value); +377 const recipe& rlhs = from_reagent(to); +378 for (long int i = 0; i < min(SIZE(rlhs.ingredients), SIZE(rrhs.ingredients)); ++i) { +379 if (!types_match(rlhs.ingredients.at(i), rrhs.ingredients.at(i))) +380 return false; +381 } +382 for (long int i = 0; i < min(SIZE(rlhs.products), SIZE(rrhs.products)); ++i) { +383 if (!types_match(rlhs.products.at(i), rrhs.products.at(i))) +384 return false; +385 } +386 return true; +387 } +388 +389 :(scenario call_variable_compound_ingredient) +390 def main [ +391 {1: (recipe (address number) -> number)} <- copy f +392 2:&:num <- copy 0 +393 3:num <- call {1: (recipe (address number) -> number)}, 2:&:num +394 ] +395 def f x:&:num -> y:num [ +396 local-scope +397 load-ingredients +398 y <- copy x 399 ] -400 def f x:&:num -> y:num [ -401 local-scope -402 load-ingredients -403 y <- copy x -404 ] -405 $error: 0 -406 -407 //: make sure we don't accidentally break on a recipe literal -408 :(scenario jump_forbidden_on_recipe_literals) -409 % Hide_errors = true; -410 def foo [ -411 local-scope -412 ] -413 def main [ -414 local-scope -415 { -416 break-if foo -417 } -418 ] -419 # error should be as if foo is not a recipe -420 +error: main: missing type for 'foo' in 'break-if foo' -421 -422 :(before "End JUMP_IF Checks") -423 check_for_recipe_literals(inst, get(Recipe, r)); -424 :(before "End JUMP_UNLESS Checks") -425 check_for_recipe_literals(inst, get(Recipe, r)); -426 :(code) -427 void check_for_recipe_literals(const instruction& inst, const recipe& caller) { -428 for (int i = 0; i < SIZE(inst.ingredients); ++i) { -429 if (is_mu_recipe(inst.ingredients.at(i))) { -430 raise << maybe(caller.name) << "missing type for '" << inst.ingredients.at(i).original_string << "' in '" << to_original_string(inst) << "'\n" << end(); -431 if (is_present_in_ingredients(caller, inst.ingredients.at(i).name)) -432 raise << " did you forget 'load-ingredients'?\n" << end(); -433 } -434 } -435 } -436 -437 :(scenario load_ingredients_missing_error_3) -438 % Hide_errors = true; -439 def foo {f: (recipe num -> num)} [ -440 local-scope -441 b:num <- call f, 1 -442 ] -443 +error: foo: missing type for 'f' in 'b:num <- call f, 1' -444 +error: did you forget 'load-ingredients'? -445 -446 :(before "End Mu Types Initialization") -447 put(Type_abbreviations, "function", new_type_tree("recipe")); -448 -449 :(scenario call_function) -450 def main [ -451 {1: (function number -> number)} <- copy f -452 2:num <- call {1: (function number -> number)}, 34 -453 ] -454 def f x:num -> y:num [ -455 local-scope -456 load-ingredients -457 y <- copy x -458 ] -459 +mem: storing 34 in location 2 +400 $error: 0 +401 +402 //: make sure we don't accidentally break on a recipe literal +403 :(scenario jump_forbidden_on_recipe_literals) +404 % Hide_errors = true; +405 def foo [ +406 local-scope +407 ] +408 def main [ +409 local-scope +410 { +411 break-if foo +412 } +413 ] +414 # error should be as if foo is not a recipe +415 +error: main: missing type for 'foo' in 'break-if foo' +416 +417 :(before "End JUMP_IF Checks") +418 check_for_recipe_literals(inst, get(Recipe, r)); +419 :(before "End JUMP_UNLESS Checks") +420 check_for_recipe_literals(inst, get(Recipe, r)); +421 :(code) +422 void check_for_recipe_literals(const instruction& inst, const recipe& caller) { +423 for (int i = 0; i < SIZE(inst.ingredients); ++i) { +424 if (is_mu_recipe(inst.ingredients.at(i))) { +425 raise << maybe(caller.name) << "missing type for '" << inst.ingredients.at(i).original_string << "' in '" << to_original_string(inst) << "'\n" << end(); +426 if (is_present_in_ingredients(caller, inst.ingredients.at(i).name)) +427 raise << " did you forget 'load-ingredients'?\n" << end(); +428 } +429 } +430 } +431 +432 :(scenario load_ingredients_missing_error_3) +433 % Hide_errors = true; +434 def foo {f: (recipe num -> num)} [ +435 local-scope +436 b:num <- call f, 1 +437 ] +438 +error: foo: missing type for 'f' in 'b:num <- call f, 1' +439 +error: did you forget 'load-ingredients'? +440 +441 :(before "End Mu Types Initialization") +442 put(Type_abbreviations, "function", new_type_tree("recipe")); +443 put(Type_abbreviations, "fn", new_type_tree("recipe")); +444 +445 //: copying functions to variables +446 +447 :(scenario copy_recipe_to_variable) +448 def main [ +449 {1: (fn number -> number)} <- copy f +450 2:num <- call {1: (function number -> number)}, 34 +451 ] +452 def f x:num -> y:num [ +453 local-scope +454 load-ingredients +455 y <- copy x +456 ] +457 +mem: storing 34 in location 2 +458 +459 :(scenario copy_overloaded_recipe_to_variable) +460 def main [ +461 local-scope +462 {x: (fn num -> num)} <- copy f +463 1:num/raw <- call x, 34 +464 ] +465 # variant f +466 def f x:bool -> y:bool [ +467 local-scope +468 load-ingredients +469 y <- copy x +470 ] +471 # variant f_2 +472 def f x:num -> y:num [ +473 local-scope +474 load-ingredients +475 y <- copy x +476 ] +477 # x contains f_2 +478 +mem: storing 34 in location 1 +479 +480 :(before "End resolve_ambiguous_call(r, index, inst, caller_recipe) Special-cases") +481 if (inst.name == "copy") { +482 for (int i = 0; i < SIZE(inst.ingredients); ++i) { +483 if (!is_recipe_literal(inst.ingredients.at(i))) continue; +484 if (non_ghost_size(get_or_insert(Recipe_variants, inst.ingredients.at(i).name)) < 1) continue; +485 // potentially overloaded recipe +486 string new_name = resolve_ambiguous_call(inst.ingredients.at(i).name, inst.products.at(i), r, index, caller_recipe); +487 if (new_name == "") continue; +488 inst.ingredients.at(i).name = new_name; +489 inst.ingredients.at(i).value = get(Recipe_ordinal, new_name); +490 } +491 return; +492 } +493 :(code) +494 string resolve_ambiguous_call(const string& recipe_name, const reagent& call_types, const recipe_ordinal r, int index, const recipe& caller_recipe) { +495 instruction inst; +496 inst.name = recipe_name; +497 if (!is_mu_recipe(call_types)) return ""; // error raised elsewhere +498 if (is_recipe_literal(call_types)) return ""; // error raised elsewhere +499 construct_fake_call(call_types, inst); +500 resolve_ambiguous_call(r, index, inst, caller_recipe); +501 return inst.name; +502 } +503 void construct_fake_call(const reagent& recipe_var, instruction& out) { +504 assert(recipe_var.type->left->name == "recipe"); +505 type_tree* stem = NULL; +506 for (stem = recipe_var.type->right; stem && stem->left->name != "->"; stem = stem->right) +507 out.ingredients.push_back(copy(stem->left)); +508 if (stem == NULL) return; +509 for (/*skip '->'*/stem = stem->right; stem; stem = stem->right) +510 out.products.push_back(copy(stem->left)); +511 } +512 +513 :(scenario copy_shape_shifting_recipe_to_variable) +514 def main [ +515 local-scope +516 {x: (fn num -> num)} <- copy f +517 1:num/raw <- call x, 34 +518 ] +519 def f x:_elem -> y:_elem [ +520 local-scope +521 load-inputs +522 y <- copy x +523 ] +524 +mem: storing 34 in location 1 +525 +526 //: passing function literals to (higher-order) functions +527 +528 :(scenario pass_overloaded_recipe_literal_to_ingredient) +529 # like copy_overloaded_recipe_to_variable except we bind 'x' in the course of +530 # a 'call' rather than 'copy' +531 def main [ +532 1:num <- g f +533 ] +534 def g {x: (fn num -> num)} -> result:num [ +535 local-scope +536 load-ingredients +537 result <- call x, 34 +538 ] +539 # variant f +540 def f x:bool -> y:bool [ +541 local-scope +542 load-ingredients +543 y <- copy x +544 ] +545 # variant f_2 +546 def f x:num -> y:num [ +547 local-scope +548 load-ingredients +549 y <- copy x +550 ] +551 # x contains f_2 +552 +mem: storing 34 in location 1 +553 +554 :(after "End resolve_ambiguous_call(r, index, inst, caller_recipe) Special-cases") +555 for (int i = 0; i < SIZE(inst.ingredients); ++i) { +556 if (!is_mu_recipe(inst.ingredients.at(i))) continue; +557 if (non_ghost_size(get_or_insert(Recipe_variants, inst.ingredients.at(i).name)) < 1) continue; +558 if (get(Recipe_ordinal, inst.name) < MAX_PRIMITIVE_RECIPES) continue; +559 if (non_ghost_size(get_or_insert(Recipe_variants, inst.name)) > 1) { +560 raise << maybe(caller_recipe.name) << "sorry, we're not yet smart enough to simultaneously guess which overloads you want for '" << inst.name << "' and '" << inst.ingredients.at(i).name << "'\n" << end(); +561 return; +562 } +563 const recipe& callee = get(Recipe, get(Recipe_ordinal, inst.name)); +564 if (!callee.has_header) { +565 raise << maybe(caller_recipe.name) << "sorry, we're not yet smart enough to guess which variant of '" << inst.ingredients.at(i).name << "' you want, when the caller '" << inst.name << "' doesn't have a header\n" << end(); +566 return; +567 } +568 string new_name = resolve_ambiguous_call(inst.ingredients.at(i).name, callee.ingredients.at(i), r, index, caller_recipe); +569 if (new_name != "") { +570 inst.ingredients.at(i).name = new_name; +571 inst.ingredients.at(i).value = get(Recipe_ordinal, new_name); +572 } +573 } +574 +575 :(scenario return_overloaded_recipe_literal_to_caller) +576 def main [ +577 local-scope +578 {x: (fn num -> num)} <- g +579 1:num/raw <- call x, 34 +580 ] +581 def g -> {x: (fn num -> num)} [ +582 local-scope +583 return f +584 ] +585 # variant f +586 def f x:bool -> y:bool [ +587 local-scope +588 load-ingredients +589 y <- copy x +590 ] +591 # variant f_2 +592 def f x:num -> y:num [ +593 local-scope +594 load-ingredients +595 y <- copy x +596 ] +597 # x contains f_2 +598 +mem: storing 34 in location 1 +599 +600 :(before "End resolve_ambiguous_call(r, index, inst, caller_recipe) Special-cases") +601 if (inst.name == "return" || inst.name == "reply") { +602 for (int i = 0; i < SIZE(inst.ingredients); ++i) { +603 if (!is_recipe_literal(inst.ingredients.at(i))) continue; +604 if (non_ghost_size(get_or_insert(Recipe_variants, inst.ingredients.at(i).name)) < 1) continue; +605 // potentially overloaded recipe +606 if (!caller_recipe.has_header) { +607 raise << maybe(caller_recipe.name) << "sorry, we're not yet smart enough to guess which variant of '" << inst.ingredients.at(i).name << "' you want, without a recipe header\n" << end(); +608 return; +609 } +610 string new_name = resolve_ambiguous_call(inst.ingredients.at(i).name, caller_recipe.products.at(i), r, index, caller_recipe); +611 if (new_name == "") continue; +612 inst.ingredients.at(i).name = new_name; +613 inst.ingredients.at(i).value = get(Recipe_ordinal, new_name); +614 } +615 return; +616 } -- cgit 1.4.1-2-gfad0