https://github.com/akkartik/mu/blob/master/045closure_name.cc
  1 //: Writing to a literal (not computed) address of 0 in a recipe chains two
  2 //: spaces together. When a variable has a property of /space:1, it looks up
  3 //: the variable in the chained/surrounding space. /space:2 looks up the
  4 //: surrounding space of the surrounding space, etc.
  5 //:
  6 //: todo: warn on default-space abuse. default-space for one recipe should
  7 //: never come from another, otherwise memory will be corrupted.
  8 
  9 :(scenario closure)
 10 def main [
 11   default-space:space <- new location:type, 30
 12   2:space/names:new-counter <- new-counter
 13   10:num/raw <- increment-counter 2:space/names:new-counter
 14   11:num/raw <- increment-counter 2:space/names:
#!/bin/sh
# Variables that control the program. #
# GIT Repo #
export DSCIP_GITREPO="https://www.example.com/example/example.git"
export DSCIP_NAME="Example"
# GIT MODE:                                                   #
# pull: Doesn't delete previous clone and just pulls changes. #
# clone: Deletes previous clone, and creates a fresh clone.   #
export DSCIP_GITMODE="clone"
# Branch to check #
export DSCIP_BRANCH="master"
# The directory where all the scripts are. By default tries to detect where    #
# automatically.                                                               #
WORKING_DIRECTORY="$(pwd -P)"
export WORKING_DIRECTORY
# Commands to run before building. #
export DSCIP_PRE_CMD="$WORKING_DIRECTORY/pre.sh"
# Commands to run to build program. #
export DSCIP_BUILD_CMD="$WORKING_DIRECTORY/build.sh"
# Commands to run after building has succeeded. #
export DSCIP_POST_CMD="$WORKING_DIRECTORY/post.sh"
# Commands to run after building has failed.
export DSCIP_FAILED_CMD="$WORKING_DIRECTORY/failed.sh"
# Daemon mode options #
export DSCIP_DAEMON="false" # If daemon mode should be enabled or not. #
export DSCIP_DAEMON_FORK="true" # If the daemon should run in the background. #
export DSCIP_SLEEP="60" # How many seconds before the daemon re-runs itself. #
# etc #
export DSCIP_DISREGARD_COMMIT_CHECK="false" # If the script should just rebuild even #
# if upstream has not updated. #
export DSCIP_OUTPUT_TO="$WORKING_DIRECTORY/output.txt" # Output to file, default is stdout. 
class="Delimiter">) raise << "slot 0 should have a single value in /names, but got '" << to_string(inst.products.at(j)) << "'\n" << end(); 62 const string& surrounding_recipe_name = s->value; 63 if (surrounding_recipe_name.empty()) { 64 raise << "slot 0 doesn't initialize its /names property in recipe '" << get(Recipe, r).name << "'\n" << end(); 65 continue; 66 } 67 if (contains_key(Surrounding_space, r) 68 && get(Surrounding_space, r) != get(Recipe_ordinal, surrounding_recipe_name)) { 69 raise << "recipe '" << get(Recipe, r).name << "' can have only one 'surrounding' recipe but has '" << get(Recipe, get(Surrounding_space, r)).name << "' and '" << surrounding_recipe_name << "'\n" << end(); 70 continue; 71 } 72 trace(9993, "name") << "lexically surrounding space for recipe " << get(Recipe, r).name << " comes from " << surrounding_recipe_name << end(); 73 if (!contains_key(Recipe_ordinal, surrounding_recipe_name)) { 74 raise << "can't find recipe providing surrounding space for '" << get(Recipe, r).name << "'; looking for '" << surrounding_recipe_name << "'\n" << end(); 75 continue; 76 } 77 put(Surrounding_space, r, get(Recipe_ordinal, surrounding_recipe_name)); 78 } 79 } 80 } 81 82 //: Once surrounding spaces are available, transform_names uses them to handle 83 //: /space properties. 84 85 :(replace{} "int lookup_name(const reagent& r, const recipe_ordinal default_recipe)") 86 int lookup_name(const reagent& x, const recipe_ordinal default_recipe) { 87 if (!has_property(x, "space")) { 88 if (Name[default_recipe].empty()) raise << "name not found: " << x.name << '\n' << end(); 89 return Name[default_recipe][x.name]; 90 } 91 string_tree* p = property(x, "space"); 92 if (!p || !p->atom) raise << "/space property should have exactly one (non-negative integer) value\n" << end(); 93 int n = to_integer(p->value); 94 assert(n >= 0); 95 recipe_ordinal surrounding_recipe = lookup_surrounding_recipe(default_recipe, n); 96 if (surrounding_recipe == -1) return -1; 97 set<recipe_ordinal> done; 98 vector<recipe_ordinal> path; 99 return lookup_name(x, surrounding_recipe, done, path); 100 } 101 102 // If the recipe we need to lookup this name in doesn't have names done yet, 103 // recursively call transform_names on it. 104 int lookup_name(const reagent& x, const recipe_ordinal r, set<recipe_ordinal>& done, vector<recipe_ordinal>& path) { 105 if (!Name[r].empty()) return Name[r][x.name]; 106 if (contains_key(done, r)) { 107 raise << "can't compute address of '" << to_string(x) << "' because\n" << end(); 108 for (int i = 1; i < SIZE(path); ++i) { 109 raise << path.at(i-1) << " requires computing names of " << path.at(i) << '\n' << end(); 110 } 111 raise << path.at(SIZE(path)-1) << " requires computing names of " << r << "..ad infinitum\n" << end(); 112 return -1; 113 } 114 done.insert(r); 115 path.push_back(r); 116 transform_names(r); // Not passing 'done' through. Might this somehow cause an infinite loop? 117 assert(!Name[r].empty()); 118 return Name[r][x.name]; 119 } 120 121 recipe_ordinal lookup_surrounding_recipe(const recipe_ordinal r, int n) { 122 if (n == 0) return r; 123 if (!contains_key(Surrounding_space, r)) { 124 raise << "don't know surrounding recipe of '" << get(Recipe, r).name << "'\n" << end(); 125 return -1; 126 } 127 assert(contains_key(Surrounding_space, r)); 128 return lookup_surrounding_recipe(get(Surrounding_space, r), n-1); 129 } 130 131 //: weaken use-before-set detection just a tad 132 :(replace{} "bool already_transformed(const reagent& r, const map<string, int>& names)") 133 bool already_transformed(const reagent& r, const map<string, int>& names) { 134 if (has_property(r, "space")) { 135 string_tree* p = property(r, "space"); 136 if (!p || !p->atom) { 137 raise << "/space property should have exactly one (non-negative integer) value in '" << r.original_string << "'\n" << end(); 138 return false; 139 } 140 if (p->value != "0") return true; 141 } 142 return contains_key(names, r.name); 143 } 144 145 :(scenario missing_surrounding_space) 146 % Hide_errors = true; 147 def f [ 148 local-scope 149 x:num/space:1 <- copy 34 150 ] 151 +error: don't know surrounding recipe of 'f' 152 +error: f: can't find a place to store 'x' 153 154 //: extra test for try_reclaim_locals() from previous layers 155 :(scenario local_scope_ignores_nonlocal_spaces) 156 def new-scope [ 157 local-scope 158 x:&:num <- new number:type 159 *x:&:num <- copy 34 160 return default-space:space 161 ] 162 def use-scope [ 163 local-scope 164 outer:space/names:new-scope <- next-ingredient 165 0:space/names:new-scope <- copy outer:space 166 return *x:&:num/space:1 167 ] 168 def main [ 169 1:space/raw <- new-scope 170 3:num/raw <- use-scope 1:space/raw 171 ] 172 +mem: storing 34 in location 3 173 174 :(scenario recursive_transform_names) 175 def foo [ 176 local-scope 177 x:num <- copy 0 178 return default-space:space/names:foo 179 ] 180 def main [ 181 local-scope 182 0:space/names:foo <- foo 183 x:num/space:1 <- copy 34 184 ] 185 $error: 0