1 //: A big convenience high-level languages provide is the ability to name memory
  2 //: locations. In Mu, a transform called 'transform_names' provides this
  3 //: convenience.
  4 
  5 :(scenario transform_names)
  6 def main [
  7   x:num <- copy 0
  8 ]
  9 +name: assign x 1
 10 +mem: storing 0 in location 1
 11 
 12 :(scenarios transform)
 13 :(scenario transform_names_fails_on_use_before_define)
 14 % Hide_errors = true;
 15 def main [
 16   x:num <- copy y:num
 17 ]
 18 +error: main: tried to read ingredient 'y' in 'x:num <- copy y:num' but it hasn't been written to yet
 19 # todo: detect conditional defines
 20 
 21 :(after "Transform.push_back(compute_container_sizes)")
 22 Transform.push_back(transform_names);  // idempotent
 23 
 24 :(before "End Globals")
 25 map<recipe_ordinal, map<string, int> > Name;
 26 
 27 //: the Name map is a global, so save it before tests and reset it for every
 28 //: test, just to be safe.
 29 :(before "End Globals")
 30 map<recipe_ordinal, map<string, int> > Name_snapshot;
 31 :(before "End save_snapshots")
 32 Name_snapshot = Name;
 33 :(before "End restore_snapshots")
 34 Name = Name_snapshot;
 35 
 36 :(code)
 37 void transform_names(const recipe_ordinal r) {
 38   recipe& caller = get(Recipe, r);
 39   trace(9991, "transform") << "--- transform names for recipe " << caller.name << end();
 40   bool names_used = false;
 41   bool numeric_locations_used = false;
 42   map<string, int>& names = Name[r];
 43   // store the indices 'used' so far in the map
 44   int& curr_idx = names[""];
 45   ++curr_idx;  // avoid using index 0, benign skip in some other cases
 46   for (int i = 0;  i < SIZE(caller.steps);  ++i) {
 47   ¦ instruction& inst = caller.steps.at(i);
 48   ¦ // End transform_names(inst) Special-cases
 49   ¦ // map names to addresses
 50   ¦ for (int in = 0;  in < SIZE(inst.ingredients);  ++in) {
 51   ¦ ¦ reagent& ingredient = inst.ingredients.at(in);
 52   ¦ ¦ // Begin transform_names Ingredient Special-cases(ingredient, inst, caller)
 53   ¦ ¦ if (is_disqualified(ingredient, inst, caller.name)) continue;
 54   ¦ ¦ if (is_numeric_location(ingredient)) numeric_locations_used = true;
 55   ¦ ¦ if (is_named_location(ingredient)) names_used = true;
 56   ¦ ¦ if (is_integer(ingredient.name)) continue;
 57   ¦ ¦ if (!already_transformed(ingredient, names)) {
 58   ¦ ¦ ¦ raise << maybe(caller.name) << "tried to read ingredient '" << ingredient.name << "' in '" << to_original_string(inst) << "' but it hasn't been written to yet\n" << end();
 59   ¦ ¦ ¦ // use-before-set Error
 60   ¦ ¦ ¦ return;
 61   ¦ ¦ }
 62   ¦ ¦ int v = lookup_name(ingredient, r);
 63   ¦ ¦ if (v >= 0) {
 64   ¦ ¦ ¦ ingredient.set_value(v);
 65   ¦ ¦ ¦ // Done Placing Ingredient(ingredient, inst, caller)
 66   ¦ ¦ }
 67   ¦ ¦ else {
 68   ¦ ¦ ¦ raise << maybe(caller.name) << "can't find a place to store '" << ingredient.name << "'\n" << end();
 69   ¦ ¦ ¦ return;
 70   ¦ ¦ }
 71   ¦ }
 72   ¦ for (int out = 0;  out < SIZE(inst.products);  ++out) {
 73   ¦ ¦ reagent& product = inst.products.at(out);
 74   ¦ ¦ // Begin transform_names Product Special-cases(product, inst, caller)
 75   ¦ ¦ if (is_disqualified(product, inst, caller.name)) continue;
 76   ¦ ¦ if (is_numeric_location(product)) numeric_locations_used = true;
 77   ¦ ¦ if (is_named_location(product)) names_used = true;
 78   ¦ ¦ if (is_integer(product.name)) continue;
 79   ¦ ¦ if (names.find(product.name) == names.end()) {
 80   ¦ ¦ ¦ trace(9993, "name") << "assign " << product.name << " " << curr_idx << end();
 81   ¦ ¦ ¦ names[product.name] = curr_idx;
 82   ¦ ¦ ¦ curr_idx += size_of(product);
 83   ¦ ¦ }
 84   ¦ ¦ int v = lookup_name(product, r);
 85   ¦ ¦ if (v >= 0) {
 86   ¦ ¦ ¦ product.set_value(v);
 87   ¦ ¦ ¦ // Done Placing Product(product, inst, caller)
 88   ¦ ¦ }
 89   ¦ ¦ else {
 90   ¦ ¦ ¦ raise << maybe(caller.name) << "can't find a place to store '" << product.name << "'\n" << end();
 91   ¦ ¦ ¦ return;
 92   ¦ ¦ }
 93   ¦ }
 94   }
 95   if (names_used && numeric_locations_used)
 96   ¦ raise << maybe(caller.name) << "mixing variable names and numeric addresses\n" << end();
 97 }
 98 
 99 bool is_disqualified(/*mutable*/ reagent& x, const instruction& inst, const string& recipe_name) {
100   if (!x.type) {
101   ¦ raise << maybe(recipe_name) << "missing type for '" << x.original_string << "' in '" << to_original_string(inst) << "'\n" << end();
102   ¦ // missing-type Error 1
103   ¦ return true;
104   }
105   if (is_raw(x)) return true;
106   if (is_literal(x)) return true;
107   // End is_disqualified Special-cases
108   if (x.initialized) return true;
109   return false;
110 }
111 
112 bool already_transformed(const reagent& r, const map<string, int>& names) {
113   return contains_key(names, r.name);
114 }
115 
116 int lookup_name(const reagent& r, const recipe_ordinal default_recipe) {
117   return Name[default_recipe][r.name];
118 }
119 
120 type_ordinal skip_addresses(type_tree* type) {
121   while (type && is_compound_type_starting_with(type, "address"))
122   ¦ type = type->right;
123   if (!type) return -1;  // error handled elsewhere
124   if (type->atom) return type->value;
125   const type_tree* base_type = type;
126   // Update base_type in skip_addresses
127   if (base_type->atom)
128   ¦ return base_type->value;
129   assert(base_type->left->atom);
130   return base_type->left->value;
131 }
132 
133 bool is_compound_type_starting_with(const type_tree* type, const string& expected_name) {
134   if (!type) return false;
135   if (type->atom) return false;
136   if (!type->left->atom) return false;
137   return type->left->value == get(Type_ordinal, expected_name);
138 }
139 
140 int find_element_name(const type_ordinal t, const string& name, const string& recipe_name) {
141   const type_info& container = get(Type, t);
142   for (int i = 0;  i < SIZE(container.elements);  ++i)
143   ¦ if (container.elements.at(i).name == name) return i;
144   raise << maybe(recipe_name) << "unknown element '" << name << "' in container '" << get(Type, t).name << "'\n" << end();
145   return -1;
146 }
147 
148 bool is_numeric_location(const reagent& x) {
149   if (is_literal(x)) return false;
150   if (is_raw(x)) return false;
151   if (x.name == "0") return false;  // used for chaining lexical scopes
152   return is_integer(x.name);
153 }
154 
155 bool is_named_location(const reagent& x) {
156   if (is_literal(x)) return false;
157   if (is_raw(x)) return false;
158   if (is_special_name(x.name)) return false;
159   return !is_integer(x.name);
160 }
161 
162 // all names here should either be disqualified or also in bind_special_scenario_names
163 bool is_special_name(const string& s) {
164   if (s == "_") return true;
165   if (s == "0") return true;
166   // End is_special_name Special-cases
167   return false;
168 }
169 
170 :(scenario transform_names_supports_containers)
171 def main [
172   x:point <- merge 34, 35
173   y:num <- copy 3
174 ]
175 +name: assign x 1
176 # skip location 2 because x occupies two locations
177 +name: assign y 3
178 
179 :(scenario transform_names_supports_static_arrays)
180 def main [
181   x:@:num:3 <- create-array
182   y:num <- copy 3
183 ]
184 +name: assign x 1
185 # skip locations 2, 3, 4 because x occupies four locations
186 +name: assign y 5
187 
188 :(scenario transform_names_passes_dummy)
189 # _ is just a dummy result that never gets consumed
190 def main [
191   _, x:num <- copy 0, 1
192 ]
193 +name: assign x 1
194 -name: assign _ 1
195 
196 //: an escape hatch to suppress name conversion that we'll use later
197 :(scenarios run)
198 :(scenario transform_names_passes_raw)
199 % Hide_errors = true;
200 def main [
201   x:num/raw <- copy 0
202 ]
203 -name: assign x 1
204 +error: can't write to location 0 in 'x:num/raw <- copy 0'
205 
206 :(scenarios transform)
207 :(scenario transform_names_fails_when_mixing_names_and_numeric_locations)
208 % Hide_errors = true;
209 def main [
210   x:num <- copy 1:num
211 ]
212 +error: main: mixing variable names and numeric addresses
213 
214 :(scenario transform_names_fails_when_mixing_names_and_numeric_locations_2)
215 % Hide_errors = true;
216 def main [
217   x:num <- copy 1
218   1:num <- copy x:num
219 ]
220 +error: main: mixing variable names and numeric addresses
221 
222 :(scenario transform_names_does_not_fail_when_mixing_names_and_raw_locations)
223 def main [
224   x:num <- copy 1:num/raw
225 ]
226 -error: main: mixing variable names and numeric addresses
227 $error: 0
228 
229 :(scenario transform_names_does_not_fail_when_mixing_names_and_literals)
230 def main [
231   x:num <- copy 1
232 ]
233 -error: main: mixing variable names and numeric addresses
234 $error: 0
235 
236 //:: Support element names for containers in 'get' and 'get-location' and 'put'.
237 //: (get-location is implemented later)
238 
239 :(before "End update GET offset_value in Check")
240 else {
241   if (!offset.initialized) {
242   ¦ raise << maybe(get(Recipe, r).name) << "uninitialized offset '" << offset.name << "' in '" << to_original_string(inst) << "'\n" << end();
243   ¦ break;
244   }
245   offset_value = offset.value;
246 }
247 
248 :(scenario transform_names_transforms_container_elements)
249 def main [
250   p:&:point <- copy 0
251   a:num <- get *p:&:point, y:offset
252   b:num <- get *p:&:point, x:offset
253 ]
254 +name: element y of type point is at offset 1
255 +name: element x of type point is at offset 0
256 
257 :(before "End transform_names(inst) Special-cases")
258 // replace element names of containers with offsets
259 if (inst.name == "get" || inst.name == "get-location" || inst.name == "put") {
260   //: avoid raising any errors here; later layers will support overloading new
261   //: instructions with the same names (static dispatch), which could lead to
262   //: spurious errors
263   if (SIZE(inst.ingredients) < 2)
264   ¦ break;  // error raised elsewhere
265   if (!is_literal(inst.ingredients.at(1)))
266   ¦ break;  // error raised elsewhere
267   if (inst.ingredients.at(1).name.find_first_not_of("0123456789") != string::npos) {
268   ¦ // since first non-address in base type must be a container, we don't have to canonize
269   ¦ type_ordinal base_type = skip_addresses(inst.ingredients.at(0).type);
270   ¦ if (contains_key(Type, base_type)) {  // otherwise we'll raise an error elsewhere
271   ¦ ¦ inst.ingredients.at(1).set_value(find_element_name(base_type, inst.ingredients.at(1).name, get(Recipe, r).name));
272   ¦ ¦ trace(9993, "name") << "element " << inst.ingredients.at(1).name << " of type " << get(Type, base_type).name << " is at offset " << no_scientific(inst.ingredients.at(1).value) << end();
273   ¦ }
274   }
275 }
276 
277 :(scenario missing_type_in_get)
278 % Hide_errors = true;
279 def main [
280   get a, x:offset
281 ]
282 +error: main: missing type for 'a' in 'get a, x:offset'
283 
284 //: this test is actually illegal so can't call run
285 :(scenarios transform)
286 :(scenario transform_names_handles_containers)
287 def main [
288   a:point <- copy 0/unsafe
289   b:num <- copy 0/unsafe
290 ]
291 +name: assign a 1
292 +name: assign b 3
293 
294 //:: Support variant names for exclusive containers in 'maybe-convert'.
295 
296 :(scenarios run)
297 :(scenario transform_names_handles_exclusive_containers)
298 def main [
299   12:num <- copy 1
300   13:num <- copy 35
301   14:num <- copy 36
302   20:point, 22:bool <- maybe-convert 12:number-or-point/unsafe, p:variant
303 ]
304 +name: variant p of type number-or-point has tag 1
305 +mem: storing 1 in location 22
306 +mem: storing 35 in location 20
307 +mem: storing 36 in location 21
308 
309 :(before "End transform_names(inst) Special-cases")
310 // convert variant names of exclusive containers
311 if (inst.name == "maybe-convert") {
312   if (SIZE(inst.ingredients) != 2) {
313   ¦ raise << maybe(get(Recipe, r).name) << "exactly 2 ingredients expected in '" << to_original_string(inst) << "'\n" << end();
314   ¦ break;
315   }
316   assert(is_literal(inst.ingredients.at(1)));
317   if (inst.ingredients.at(1).name.find_first_not_of("0123456789") != string::npos) {
318   ¦ // since first non-address in base type must be an exclusive container, we don't have to canonize
319   ¦ type_ordinal base_type = skip_addresses(inst.ingredients.at(0).type);
320   ¦ if (contains_key(Type, base_type)) {  // otherwise we'll raise an error elsewhere
321   ¦ ¦ inst.ingredients.at(1).set_value(find_element_name(base_type, inst.ingredients.at(1).name, get(Recipe, r).name));
322   ¦ ¦ trace(9993, "name") << "variant " << inst.ingredients.at(1).name << " of type " << get(Type, base_type).name << " has tag " << no_scientific(inst.ingredients.at(1).value) << end();
323   ¦ }
324   }
325 }
326 
327 :(scenario missing_type_in_maybe_convert)
328 % Hide_errors = true;
329 def main [
330   maybe-convert a, x:variant
331 ]
332 +error: main: missing type for 'a' in 'maybe-convert a, x:variant'