Lumiera 0.pre.04
»edit your freedom«
Loading...
Searching...
No Matches
tree-mutator-binding-test.cpp
Go to the documentation of this file.
1/*
2 TreeMutatorBinding(Test) - techniques to map generic changes onto concrete tree shaped data
3
4 Copyright (C)
5 2016, Hermann Vosseler <Ichthyostega@web.de>
6
7  **Lumiera** is free software; you can redistribute it and/or modify it
8  under the terms of the GNU General Public License as published by the
9  Free Software Foundation; either version 2 of the License, or (at your
10  option) any later version. See the file COPYING for further details.
11
12* *****************************************************************/
13
19#include "lib/test/run.hpp"
20#include "lib/format-util.hpp"
26#include "lib/format-cout.hpp"
27#include "lib/format-util.hpp"
28#include "lib/error.hpp"
29#include "lib/util.hpp"
30
31#include <vector>
32#include <string>
33
34using util::join;
35using util::isnil;
36using util::contains;
37using util::stringify;
39using lib::time::Time;
40using std::string;
41
42using util::typeStr;
43
44
45namespace lib {
46namespace diff{
47namespace test{
48
49 using LERR_(LOGIC);
50
51
52 namespace {//Test fixture....
53
54 // define some GenNode elements
55 // to act as templates within the concrete diff
56 // NOTE: everything in this diff language is by-value
57 const GenNode ATTRIB1("α", 1), // attribute α = 1
58 ATTRIB2("β", int64_t(2)), // attribute α = 2L (int64_t)
59 ATTRIB3("γ", 3.45), // attribute γ = 3.45 (double)
60 TYPE_X("type", "ξ"), // a "magic" type attribute "Xi"
61 TYPE_Z("type", "ζ"), //
62 CHILD_A("a"), // unnamed string child node
63 CHILD_B('b'), // unnamed char child node
64 CHILD_T(Time(12,34,56,78)), // unnamed time value child
65 SUB_NODE = MakeRec().genNode(), // empty anonymous node used to open a sub scope
66 ATTRIB_NODE = MakeRec().genNode("δ"), // empty named node to be attached as attribute δ
67 GAMMA_PI("γ", 3.14159265); // happens to have the same identity (ID) as ATTRIB3
68
69 }//(End)Test fixture
70
71
72
73
74
75
76
77
78
79 /********************************************************************************/
145 class TreeMutatorBinding_test : public Test
146 {
147
148 virtual void
149 run (Arg)
150 {
151 mutateDummy();
155 }
156
157
159 void
161 {
163 TestMutationTarget target;
164 auto mutator =
166 .attachDummy (target);
167 mutator.init();
168
169 CHECK (isnil (target));
170 CHECK (not mutator.hasSrc());
171
172 mutator.injectNew (ATTRIB1);
173 CHECK (!isnil (target));
174 CHECK (contains(target.showContent(), "α = 1"));
175 CHECK (target.verifyEvent("injectNew","α = 1")
176 .after("attachMutator"));
177
178 mutator.injectNew (ATTRIB3);
179 mutator.injectNew (ATTRIB3);
180 mutator.injectNew (CHILD_B);
181 mutator.injectNew (CHILD_B);
182 mutator.injectNew (CHILD_T);
183 CHECK (mutator.completeScope());
184 CHECK (target.verify("attachMutator")
185 .beforeEvent("injectNew","α = 1")
186 .beforeEvent("injectNew","γ = 3.45")
187 .beforeEvent("injectNew","γ = 3.45")
188 .beforeEvent("injectNew","b")
189 .beforeEvent("injectNew","b")
190 .beforeEvent("injectNew","78:56:34.012")
191 .beforeEvent("completeScope","scope completed")
192 );
193 CHECK (target.showContent() == "α = 1, γ = 3.45, γ = 3.45, b, b, 78:56:34.012");
194 cout << "Content after population; "
195 << target.showContent() <<endl;
196
197
198 // now attach new mutator for second round...
199 auto mutator2 =
201 .attachDummy (target);
202 mutator2.init();
203
204 CHECK (target.verify("attachMutator")
205 .beforeEvent("injectNew","78:56:34.012")
206 .before("attachMutator"));
207
208 CHECK (isnil (target)); // the "visible" new content is still void
209 CHECK (mutator2.hasSrc()); // content was moved into hidden "src" buffer
210 CHECK (target.showSrcBuffer() == "α = 1, γ = 3.45, γ = 3.45, b, b, 78:56:34.012");
211
212 CHECK (mutator2.matchSrc (ATTRIB1)); // current head element of src "matches" the given spec
213 CHECK (isnil (target)); // the match didn't change anything
214
215 CHECK (mutator2.accept_until(Ref::ATTRIBS)); // accept_until
216 CHECK (mutator2.hasSrc());
217 CHECK (!isnil (target)); // the fast forward did accept some entries
218 CHECK (target.showContent() == "α = 1, γ = 3.45, γ = 3.45");
219 CHECK (mutator2.matchSrc (CHILD_B)); // ...and we're located behind the attributes, at first child
220
221 mutator2.injectNew (ATTRIB2); // injectNew
222 CHECK (target.showContent() == "α = 1, γ = 3.45, γ = 3.45, β = 2");
223
224 // now proceeding with the children.
225 // NOTE: the TestWireTap / TestMutationTarget does not enforce the attribute / children distinction!
226 CHECK (mutator2.hasSrc()); // still located behind the attributes...
227 CHECK (mutator2.matchSrc (CHILD_B)); // first child waiting in src is CHILD_B
228 mutator2.skipSrc (CHILD_B); // ...which will be skipped (and thus discarded) // skipSrc
229 mutator2.injectNew (SUB_NODE); // inject a new nested sub-structure here // injectNew
230 CHECK (mutator2.matchSrc (CHILD_B)); // yet another B-child is waiting
231 CHECK (not mutator2.findSrc (CHILD_A)); // unsuccessful find operation won't do anything
232 CHECK (mutator2.hasSrc());
233 CHECK (mutator2.matchSrc (CHILD_B)); // child B still waiting, unaffected
234 CHECK (not mutator2.acceptSrc (CHILD_T)); // refusing to accept/pick a non matching element
235 CHECK (mutator2.matchSrc (CHILD_B)); // child B still patiently waiting, unaffected
236 CHECK (mutator2.hasSrc());
237 CHECK (mutator2.findSrc (CHILD_T)); // search for an element further down into src... // findSrc
238 CHECK (!isnil (target)); // ...pick and accept it into the "visible" part of target
239 CHECK (target.showContent() == "α = 1, γ = 3.45, γ = 3.45, β = 2, Rec(), 78:56:34.012");
240
241 CHECK (mutator2.matchSrc (CHILD_B)); // element at head of src is still CHILD_B (as before)
242 CHECK (mutator2.acceptSrc (CHILD_B)); // now pick and accept this src element as child // acceptSrc
243
244 CHECK (mutator2.hasSrc()); // next we have to clean up waste
245 mutator2.skipSrc (CHILD_T); // left behind by the findSrc() operation // skipSrc
246 CHECK (target.showContent() == "α = 1, γ = 3.45, γ = 3.45, β = 2, Rec(), 78:56:34.012, b");
247 CHECK (not mutator2.hasSrc()); // source contents exhausted
248 CHECK (not mutator2.acceptSrc (CHILD_T));
249 CHECK (mutator2.completeScope()); // no pending elements left, everything resolved
250 CHECK (target.verify("attachMutator")
251 .beforeEvent("injectNew","78:56:34.012")
252 .before("attachMutator")
253 .beforeEvent("accept_until after ATTRIBS","α = 1")
254 .beforeEvent("accept_until after ATTRIBS","γ = 3.45")
255 .beforeEvent("accept_until after ATTRIBS","γ = 3.45")
256 .beforeEvent("injectNew","β = 2")
257 .beforeEvent("skipSrc","b")
258 .beforeEvent("injectNew","Rec()")
259 .beforeEvent("findSrc","78:56:34.012")
260 .beforeEvent("acceptSrc","b")
261 .beforeEvent("skipSrc","⟂")
262 .beforeEvent("completeScope","scope completed / 6 waste elm(s)")
263 );
264 CHECK (target.showContent() == "α = 1, γ = 3.45, γ = 3.45, β = 2, Rec(), 78:56:34.012, b");
265 cout << "Content after reordering; "
266 << target.showContent() <<endl;
267
268
269
270 // the third round will cover tree mutation primitives...
271 auto mutator3 =
273 .attachDummy (target);
274 mutator3.init();
275
276 // the first thing we try out is how to navigate through the sequence partially
277 CHECK (isnil (target));
278 CHECK (mutator3.matchSrc (ATTRIB1)); // new mutator starts out anew at the beginning
279 CHECK (mutator3.accept_until (CHILD_T)); // fast forward behind the second-last child (CHILD_T) // accept_until
280 CHECK (mutator3.matchSrc (CHILD_B)); // this /would/ be the next source element...
281 CHECK (not mutator3.completeScope()); // CHILD_B is still pending, not done yet...
282 CHECK (mutator3.accept_until (Ref::END)); // fast forward, since we do not want to re-order anything // accept_until
283 CHECK ( mutator3.completeScope()); // now any pending elements where default-resolved
284
285 // next thing will be an assignment to some element by ID
286 CHECK (not contains(target.showContent(), "γ = 3.1415927"));
287 CHECK (mutator3.assignElm(GAMMA_PI)); // ...we assign a new payload to the current element first // assignElm
288 CHECK ( contains(target.showContent(), "γ = 3.1415927"));
289 CHECK ( mutator3.completeScope()); // now any pending elements where default-resolved
290 cout << "Content after assignment; "
291 << target.showContent() <<endl;
292
293 // for mutation of an enclosed scope, in real usage the managing TreeDiffInterpreter
294 // would maintain a stack of "mutation frames", where each one provides an OpaqueHolder
295 // to place a suitable sub-mutator for this nested scope. At this point, we can't get any further
296 // with this TestWireTap / TestMutationTarget approach, since the latter just records actions and
297 // otherwise forwards operation to the rest of the TreeMutator. In case there is no /real/ mutator
298 // in any "onion layer" below the TestWireTap within this TreeMutator, we'll just get a default (NOP)
299 // implementation of TreeMutator without any further functionality.
300
301 InPlaceBuffer<TreeMutator, sizeof(mutator3)> subMutatorBuffer;
302 TreeMutator::Handle placementHandle(subMutatorBuffer);
303
304 CHECK (mutator3.mutateChild (SUB_NODE, placementHandle)); // mutateChild
305 CHECK (not subMutatorBuffer->hasSrc()); // ...this is all we can do here
306 // the real implementation would instead find a suitable
307 // sub-mutator within this buffer and recurse into that.
308
309 // error handling: assignment might throw
310 GenNode differentTime{CHILD_T.idi.getSym(), Time(11,22)};
311 VERIFY_ERROR (LOGIC, mutator3.assignElm (differentTime));
312
313 CHECK (target.showContent() == "α = 1, γ = 3.1415927, γ = 3.45, β = 2, Rec(), 78:56:34.012, b");
314 CHECK (target.verifyEvent("findSrc","78:56:34.012")
315 .beforeEvent("acceptSrc","b")
316 .before("attachMutator TestWireTap")
317 .beforeEvent("accept_until _CHILD_Time.","α = 1")
318 .beforeEvent("accept_until _CHILD_Time.","γ = 3.45")
319 .beforeEvent("accept_until _CHILD_Time.","γ = 3.45")
320 .beforeEvent("accept_until _CHILD_Time.","β = 2")
321 .beforeEvent("accept_until _CHILD_Time.","Rec()")
322 .beforeEvent("accept_until _CHILD_Time.","78:56:34.012")
323 .beforeEvent("completeScope","scope NOT completed")
324 .beforeEvent("accept_until END","b")
325 .beforeEvent("completeScope","scope completed / 7 waste elm(s)")
326 .beforeEvent("assignElm","γ: 3.45 ⤅ 3.1415927")
327 .beforeEvent("completeScope","scope completed / 7 waste elm(s)")
328 .beforeEvent("mutateChild","start mutation...Rec()")
329 );
330
331 cout << "____Mutation-Log______________\n"
332 << join(target.getLog(), "\n")
333 << "\n───╼━━━━━━━━━╾────────────────"<<endl;
334 }
335
336
337
338
339
353 void
355 {
357
358 // private data structures to be mutated
359 struct Data
360 {
361 string key;
362 string val;
363
364 operator string() const { return _Fmt{"≺%s∣%s≻"} % key % val; }
365 bool operator== (Data const& o) const { return key==o.key and val==o.val; }
366 bool operator!= (Data const& o) const { return not (*this == o); }
367 };
368
369 using VecD = std::vector<Data>;
370 using MapD = std::map<string, VecD>;
371
372 VecD target;
373 MapD subScopes;
374
375 // now set up a binding to these opaque private structures...
376 auto mutator1 =
378 .attach (collection(target)
379 .constructFrom ([&](GenNode const& spec) -> Data
380 {
381 cout << "constructor invoked on "<<spec<<endl;
382 return {spec.idi.getSym(), render(spec.data)};
383 }));
384
385 CHECK (sizeof(mutator1) <= sizeof(VecD) // the buffer for pending elements
386 + sizeof(VecD*) // the reference to the original collection
387 + 2 * sizeof(VecD::iterator) // one Lumiera RangeIter (comprised of pos and end iterators)
388 + 4 * sizeof(void*) // the four unused default configured binding functions
389 + 1 * sizeof(void*) // one back reference from the closure to this scope
390 + sizeof(void*)); // the TreeMutator VTable
391
392
393 // --- first round: populate the collection ---
394
395 mutator1.init();
396
397 CHECK (isnil (target));
398 CHECK (not mutator1.hasSrc());
399
400 mutator1.injectNew (ATTRIB1);
401 CHECK (!isnil (target));
402 CHECK (contains(join(target), "≺α∣1≻"));
403
404 mutator1.injectNew (ATTRIB3);
405 mutator1.injectNew (ATTRIB3);
406 mutator1.injectNew (CHILD_B);
407 mutator1.injectNew (CHILD_B);
408 mutator1.injectNew (CHILD_T);
409 CHECK (mutator1.completeScope());
410
411 auto contents = stringify(eachElm(target));
412 CHECK ("≺α∣1≻" == *contents);
413 ++contents;
414 CHECK ("≺γ∣3.45≻" == *contents);
415 ++contents;
416 CHECK ("≺γ∣3.45≻" == *contents);
417 ++contents;
418 CHECK (contains(*contents, "∣b≻"));
419 ++contents;
420 CHECK (contains(*contents, "∣b≻"));
421 ++contents;
422 CHECK (contains(*contents, "∣78:56:34.012≻"));
423 ++contents;
424 CHECK (isnil (contents));
425
426 cout << "injected......" << join(target) <<endl;
427
428
429 // --- second round: reorder the collection ---
430
431
432 // Mutators are one-time disposable objects,
433 // thus we'll have to build a new one for the second round...
434 auto mutator2 =
436 .attach (collection(target)
437 .constructFrom ([&](GenNode const& spec) -> Data
438 {
439 cout << "constructor invoked on "<<spec<<endl;
440 return {spec.idi.getSym(), render(spec.data)};
441 })
442 .matchElement ([&](GenNode const& spec, Data const& elm)
443 {
444 cout << "match? "<<spec.idi.getSym()<<"=?="<<elm.key<<endl;
445 return spec.idi.getSym() == elm.key;
446 }));
447
448 // we have two lambdas now and thus can save on the size of one function pointer....
449 CHECK (sizeof(mutator1) - sizeof(mutator2) == sizeof(void*));
450
451 mutator2.init();
452 CHECK (isnil (target)); // the "visible" new content is still void
453
454 CHECK (mutator2.matchSrc (ATTRIB1)); // current head element of src "matches" the given spec
455 CHECK (isnil (target)); // the match didn't change anything
456
457 CHECK (mutator2.accept_until(Ref::ATTRIBS));
458 CHECK (mutator2.matchSrc (ATTRIB1)); // NOTE: collection values can be anything; thus this
459 // collection binding layer can not have any notion of
460 // "this is an attribute". It will not accept anything
461 // and just delegate to the next lower layer, which here
462 // is the empty binding and thus finally returns true
463
464 CHECK (mutator2.accept_until(ATTRIB3)); // ...but of course we can fast forward to dedicated values // accept_until
465 CHECK (!isnil (target)); // the fast forward did indeed accept some entries
466 CHECK (mutator2.acceptSrc(ATTRIB3)); // we have a duplicate in list, need to accept that as well // accept
467 CHECK (mutator2.hasSrc());
468 CHECK (mutator2.matchSrc (CHILD_B)); // ...now we're located behind the attributes, at first child
469 mutator2.injectNew (ATTRIB2); // injectNew
470
471 CHECK (mutator2.matchSrc (CHILD_B)); // first child waiting in src is CHILD_B
472 mutator2.skipSrc (CHILD_B); // ...which will be skipped (and thus discarded) // skipSrc
473 mutator2.injectNew (SUB_NODE); // inject a nested sub-structure (implementation defined) // injectNew
474 CHECK (mutator2.matchSrc (CHILD_B)); // yet another B-child is waiting
475 CHECK (not mutator2.findSrc (CHILD_A)); // unsuccessful find operation won't do anything
476 CHECK (mutator2.hasSrc());
477 CHECK (mutator2.matchSrc (CHILD_B)); // child B still waiting, unaffected
478 CHECK (not mutator2.acceptSrc (CHILD_T)); // refusing to accept/pick a non matching element
479 CHECK (mutator2.matchSrc (CHILD_B)); // child B still patiently waiting, unaffected
480 CHECK (mutator2.hasSrc());
481 CHECK (mutator2.findSrc (CHILD_T)); // search for an element further down into src... // findSrc
482 CHECK (mutator2.matchSrc (CHILD_B)); // element at head of src is still CHILD_B (as before)
483 CHECK (mutator2.acceptSrc (CHILD_B)); // now pick and accept this src element as child // acceptSrc
484
485 CHECK (mutator2.hasSrc()); // next we have to clean up waste
486 mutator2.skipSrc (CHILD_T); // left behind by the findSrc() operation // skipSrc
487 CHECK (not mutator2.hasSrc()); // source contents exhausted
488 CHECK (not mutator2.acceptSrc (CHILD_T)); // ...anything beyond is NOP
489 CHECK (mutator2.completeScope()); // no pending elements left, everything resolved
490
491 // verify reordered shape
492 contents = stringify(eachElm(target));
493 CHECK ("≺α∣1≻" == *contents);
494 ++contents;
495 CHECK ("≺γ∣3.45≻" == *contents);
496 ++contents;
497 CHECK ("≺γ∣3.45≻" == *contents);
498 ++contents;
499 CHECK ("≺β∣2≻" == *contents);
500 ++contents;
501 CHECK (contains(*contents, "∣Rec()≻"));
502 ++contents;
503 CHECK (contains(*contents, "∣78:56:34.012≻"));
504 ++contents;
505 CHECK (contains(*contents, "∣b≻"));
506 ++contents;
507 CHECK (isnil (contents));
508
509 cout << "Content after reordering...."
510 << join(target) <<endl;
511
512
513 // --- third round: mutate data and sub-scopes ---
514
515
516 // This time we build the Mutator bindings in a way to allow mutation
517 // For one, "mutation" means to assign a changed value to a simple node / attribute.
518 // And beyond that, mutation entails to open a nested scope and delve into that recursively.
519 // Here, as this is really just a test and demonstration, we implement those nested scopes aside,
520 // managed within a map and keyed by the sub node's ID.
521 auto mutator3 =
523 .attach (collection(target)
524 .constructFrom ([&](GenNode const& spec) -> Data
525 {
526 cout << "constructor invoked on "<<spec<<endl;
527 return {spec.idi.getSym(), render(spec.data)};
528 })
529 .matchElement ([&](GenNode const& spec, Data const& elm) -> bool
530 {
531 cout << "match? "<<spec.idi.getSym()<<"=?="<<elm.key<<endl;
532 return spec.idi.getSym() == elm.key;
533 })
534 .assignElement ([&](Data& target, GenNode const& spec) -> bool
535 {
536 cout << "assign "<<target<<" <- "<<spec<<endl;
537 CHECK (target.key == spec.idi.getSym(), "assignment to target with wrong identity");
538 target.val = render(spec.data);
539 return true;
540 })
541 .buildChildMutator ([&](Data& target, GenNode::ID const& subID, TreeMutator::Handle buff) -> bool
542 {
543 // use our "inside knowledge" to get at the nested scope implementation
544 VecD& subScope = subScopes[subID];
545 buff.emplace (
547 .attach (collection(subScope)
548 .constructFrom ([&](GenNode const& spec) -> Data
549 {
550 cout << "SubScope| constructor invoked on "<<spec<<endl;
551 return {spec.idi.getSym(), render(spec.data)};
552 })));
553
554 // NOTE: mutation of sub scope has not happened yet
555 // we can only document the sub scope to be opened now
556 cout << "openSub("<<subID.getSym()<<") ⟻ "<<target<<endl;
557 target.val = "Rec(--"+subID.getSym()+"--)";
558 return true;
559 }));
560
561 mutator3.init();
562
563 CHECK (isnil (target));
564 CHECK (mutator3.matchSrc (ATTRIB1)); // new mutator starts out anew at the beginning
565 CHECK (mutator3.accept_until (CHILD_T)); // fast forward behind the second-last child (CHILD_T) // accept_until
566 CHECK (mutator3.matchSrc (CHILD_B)); // this /would/ be the next source element, but rather...
567 CHECK (not mutator3.completeScope()); // CHILD_B is still pending, not done yet...
568 CHECK (mutator3.accept_until (Ref::END)); // fast forward, since we do not want to re-order anything // accept_until
569 CHECK ( mutator3.completeScope()); // now any pending elements where default-resolved
570
571 CHECK (not contains(join(target), "≺γ∣3.1415927≻"));
572 CHECK (mutator3.assignElm(GAMMA_PI)); // ...we assign a new payload to the designated element // assignElm
573 CHECK ( contains(join(target), "≺γ∣3.1415927≻"));
574 CHECK ( mutator3.completeScope());
575 cout << "Content after assignment...."
576 << join(target) <<endl;
577
578
579 // prepare for recursion into sub scope..
580 // Since this is a demonstration, we do not actually recurse into anything,
581 // rather we invoke the operations on a nested mutator right from here.
582
583 InPlaceBuffer<TreeMutator, sizeof(mutator1)> subMutatorBuffer;
584 TreeMutator::Handle placementHandle(subMutatorBuffer);
585
586 CHECK (mutator3.mutateChild (SUB_NODE, placementHandle)); // mutateChild
587
588 CHECK (isnil (subScopes[SUB_NODE.idi])); // ...this is where the nested mutator is expected to work on
589 CHECK (not subMutatorBuffer->hasSrc());
590
591 // now use the Mutator *interface* to talk to the nested mutator...
592 // This code might be confusing, because in fact we're playing two roles here!
593 // For one, above, in the definition of mutator3 and in the declaration of MapD subScopes,
594 // the test code represents what a private data structure and binding would do.
595 // But below we enact the TreeDiffAplicator, which *would* use the Mutator interface
596 // to talk to an otherwise opaque nested mutator implementation. Actually, here this
597 // nested opaque mutator is created on-the-fly, embedded within the .buildChildMutator(..lambda...)
598 // Incidentally, we "just happen to know" how large the buffer needs to be to hold that mutator,
599 // since this is a topic beyond the scope of this test. In real usage, the DiffApplicator cares
600 // to provide a stack of suitably sized buffers for the nested mutators.
601
602 subMutatorBuffer->injectNew (TYPE_X); // >> // injectNew
603 subMutatorBuffer->injectNew (ATTRIB2); // >> // injectNew
604 subMutatorBuffer->injectNew (CHILD_B); // >> // injectNew
605 subMutatorBuffer->injectNew (CHILD_A); // >> // injectNew
606
607 CHECK (not isnil (subScopes[SUB_NODE.idi])); // ...and "magically" these instructions happened to insert
608 cout << "Sub|" << join(subScopes[SUB_NODE.idi]) <<endl; // some new content into our implementation defined sub scope!
609
610 // verify contents of nested scope after mutation
611 contents = stringify(eachElm(subScopes[SUB_NODE.idi]));
612 CHECK ("≺type∣ξ≻" == *contents);
613 ++contents;
614 CHECK ("≺β∣2≻" == *contents);
615 ++contents;
616 CHECK (contains(*contents, "∣b≻"));
617 ++contents;
618 CHECK (contains(*contents, "∣a≻"));
619 ++contents;
620 CHECK (isnil (contents));
621
622
623 // now back to parent scope....
624 // ...add a new attribute and immediately recurse into it
625 mutator3.injectNew (ATTRIB_NODE);
626 CHECK (mutator3.mutateChild (ATTRIB_NODE, placementHandle)); // NOTE: we're just recycling the buffer. InPlaceHolder handles lifecycle properly
627 subMutatorBuffer->injectNew (TYPE_Z);
628 subMutatorBuffer->injectNew (CHILD_A);
629 subMutatorBuffer->injectNew (CHILD_A);
630 subMutatorBuffer->injectNew (CHILD_A);
631 CHECK (subMutatorBuffer->completeScope()); // no pending "open ends" left in sub-scope
632 CHECK (mutator3.completeScope()); // and likewise in the enclosing main scope
633
634 // and thus we've gotten a second nested scope, populated with new values
635 cout << "Sub|" << join(subScopes[ATTRIB_NODE.idi]) <<endl;
636
637 // verify contents of this second nested scope
638 contents = stringify(eachElm(subScopes[ATTRIB_NODE.idi]));
639 CHECK ("≺type∣ζ≻" == *contents);
640 ++contents;
641 CHECK (contains(*contents, "∣a≻"));
642 ++contents;
643 CHECK (contains(*contents, "∣a≻"));
644 ++contents;
645 CHECK (contains(*contents, "∣a≻"));
646 ++contents;
647 CHECK (isnil (contents));
648
649
650 // back to parent scope....
651 // verify the marker left by our "nested sub-scope lambda"
652 CHECK (contains (join(target), "Rec(--"+SUB_NODE.idi.getSym()+"--)"));
653 CHECK (contains (join(target), "Rec(--"+ATTRIB_NODE.idi.getSym()+"--)"));
654
655 cout << "Content after nested mutation...."
656 << join(target) <<endl;
657 }
658
659
660
661
662
671 void
673 {
675
676 // local data fields to be handled as "attributes"
677 int alpha = -1;
678 int64_t beta = -1;
679 double gamma = -1;
680
681 // we'll use this as an attribute with nested scope ("object valued attribute")
682 TestMutationTarget delta;
683
684
685 #define LOG_SETTER(KEY) cout << STRINGIFY(KEY) " := "<<val<<endl;
686
687
688 // set up a binding to these opaque private structures...
689 auto mutator1 =
691 .change("α", [&](int val)
692 {
693 LOG_SETTER ("alpha")
694 alpha = val;
695 })
696 .change("γ", [&](double val)
697 {
698 LOG_SETTER ("gamma")
699 gamma = val;
700 });
701 mutator1.init();
702
703 CHECK (sizeof(mutator1) <= sizeof(void*) // the TreeMutator VTable
704 + 2 * sizeof(void*) // one closure reference for each lambda
705 + 2 * sizeof(GenNode::ID)); // one attribute-key for each binding
706
707
708
709 // --- first round: introduce new "attributes" ---
710
711 CHECK (-1 == alpha);
712 CHECK (-1 == beta);
713 CHECK (-1 == gamma);
714
715 CHECK (not mutator1.hasSrc()); // NOTE: the attribute binding has no "reference source sequence" and thus no dynamic state.
716 // (in fact it is predetermined, because it relies on a likewise fixed class definition)
717 CHECK (mutator1.completeScope()); // NOTE: this is always true and NOP, for the same reason: the structure of the binding is fixed
718
719 mutator1.injectNew (ATTRIB1);
720 CHECK ( 1 == alpha);
721 CHECK (-1 == beta);
722 CHECK (-1 == gamma);
723
724 mutator1.injectNew (ATTRIB3);
725 CHECK ( 1 == alpha);
726 CHECK (-1 == beta);
727 CHECK (3.45 == gamma);
728
729 mutator1.injectNew (ATTRIB3);
730 CHECK ( 1 == alpha);
731 CHECK (-1 == beta);
732 CHECK (3.45 == gamma);
733
734 CHECK (not mutator1.injectNew (ATTRIB2)); // ...because we didn't define a binding for ATTRIB2 (aka "beta")
735
736 // any changes to something other than attributes are just delegated to the next "onion layer"
737 // since in this case here, there is only one layer (our attribute binding), these other changes will be ignored silently
738 mutator1.injectNew (CHILD_B);
739 mutator1.injectNew (CHILD_B);
740 mutator1.injectNew (CHILD_T);
741 CHECK (mutator1.completeScope()); // this invocation typically happens at this point, but is NOP (see above)
742
743 CHECK ( 1 == alpha);
744 CHECK (-1 == beta);
745 CHECK (3.45 == gamma);
746 cout << "successfully 'injected' new attributes." <<endl;
747
748
749
750 // --- second round: reordering ---
751
752
753 // in fact any re-ordering of "attributes" is prohibited,
754 // because "attributes" are mapped to object or data fields,
755 // which are fixed by definition and don't expose any ordering.
756 // While any mutations beyond attributes are passed on / ignored
757 auto mutator2 =
759 .change("α", [&](int val)
760 {
761 LOG_SETTER ("alpha")
762 alpha = val;
763 })
764 .change("β", [&](int64_t val)
765 {
766 LOG_SETTER ("beta")
767 beta = val;
768 })
769 .change("γ", [&](double val)
770 {
771 LOG_SETTER ("gamma")
772 gamma = val;
773 });
774 mutator2.init();
775
776
777 CHECK (sizeof(mutator2) <= sizeof(void*) // the TreeMutator VTable
778 + 3 * sizeof(void*) // one closure reference for each lambda
779 + 3 * sizeof(GenNode::ID)); // one attribute-key for each binding
780
781
782
783 CHECK ( 1 == alpha);
784 CHECK (-1 == beta);
785 CHECK (3.45 == gamma); // values not affected by attaching a new mutator
786
787 CHECK (mutator2.matchSrc (ATTRIB1)); // this "match" is positive, since our binding supports this attribute
788 CHECK ( 1 == alpha); // the (NOP) match didn't change anything...
789 CHECK (-1 == beta);
790 CHECK (3.45 == gamma);
791
792 VERIFY_ERROR (LOGIC, mutator2.findSrc (ATTRIB3));
793 // search for an element and thus reordering is explicitly rejected...
794 // If we hadn't defined a binding for "γ", then the same operation
795 // would have been passed on silently to other binding layers.
796
797 CHECK (mutator2.matchSrc (ATTRIB1)); // behaviour of the binding remains unaffected
798 CHECK (mutator2.acceptSrc (ATTRIB1)); // now pick and "accept" this src element (also a NOP) // acceptSrc
799
800 VERIFY_ERROR (LOGIC, mutator2.skipSrc (ATTRIB3));
801 // and 'skip' likewise is just not implemented for attributes // skipSrc
802 CHECK ( 1 == alpha);
803 CHECK (-1 == beta);
804 CHECK (3.45 == gamma); // all these non-operations actually didn't change anything...
805
806 CHECK (mutator2.accept_until(Ref::ATTRIBS)); // accept_until ATTRIBS
807 // what /is/ allowed though, for reasons of logic,
808 // is to "fast forward behind all attributes"
809 // of course this is implemented as NOP
810 CHECK (mutator2.accept_until(Ref::END)); // likewise for Ref::END // accept_until END
811
812 mutator2.injectNew (ATTRIB2); // injectNew
813
814 CHECK ( 1 == alpha);
815 CHECK ( 2 == beta); // the first operation actually causing a tangible effect
816 CHECK (3.45 == gamma);
817
818
819 // for sake of completeness, we'll be applying the same sequence of operations as in the other tests
820 // but since all those operations are not relevant for our attribute binding, they will be passed on
821 // to lower binding layers. And since, moreover, there /are no lower binding layers/ in our setup,
822 // they will just do nothing and return false
823 CHECK (not mutator2.matchSrc (CHILD_B));
824 mutator2.skipSrc (CHILD_B); // ...no setter binding, thus no effect // skipSrc
825 CHECK (not mutator2.injectNew (SUB_NODE));// ...no setter binding, thus no effect // injectNew
826 CHECK (not mutator2.matchSrc (CHILD_B));
827 CHECK (not mutator2.findSrc (CHILD_T)); // find for non-attribute is just passed down // findSrc
828 CHECK (not mutator2.acceptSrc (CHILD_B)); // acceptSrc
829 mutator2.skipSrc (CHILD_T); // skipSrc
830
831 CHECK ( 1 == alpha);
832 CHECK ( 2 == beta);
833 CHECK (3.45 == gamma); // no further effect on our attribute fields
834
835 cout << "ignored all 'reordering' operations (as expected)..." <<endl;
836
837
838
839 // --- third round: mutate data and sub-scopes ---
840
841
842 // This third part of the test covers the actual purpose of attribute binding:
843 // the ability to assign values or even to open a sub-scope enabling recursion
844 // into a nested object stored within a data field.
845 auto mutator3 =
847 .change("γ", [&](double val)
848 {
849 LOG_SETTER ("gamma")
850 gamma = val;
851 })
852 .mutateAttrib("δ", [&](TreeMutator::Handle buff)
853 {
854 // NOTE: we use "implementation inside knowledge" regarding the nested scope,
855 // which is here represented as TestMutationTarget
856 buff.emplace (
858 .attachDummy (delta));
859
860 // NOTE: when this closure is invoked, we're about to open the sub scope,
861 // while mutation has not happened yet
862 cout << "openSub()...\n"
863 << join(delta.getLog(), "\n") <<endl;
864 });
865 mutator3.init();
866
867 CHECK (sizeof(mutator1) <= sizeof(void*) // the TreeMutator VTable
868 + 2 * sizeof(void*) // one closure reference for each lambda
869 + 2 * sizeof(GenNode::ID)); // one attribute-key for each binding
870
871
872
873 VERIFY_ERROR (LOGIC, mutator3.accept_until (ATTRIB3)); // rejected; no support for ordering // accept_until
874 CHECK (not mutator3.accept_until (ATTRIB2)); // unknown binding, no one is responsible
875 CHECK (not mutator3.accept_until (ATTRIB1));
876 CHECK (mutator3.accept_until (Ref::ATTRIBS)); // only the generic end-of-scope marks supported
877 CHECK (mutator3.accept_until (Ref::END)); // (and implemented as NOP plus forwarding down)
878
879 // explanation: due to the nature of a 'data field',
880 // this binding has no notion of 'ordering' and thus no 'current position'.
881 // Rather, the decision if some diff verb is applicable can be done statically.
882 CHECK (mutator3.completeScope()); // always true (for the same reason)
883
884
885 CHECK (not mutator3.acceptSrc (ATTRIB1));
886 CHECK (not mutator3.acceptSrc (ATTRIB2));
887 CHECK ( mutator3.acceptSrc (ATTRIB3)); // in this round we just have a binding for ATTRIB3 (== "γ")
888 CHECK ( mutator3.acceptSrc (ATTRIB_NODE));
889 // ...and of course a binding for a nested ATTRIB_NODE
890
891 CHECK (3.45 == gamma);
892 CHECK (mutator3.assignElm(GAMMA_PI)); // ...we assign a new payload to the current element first // assignElm
893 CHECK (3.14159265 == gamma);
894 CHECK ( 1 == alpha); // the other fields remain unaffected
895 CHECK ( 2 == beta);
896
897 cout << "successfully assigned a new value." <<endl;
898
899
900 // prepare for recursion into sub scope...
901 // In this demonstration, the nested scope is declared to live within an attribute `ATTRIB_NODE` (== "δ").
902 // It is implemented as `TestMutationTarget delta`, which allows us to verify a fully operational nested mutator.
903
904 const size_t BUFF_SIZ = sizeof(TreeMutator::build().attachDummy (delta));
905 // use some suitable size here, not the point in focus for this test
906
908 TreeMutator::Handle placementHandle(subMutatorBuffer);
909
910 CHECK (mutator3.mutateChild (ATTRIB_NODE, placementHandle)); // mutateChild
911
912 CHECK (isnil (delta)); // ...this is where the nested mutator is expected to work on
913 CHECK (not subMutatorBuffer->hasSrc());
914
915
916 // now use the Mutator *interface* to talk to the nested mutator...
917 subMutatorBuffer->injectNew (TYPE_X); // >> // injectNew
918 subMutatorBuffer->injectNew (ATTRIB2); // >> // injectNew
919 subMutatorBuffer->injectNew (CHILD_B); // >> // injectNew
920 subMutatorBuffer->injectNew (CHILD_A); // >> // injectNew
921
922 CHECK (not isnil (delta)); // ...and "magically" these instructions happened to insert
923 cout << "Sub|" << delta.showContent() <<endl; // some new content into our implementation defined sub scope!
924 cout << "____Mutation-Log(nested)______\n"
925 << join(delta.getLog(), "\n")
926 << "\n───╼━━━━━━━━━╾────────────────"<<endl;
927
928 // verify contents of nested scope after mutation
929 CHECK (delta.showContent() == "type = ξ, β = 2, b, a");
930
931 // verify unaffected parent scope (data fields)
932 CHECK (3.14159265 == gamma);
933 CHECK ( 1 == alpha);
934 CHECK ( 2 == beta);
935 }
936
937
938
939
940
947 void
949 {
951
952 // private target data be mutated
953 Rec::Mutator target;
954
955 // set up a GenNode binding to work on this root node...
956 auto mutator1 =
958 .attach (target);
959 mutator1.init();
960
961
962#if false
964
965 CHECK (sizeof(mutator1) <= 2 * (sizeof(VecG) // we use two collection bindings...
966 +sizeof(VecG*) // with a buffer for pending elements and a reference to the original collection
967 + 2* sizeof(VecG::iterator) // and one Lumiera RangeIter (comprised of pos and end iterators)
968 +sizeof(void*) // the VTable for each layer of TreeMutator impl
969 )
970 + 1 * sizeof(void*)); // plus one unused selector, implemented as pointer to the default impl
976#endif
977
978
979 // --- first round: populate the collection ---
980
981 CHECK (isnil (target));
982 CHECK (not mutator1.hasSrc());
983
984 mutator1.injectNew (ATTRIB1);
985 CHECK (!isnil (target));
986 CHECK (contains(renderRecord(target), "α = 1"));
987
988 mutator1.injectNew (ATTRIB3);
989 mutator1.injectNew (ATTRIB3);
990 mutator1.injectNew (CHILD_B);
991 mutator1.injectNew (CHILD_B);
992 mutator1.injectNew (CHILD_T);
993 CHECK (mutator1.completeScope());
994
995 Rec& root = target;
996
997 CHECK (!isnil (root)); // nonempty -- content has been added
998 CHECK (Rec::TYPE_NIL == root.getType()); // type field was not touched
999 CHECK (1 == root.get("α").data.get<int>()); // has gotten our int attribute "α"
1000 CHECK (3.45 == root.get("γ").data.get<double>()); // ... and double attribute "γ"
1001 auto scope = root.scope(); // look into the scope contents...
1002 CHECK ( *scope == CHILD_B); // there we find is CHILD_B
1003 CHECK (*++scope == CHILD_B); // followed by a second CHILD_B
1004 CHECK (*++scope == CHILD_T); // and another one CHILD_T
1005
1006 cout << "injected...................."
1007 << renderRecord(target)<<endl;
1008
1009
1010
1011 // --- second round: reorder the collection ---
1012
1013
1014 // Mutators are one-time disposable objects,
1015 // thus we'll have to build a new one for the second round...
1016 auto mutator2 =
1018 .attach (target);
1019
1020 mutator2.init();
1021
1022 CHECK (isnil (target)); // old content moved aside, visible new content still void
1023
1024 CHECK (mutator2.matchSrc (ATTRIB1)); // current head element of src "matches" the given spec
1025 CHECK (isnil (target)); // the match didn't change anything
1026
1027 CHECK (mutator2.accept_until(ATTRIB3)); // accept and fast forward behind a given value // accept_until
1028 CHECK (!isnil (target)); // the fast forward did indeed accept some entries
1029
1030 CHECK (mutator2.matchSrc (ATTRIB3)); // we had a duplicate attribute entry (and Record<GenNode>
1031 // indeed represents duplicates), so this is waiting next
1032
1033 CHECK (mutator2.accept_until(Ref::ATTRIBS)); // accept_until
1034
1035 CHECK (mutator2.hasSrc()); // ...we did a "blind" fast forward, accepting all attributes
1036 CHECK (mutator2.matchSrc (CHILD_B)); // thus we're now located behind the attributes, at first child
1037 mutator2.injectNew (ATTRIB2); // ..no one prevents us from injecting another attribute... // injectNew
1038
1039 CHECK (mutator2.matchSrc (CHILD_B)); // first child still waiting in src is CHILD_B
1040 mutator2.skipSrc (CHILD_B); // ...which will be skipped (and thus discarded) // skipSrc
1041 mutator2.injectNew (SUB_NODE); // inject a nested sub-structure (here a Record<GenNode>) // injectNew
1042 CHECK (mutator2.matchSrc (CHILD_B)); // yet another B-child is waiting
1043 CHECK (not mutator2.findSrc (CHILD_A)); // unsuccessful find operation won't do anything
1044 CHECK (mutator2.hasSrc());
1045 CHECK (mutator2.matchSrc (CHILD_B)); // child B still waiting, unaffected
1046 CHECK (not mutator2.acceptSrc (CHILD_T)); // refusing to accept/pick a non matching element
1047 CHECK (mutator2.matchSrc (CHILD_B)); // child B still patiently waiting, unaffected
1048 CHECK (mutator2.hasSrc());
1049 CHECK (mutator2.findSrc (CHILD_T)); // search for an element further down into src... // findSrc
1050 CHECK (mutator2.matchSrc (CHILD_B)); // element at head of src is still CHILD_B (as before)
1051 CHECK (mutator2.acceptSrc (CHILD_B)); // now pick and accept this src element as child // acceptSrc
1052
1053 CHECK (mutator2.hasSrc()); // next we have to clean up waste
1054 mutator2.skipSrc (CHILD_T); // left behind by the findSrc() operation // skipSrc
1055 CHECK (not mutator2.hasSrc()); // source contents exhausted
1056 CHECK (not mutator2.acceptSrc (CHILD_T)); // ...anything beyond is NOP
1057 CHECK (mutator2.completeScope()); // no pending elements left, everything resolved
1058
1059 // verify reordered shape
1060 CHECK (!isnil (root)); // nonempty -- content has been moved back
1061 CHECK (Rec::TYPE_NIL == root.getType()); // type field was not touched
1062 CHECK (1 == root.get("α").data.get<int>()); // all attributes accessible
1063 CHECK (2 == root.get("β").data.get<int64_t>());
1064 CHECK (3.45 == root.get("γ").data.get<double>());
1065 auto attrs = root.attribs(); // verify the sequence of attributes...
1066 CHECK ( *attrs == ATTRIB1); // first attribute "α" was left as it was
1067 CHECK (*++attrs == ATTRIB3); // same for the attribute "γ"
1068 CHECK (*++attrs == ATTRIB3); // ...and its duplicate
1069 CHECK (*++attrs == ATTRIB2); // and here is the newly inserted "β"
1070 CHECK (isnil (++attrs));
1071 scope = root.scope(); // look into the scope contents...
1072 CHECK ( *scope == SUB_NODE); // first the new empty nested child node
1073 CHECK (*++scope == CHILD_T); // but now followed immediately by CHILD_T
1074 CHECK (*++scope == CHILD_B); // while CHILD_B has be shuffled back
1075 CHECK (isnil (++scope)); // ...and that's all
1076
1077 cout << "Content after reordering...."
1078 << renderRecord(target) <<endl;
1079
1080
1081 // --- third round: mutate data and sub-scopes ---
1082
1083
1084 // since our target data here is comprised of GenNode elements,
1085 // we're both able to assign data and to enter a nested Record<GenNode>
1086 // The default configuration is outfitted for this use as-is right away.
1087 auto mutator3 =
1089 .attach (target);
1090
1091 mutator3.init();
1092
1093 CHECK (isnil (target));
1094 CHECK (mutator3.matchSrc (ATTRIB1)); // new mutator starts out anew at the beginning
1095 CHECK (mutator3.accept_until (CHILD_T)); // fast forward behind the second-last child (CHILD_T) // accept_until
1096 CHECK (mutator3.matchSrc (CHILD_B)); // this /would/ be the next source element, but rather...
1097 CHECK (not mutator3.completeScope()); // CHILD_B is still pending, not done yet...
1098 CHECK (mutator3.accept_until (Ref::END)); // fast forward, since we do not want to re-order anything // accept_until
1099 CHECK ( mutator3.completeScope()); // now any pending elements where default-resolved
1100
1101 CHECK (not contains(renderRecord(target), "γ = 3.1415927"));
1102 CHECK (mutator3.assignElm(GAMMA_PI)); // ...we assign a new payload to the designated element // assignElm
1103 CHECK ( contains(renderRecord(target), "γ = 3.1415927"));
1104 CHECK ( mutator3.completeScope());
1105 cout << "Content after assignment...."
1106 << renderRecord(target) <<endl;
1107
1108 // Note: it is up to the implementation of the target data how to deal with duplicate attributes
1109 // Record<GenNode> represents attributes as a list of named sub GenNode elements, and the
1110 // access to attributes uses the first match found
1111 attrs = root.attribs(); // visit all attributes sequentially...
1112 CHECK ( *attrs == ATTRIB1); // first attribute "α" was left as it was
1113 CHECK (*++attrs == GAMMA_PI); // this is where the value assignment happened...
1114 CHECK ( attrs->data.get<double>() == GAMMA_PI.data.get<double>());
1115 CHECK (*++attrs == ATTRIB3); // ...while the duplicate "γ"...
1116 CHECK ( attrs->data.get<double>() == 3.45); // ...still holds the original value
1117 CHECK (*++attrs == ATTRIB2);
1118 CHECK (isnil (++attrs));
1119
1120 // prepare for recursion into sub scope..
1121 // Since this is a demonstration, we do not actually recurse into anything,
1122 // rather we just let the binding generate a nested mutator into some buffer
1123 // and then we invoke the operations this nested mutator right from here.
1124
1125 InPlaceBuffer<TreeMutator, sizeof(mutator1)> subMutatorBuffer;
1126 TreeMutator::Handle placementHandle(subMutatorBuffer);
1127
1128 CHECK (mutator3.mutateChild (SUB_NODE, placementHandle)); // mutateChild
1129
1130 GenNode const& subNode = *root.scope();
1131 CHECK (subNode == SUB_NODE); // ...this is the sub node
1132 CHECK (isnil (subNode.data.get<Rec>())); // where the nested mutator is expected to work on
1133
1134 // now use the Mutator *interface* to talk to the nested mutator...
1135 // which was built and placed into the provided buffer
1136 CHECK (not subMutatorBuffer->hasSrc());
1137 subMutatorBuffer->injectNew (TYPE_X); // >> // injectNew
1138 subMutatorBuffer->injectNew (ATTRIB2); // >> // injectNew
1139 subMutatorBuffer->injectNew (CHILD_B); // >> // injectNew
1140 subMutatorBuffer->injectNew (CHILD_A); // >> // injectNew
1141
1142 Rec const& nestedRec = subNode.data.get<Rec>();
1143 CHECK (not isnil (nestedRec)); // ...and "magically" these instructions happened to insert
1144 cout << "Sub-" << renderNode(subNode) <<endl; // some new content into our implementation defined sub scope!
1145
1146 // verify contents of nested scope after mutation
1147 CHECK ("ξ" == nestedRec.getType()); // type of nested node has been set to Xi
1148 attrs = nestedRec.attribs(); // look into the nested node's attributes...
1149 CHECK ( *attrs == ATTRIB2);
1150 CHECK (isnil (++attrs));
1151 scope = nestedRec.scope(); // look into the nested nodes's scope contents...
1152 CHECK ( *scope == CHILD_B);
1153 CHECK (*++scope == CHILD_A);
1154 CHECK (isnil (++scope)); // ...and that's all
1155
1156
1157 // now back to parent scope....
1158 // ...add a new attribute and immediately recurse into it
1159 mutator3.injectNew (ATTRIB_NODE);
1160 CHECK (mutator3.mutateChild (ATTRIB_NODE, placementHandle)); // NOTE: we're just recycling the buffer. InPlaceHolder handles lifecycle properly
1161 subMutatorBuffer->injectNew (TYPE_X);
1162 subMutatorBuffer->injectNew (CHILD_A);
1163 subMutatorBuffer->injectNew (CHILD_A);
1164 subMutatorBuffer->injectNew (CHILD_A);
1165 subMutatorBuffer->assignElm (TYPE_Z); // NOTE use assignment to *change* the type field
1166 CHECK (subMutatorBuffer->completeScope()); // no pending "open ends" left in sub-scope
1167 CHECK (mutator3.completeScope()); // and likewise in the enclosing main scope
1168
1169 // and thus we've gotten a second nested scope, populated with new values
1170 Rec const& attrRec = root.get("δ").data.get<Rec>();
1171 cout << "Att-" << renderNode(attrRec) <<endl;
1172
1173 // verify contents of this second nested scope
1174 CHECK (not isnil (attrRec));
1175 CHECK ("ζ" == attrRec.getType());
1176 CHECK (isnil (attrRec.attribs()));
1177 scope = attrRec.scope();
1178 CHECK (not isnil (scope));
1179 CHECK ( *scope == CHILD_A);
1180 CHECK (*++scope == CHILD_A);
1181 CHECK (*++scope == CHILD_A);
1182 CHECK (isnil (++scope));
1183
1184
1185 // back to parent scope....
1186 // verify the parent scope indeed contains the nested elements in new shape
1187 CHECK (contains (renderRecord(target), renderRecord(attrRec)));
1188 CHECK (contains (renderRecord(target), renderRecord(nestedRec)));
1189
1190 cout << "Content after sub mutation.."
1191 << renderRecord(target) <<endl;
1192 }
1193 };
1194
1195
1198
1199
1200
1201}}} // namespace lib::diff::test
Buffer to place and maintain an object instance privately within another object.
A handle to allow for safe »remote implantation« of an unknown subclass into a given opaque InPlaceBu...
SUB & emplace(SUB &&implementation)
move-construct an instance of a subclass into the opaque buffer
object-like record of data.
Definition record.hpp:142
static const string TYPE_NIL
Definition record.hpp:153
Access get(string key) const
Definition record.hpp:245
scopeIter attribs() const
Definition record.hpp:318
scopeIter scope() const
Definition record.hpp:319
string getType() const
Definition record.hpp:227
Test adapter to watch and verify how the TreeMutator binds to custom tree data structures.
EventMatch verify(string match) const
string showContent() const
render payload content for diagnostics
EventMatch verifyEvent(string match) const
string showSrcBuffer() const
render elements waiting in source buffer to be accepted
Customisable intermediary to abstract mutating operations on arbitrary, hierarchical object-like data...
static Builder< TreeMutator > build()
DSL: start building a custom adapted tree mutator, where the operations are tied by closures or wrapp...
string const & getSym() const
Definition entry-id.hpp:169
EventMatch & before(string match)
find a match (substring match) of the given text in an EventLog entry after the current position
EventMatch & beforeEvent(string match)
find a match for an "event" after the current point of reference
EventMatch & after(string match)
find a match (substring match) of the given text in an EventLog entry before the current position,...
Lumiera's internal time value datatype.
A front-end for using printf-style formatting.
Lumiera error handling (C++ interface).
#define LERR_(_NAME_)
Definition error.hpp:45
Automatically use custom string conversion in C++ stream output.
Collection of small helpers and convenience shortcuts for diagnostics & formatting.
Preconfigured adapters for some STL container standard usage situations.
Rec::Mutator MakeRec
Definition gen-node.hpp:135
_SeqT< CON >::Range eachElm(CON &coll)
Implementation namespace for support and library code.
Test runner and basic definitions for tests.
vector< string > stringify(ELMS const &...elms)
standard setup: convert to string into a vector
bool contains(MAP &map, typename MAP::key_type const &key)
shortcut for containment test on a map
Definition util.hpp:230
string join(COLL &&coll, string const &delim=", ")
enumerate a collection's contents, separated by delimiter.
bool isnil(lib::time::Duration const &dur)
Simplistic test class runner.
#define LAUNCHER(_TEST_CLASS_, _GROUPS_)
Definition run.hpp:116
generic data element node within a tree
Definition gen-node.hpp:224
static const Ref END
symbolic ID ref "_END_"
Definition gen-node.hpp:863
static const Ref ATTRIBS
symbolic ID ref "_ATTRIBS_"
Definition gen-node.hpp:866
A collection of frequently used helper functions to support unit testing.
#define VERIFY_ERROR(ERROR_ID, ERRONEOUS_STATEMENT)
Macro to verify that a statement indeed raises an exception.
#define MARK_TEST_FUN
Macro to mark the current test function in STDOUT.
Diagnostic helper for unit tests regarding mutation of custom data.
a family of time value like entities and their relationships.
#define LOG_SETTER(KEY)
Customisable intermediary to abstract generic tree mutation operations.
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...