https://github.com/akkartik/mu/blob/master/052tangle.cc
  1 //: Allow code for recipes to be pulled in from multiple places and inserted
  2 //: at special labels called 'waypoints' using two new top-level commands:
  3 //:   before
  4 //:   after
  5 
  6 //: Most labels are local: they must be unique to a recipe, and are invisible
  7 //: outside the recipe. However, waypoints are global: a recipe can have
  8 //: multiple of them, you can't use them as jump targets.
  9 :(before "End is_jump_target Special-cases")
 10 if (is_waypoint(label)) return false;
 11 //: Waypoints are always surrounded by '<>', e.g. <handle-request>.
 12 :(code)
 13 bool is_waypoint(string label) {
 14   return *label.begin() == '<' && *label.rbegin() == '>';
 15 }
 16 
 17 :(scenario tangle_before)
 18 def main [
 19   1:num <- copy 0
 20   <label1>
 21   3:num <- copy 0
 22 ]
 23 before <label1> [
 24   2:num <- copy 0
 25 ]
 26 +mem: storing 0 in location 1
 27 +mem: storing 0 in location 2
 28 +mem: storing 0 in location 3
 29 # nothing else
 30 $mem: 3
 31 
 32 //: while loading recipes, load before/after fragments
 33 
 34 :(before "End Globals")
 35 map<string /*label*/, recipe> Before_fragments, After_fragments;
 36 set<string /*label*/> Fragments_used;
 37 :(before "End Reset")
 38 Before_fragments.clear();
 39 After_fragments.clear();
 40 Fragments_used.clear();
 41 
 42 :(before "End Command Handlers")
 43 else if (command == "before") {
 44   string label = next_word(in);
 45   if (label.empty()) {
 46     assert(!has_data(in));
 47     raise << "incomplete 'before' block at end of file\n" << end();
 48     return result;
 49   }
 50   recipe tmp;
 51   slurp_body(in, tmp);
 52   if (is_waypoint(label))
 53     Before_fragments[label].steps.insert(Before_fragments[label].steps.end(), tmp.steps.begin(), tmp.steps.end());
 54   else
 55     raise << "can't tangle before non-waypoint " << label << '\n' << end();
 56   // End before Command Handler
 57 }
 58 else if (command == "after") {
 59   string label = next_word(in);
 60   if (label.empty()) {
 61     assert(!has_data(in));
 62     raise << "incomplete 'after' block at end of file\n" << end();
 63     return result;
 64   }
 65   recipe tmp;
 66   slurp_body(in, tmp);
 67   if (is_waypoint(label))
 68     After_fragments[label].steps.insert(After_fragments[label].steps.begin(), tmp.steps.begin(), tmp.steps.end());
 69   else
 70     raise << "can't tangle after non-waypoint " << label << '\n' << end();
 71   // End after Command Handler
 72 }
 73 
 74 //: after all recipes are loaded, insert fragments at appropriate labels.
 75 
 76 :(after "Begin Instruction Inserting/Deleting Transforms")
 77 Transform.push_back(insert_fragments);  // NOT idempotent
 78 
 79 //: We might need to perform multiple passes, in case inserted fragments
 80 //: include more labels that need further insertions. Track which labels we've
 81 //: already processed using an extra field.
 82 :(before "End instruction Fields")
 83 mutable bool tangle_done;
 84 :(before "End instruction Constructor")
 85 tangle_done = false;
 86 
 87 :(code)
 88 void insert_fragments(const recipe_ordinal r) {
 89   insert_fragments(get(Recipe, r));
 90 }
 91 
 92 void insert_fragments(recipe& r) {
 93   trace(9991, "transform") << "--- insert fragments into recipe " << r.name << end();
 94   bool made_progress = true;
 95   int pass = 0;
 96   while (made_progress) {
 97     made_progress = false;
 98     // create a new vector because insertions invalidate iterators
 99     vector<instruction> result;
100     for (int i = 0;  i < SIZE(r.steps);  ++i) {
101       const instruction& inst = r.steps.at(i);
102       if (!inst.is_label || !is_waypoint(inst.label) || inst.tangle_done) {
103         result.push_back(inst);
104         continue;
105       }
106       inst.tangle_done = true;
107       made_progress = true;
108       Fragments_used.insert(inst.label);
109       ostringstream prefix;
110       prefix << '+' << r.name << '_' << pass << '_' << i;
111       // ok to use contains_key even though Before_fragments uses [],
112       // because appending an empty recipe is a noop
113       if (contains_key(Before_fragments, inst.label)) {
114         trace(9992, "transform") << "insert fragments before label " << inst.label << end();
115         append_fragment(result, Before_fragments[inst.label].steps, prefix.str());
116       }
117       result.push_back(inst);
118       if (contains_key(After_fragments, inst.label)) {
119         trace(9992, "transform") << "insert fragments after label " << inst.label << end();
120         append_fragment(result, After_fragments[inst.label].steps, prefix.str());
121       }
122     }
123     r.steps.swap(result);
124     ++pass;
125   }
126 }
127 
128 void append_fragment(vector<instruction>& base, const vector<instruction>& patch, const string prefix) {
129   // append 'patch' to 'base' while keeping 'base' oblivious to any new jump
130   // targets in 'patch' oblivious to 'base' by prepending 'prefix' to them.
131   // we might tangle the same fragment at multiple points in a single recipe,
132   // and we need to avoid duplicate jump targets.
133   // so we'll keep jump targets local to the specific before/after fragment
134   // that introduces them.
135   set<string> jump_targets;
136   for (int i = 0;  i < SIZE(patch);  ++i) {
137     const instruction& inst = patch.at(i);
138     if (inst.is_label && is_jump_target(inst.label))
139       jump_targets.insert(inst.label);
140   }
141   for (int i = 0;  i < SIZE(patch);  ++i) {
142     instruction inst = patch.at(i);
143     if (inst.is_label) {
144       if (contains_key(jump_targets, inst.label))
145         inst.label = prefix+inst.label;
146       base.push_back(inst);
147       continue;
148     }
149     for (int j = 0;  j < SIZE(inst.ingredients);  ++j) {
150       reagent& x = inst.ingredients.at(j);
151       if (is_jump_target(x.name) && contains_key(jump_targets, x.name))
152         x.name = prefix+x.name;
153     }
154     base.push_back(inst);
155   }
156 }
157 
158 //: complain about unapplied fragments
159 //: This can't run during transform because later (shape-shifting recipes)
160 //: we'll encounter situations where fragments might get used long after
161 //: they're loaded, and we might run transform_all in between. To avoid
162 //: spurious errors, run this check right at the end, after all code is
163 //: loaded, right before we run main.
164 :(before "End Commandline Parsing")
165 check_insert_fragments();
166 :(code)
167 void check_insert_fragments() {
168   for (map<string, recipe>::iterator p = Before_fragments.begin();  p != Before_fragments.end();  ++p) {
169     if (!contains_key(Fragments_used, p->first))
170       raise << "could not locate insert before label " << p->first << '\n' << end();
171   }
172   for (map<string, recipe>::iterator p = After_fragments.begin();  p != After_fragments.end();  ++p) {
173     if (!contains_key(Fragments_used, p->first))
174       raise << "could not locate insert after label " << p->first << '\n' << end();
175   }
176 }
177 
178 :(scenario tangle_before_and_after)
179 def main [
180   1:num <- copy 0
181   <label1>
182   4:num <- copy 0
183 ]
184 before <label1> [
185   2:num <- copy 0
186 ]
187 after <label1> [
188   3:num <- copy 0
189 ]
190 +mem: storing 0 in location 1
191 +mem: storing 0 in location 2
192 # label1
193 +mem: storing 0 in location 3
194 +mem: storing 0 in location 4
195 # nothing else
196 $mem: 4
197 
198 :(scenario tangle_ignores_jump_target)
199 % Hide_errors = true;
200 def main [
201   1:num <- copy 0
202   +label1
203   4:num <- copy 0
204 ]
205 before +label1 [
206   2:num <- copy 0
207 ]
208 +error: can't tangle before non-waypoint +label1
209 
210 :(scenario tangle_keeps_labels_separate)
211 def main [
212   1:num <- copy 0
213   <label1>
214   <label2>
215   6:num <- copy 0
216 ]
217 before <label1> [
218   2:num <- copy 0
219 ]
220 after <label1> [
221   3:num <- copy 0
222 ]
223 before <label2> [
224   4:num <- copy 0
225 ]
226 after <label2> [
227   5:num <- copy 0
228 ]
229 +mem: storing 0 in location 1
230 +mem: storing 0 in location 2
231 # label1
232 +mem: storing 0 in location 3
233 # 'after' fragments for earlier label always go before 'before' fragments for later label
234 +mem: storing 0 in location 4
235 # label2
236 +mem: storing 0 in location 5
237 +mem: storing 0 in location 6
238 # nothing else
239 $mem: 6
240 
241 :(scenario tangle_stacks_multiple_fragments)
242 def main [
243   1:num <- copy 0
244   <label1>
245   6:num <- copy 0
246 ]
247 before <label1> [
248   2:num <- copy 0
249 ]
250 after <label1> [
251   3:num <- copy 0
252 ]
253 before <label1> [
254   4:num <- copy 0
255 ]
256 after <label1> [
257   5:num <- copy 0
258 ]
259 +mem: storing 0 in location 1
260 # 'before' fragments stack in order
261 +mem: storing 0 in location 2
262 +mem: storing 0 in location 4
263 # label1
264 # 'after' fragments stack in reverse order
265 +mem: storing 0 in location 5
266 +mem: storing 0 in location 3
267 +mem: storing 0 in location 6
268 # nothing else
269 $mem: 6
270 
271 :(scenario tangle_supports_fragments_with_multiple_instructions)
272 def main [
273   1:num <- copy 0
274   <label1>
275   6:num <- copy 0
276 ]
277 before <label1> [
278   2:num <- copy 0
279   3:num <- copy 0
280 ]
281 after <label1> [
282   4:num <- copy 0
283   5:num <- copy 0
284 ]
285 +mem: storing 0 in location 1
286 +mem: storing 0 in location 2
287 +mem: storing 0 in location 3
288 # label1
289 +mem: storing 0 in location 4
290 +mem: storing 0 in location 5
291 +mem: storing 0 in location 6
292 # nothing else
293 $mem: 6
294 
295 :(scenario tangle_tangles_into_all_labels_with_same_name)
296 def main [
297   1:num <- copy 10
298   <label1>
299   4:num <- copy 10
300   recipe2
301 ]
302 def recipe2 [
303   1:num <- copy 11
304   <label1>
305   4:num <- copy 11
306 ]
307 before <label1> [
308   2:num <- copy 12
309 ]
310 after <label1> [
311   3:num <- copy 12
312 ]
313 +mem: storing 10 in location 1
314 +mem: storing 12 in location 2
315 # label1
316 +mem: storing 12 in location 3
317 +mem: storing 10 in location 4
318 # recipe2
319 +mem: storing 11 in location 1
320 +mem: storing 12 in location 2
321 # label1
322 +mem: storing 12 in location 3
323 +mem: storing 11 in location 4
324 # nothing else
325 $mem: 8
326 
327 :(scenario tangle_tangles_into_all_labels_with_same_name_2)
328 def main [
329   1:num <- copy 10
330   <label1>
331   <label1>
332   4:num <- copy 10
333 ]
334 before <label1> [
335   2:num <- copy 12
336 ]
337 after <label1> [
338   3:num <- copy 12
339 ]
340 +mem: storing 10 in location 1
341 +mem: storing 12 in location 2
342 # label1
343 +mem: storing 12 in location 3
344 +mem: storing 12 in location 2
345 # label1
346 +mem: storing 12 in location 3
347 +mem: storing 10 in location 4
348 # nothing else
349 $mem: 6
350 
351 :(scenario tangle_tangles_into_all_labels_with_same_name_3)
352 def main [
353   1:num <- copy 10
354   <label1>
355   <foo>
356   4:num <- copy 10
357 ]
358 before <label1> [
359   2:num <- copy 12
360 ]
361 after <label1> [
362   3:num <- copy 12
363 ]
364 after <foo> [
365   <label1>
366 ]
367 +mem: storing 10 in location 1
368 +mem: storing 12 in location 2
369 # label1
370 +mem: storing 12 in location 3
371 +mem: storing 12 in location 2
372 # foo/label1
373 +mem: storing 12 in location 3
374 +mem: storing 10 in location 4
375 # nothing else
376 $mem: 6
377 
378 :(scenario tangle_handles_jump_target_inside_fragment)
379 def main [
380   1:num <- copy 10
381   <label1>
382   4:num <- copy 10
383 ]
384 before <label1> [
385   jump +label2:label
386   2:num <- copy 12
387   +label2
388   3:num <- copy 12
389 ]
390 +mem: storing 10 in location 1
391 # label1
392 +mem: storing 12 in location 3
393 +mem: storing 10 in location 4
394 # ignored by jump
395 -mem: storing 12 in label 2
396 # nothing else
397 $mem: 3
398 
399 :(scenario tangle_renames_jump_target)
400 def main [
401   1:num <- copy 10
402   <label1>
403   +label2
404   4:num <- copy 10
405 ]
406 before <label1> [
407   jump +label2:label
408   2:num <- copy 12
409   +label2  # renamed
410   3:num <- copy 12
411 ]
412 +mem: storing 10 in location 1
413 # label1
414 +mem: storing 12 in location 3
415 +mem: storing 10 in location 4
416 # ignored by jump
417 -mem: storing 12 in label 2
418 # nothing else
419 $mem: 3
420 
421 :(scenario tangle_jump_to_base_recipe)
422 def main [
423   1:num <- copy 10
424   <label1>
425   +label2
426   4:num <- copy 10
427 ]
428 before <label1> [
429   jump +label2:label
430   2:num <- copy 12
431   3:num <- copy 12
432 ]
433 +mem: storing 10 in location 1
434 # label1
435 +mem: storing 10 in location 4
436 # ignored by jump
437 -mem: storing 12 in label 2
438 -mem: storing 12 in location 3
439 # nothing else
440 $mem: 2
441 
442 //: ensure that there are no new fragments created for a label after it's already been inserted to
443 
444 :(code)
445 void test_new_fragment_after_tangle() {
446   // define a recipe
447   load("def foo [\n"
448        "  local-scope\n"
449        "  <label>\n"
450        "]\n"
451        "after <label> [\n"
452        "  1:num/raw <- copy 34\n"
453        "]\n");
454   transform_all();
455   CHECK_TRACE_DOESNT_CONTAIN_ERRORS();
456   Hide_errors = true;
457   // try to tangle into recipe foo after transform
458   load("before <label> [\n"
459        "  2:num/raw <- copy 35\n"
460        "]\n");
461   CHECK_TRACE_CONTAINS_ERRORS();
462 }
463 
464 :(before "End before Command Handler")
465 if (contains_key(Fragments_used, label))
466   raise << "we've already tangled some code at label " << label << " in a previous call to transform_all(). Those locations won't be updated.\n" << end();
467 :(before "End after Command Handler")
468 if (contains_key(Fragments_used, label))
469   raise << "we've already tangled some code at label " << label << " in a previous call to transform_all(). Those locations won't be updated.\n" << end();