# Mu - 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();