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 
from strutils import endsWith, split
from os import isAbsolute

proc checkMsg*(msg, expectedEnd, name: string, absolute = true)=
  let filePrefix = msg.split(' ', maxSplit = 1)[0]
  if absolute and not filePrefix.isAbsolute:
    echo name, ":not absolute: `", msg & "`"
  elif not msg.endsWith expectedEnd:
    echo name, ":expected suffix:\n`" & expectedEnd & "`\ngot:\n`" & msg & "`"
  else:
    echo name, ":ok"
ineNr"> 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();