Lumiera 0.pre.04
»edit your freedom«
Loading...
Searching...
No Matches
parse-test.cpp
Go to the documentation of this file.
1/*
2 Parse(Test) - verify parsing textual specifications
3
4 Copyright (C)
5 2024, 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
20#include "lib/test/run.hpp"
23#include "lib/parse.hpp"
24
25#include <vector>
26
27
28namespace util {
29namespace parse{
30namespace test {
31
35 using std::decay_t;
36 using std::vector;
37 using std::get;
38
39
40
41
42 /****************************************************/
48 class Parse_test : public Test
49 {
50
51 virtual void
52 run (Arg)
53 {
54 simpleUsage();
55
56 acceptTerminal();
57 acceptSequential();
58 acceptAlternatives();
59 acceptIterWithDelim();
60 acceptOptionally();
61 acceptBracketed();
62
63 verify_modelBinding();
64 verify_recursiveSyntax();
65 verify_nestedSpecTerms();
66 }
67
68
70 void
71 simpleUsage ()
72 {
73 using Model = std::pair<string, vector<string>>;
74
75 auto word = accept("\\w+").bindMatch();
76 auto term = accept(word)
77 .bracket (accept_repeated(",", word))
78 .bind([](auto res){ return Model{get<0>(res),get<1>(res)}; });
79
80 CHECK (not term.hasResult());
81
82 term.parse("great (hypertrophy, confusion, deception, profit)");
83 CHECK (term.success());
84 Model model = term.getResult();
85 CHECK (model.first == "great");
86 CHECK (model.second[0] == "hypertrophy");
87 CHECK (model.second[1] == "confusion" );
88 CHECK (model.second[2] == "deception" );
89 CHECK (model.second[3] == "profit" );
90 }
91
92
93
94
96 void
97 acceptTerminal()
98 {
99 // set up a parser function to accept some token as terminal
100 auto parse = Parser{"hello (\\w+) world"};
101 string toParse{"hello vile world of power"};
102 auto eval = parse (toParse);
103 CHECK (eval.result);
104 smatch res = *eval.result; // ◁——————————————————————— »result model« of a terminal parse is the RegExp-Matcher
105 CHECK (res.ready() and not res.empty());
106 CHECK (res.size() == "2"_expect );
107 CHECK (res.position() == "0"_expect );
108 CHECK (res.str() == "hello vile world"_expect );
109 CHECK (res[1] == "vile"_expect );
110 CHECK (res.suffix() == " of power"_expect );
111
112 auto syntax = Syntax{move (parse)}; // Build a syntax clause from the simple terminal symbol parser
113 CHECK (not syntax.hasResult());
114 syntax.parse (toParse);
115 CHECK (syntax.success()); // Syntax clause holds an implicit state from the last parse
116 CHECK (syntax.getResult()[1] == "vile"_expect);
117
118 // shorthand notation to start building a syntax
119 auto syntax2 = accept ("(\\w+) world");
120 CHECK (not syntax2.hasResult());
121 syntax2.parse (toParse);
122 CHECK (not syntax2.success());
123
124 string bye{"cruel world"};
125 syntax2.parse (bye);
126 CHECK (syntax2.success());
127 CHECK (syntax2.getResult()[1] == "cruel"_expect);
128
129 // Going full circle: extract Parser definition from syntax
130 auto parse2 = Parser{syntax2};
131 CHECK (eval.result->str(1) == "vile"); // leftover value
132 eval = parse2 (toParse);
133 CHECK (not eval.result);
134 eval = parse2 (bye);
135 CHECK (eval.result->str(1) == "cruel");
136 }
137
138
147 void
148 acceptSequential()
149 { //_______________________________________________
150 // Demonstration: how sequence combinator works....
151 auto term1 = buildConnex ("hello");
152 auto term2 = buildConnex ("world");
153 auto parseSeq = [&](StrView toParse)
154 {
155 using R1 = decltype(term1)::Result;
156 using R2 = decltype(term2)::Result;
157 using ProductResult = std::tuple<R1,R2>;
158 using ProductEval = Eval<ProductResult>;
159 auto eval1 = term1.parse (toParse);
160 if (eval1.result)
161 {
162 uint end1 = eval1.consumed;
163 StrView restInput = toParse.substr(end1);
164 auto eval2 = term2.parse (restInput);
165 if (eval2.result)
166 {
167 uint consumedOverall = end1 + eval2.consumed;
168 return ProductEval{ProductResult{move(*eval1.result)
169 ,move(*eval2.result)}
170 ,consumedOverall
171 };
172 }
173 }
174 return ProductEval{std::nullopt};
175 };
176 string s1{"hello millions"};
177 string s2{"hello world"};
178 string s3{" hello world trade "};
179
180 auto e1 = parseSeq(s1);
181 CHECK (not e1.result); // Syntax 'hello'>>'world' does not accept "hello millions"
182 auto e2 = parseSeq(s2);
183 CHECK ( e2.result);
184
185 using SeqRes = decltype(e2)::Result; // Note: the result type depends on the actual syntax construction
186 CHECK (is_Tuple<SeqRes>()); // Result model from sequence is the tuple of terminal results
187 auto& [r1,r2] = *e2.result;
188 CHECK (r1.str() == "hello"_expect);
189 CHECK (r2.str() == "world"_expect);
190
191 CHECK (term2.parse(" world").result); // Note: leading whitespace skipped by the basic terminal parsers
192 CHECK (term2.parse("\n \t world ").result);
193 CHECK (not term2.parse(" old ").result);
194
195
196 //____________________________________________________
197 // DSL syntax clause builder: a sequence of terminals...
198 auto syntax = accept("hello").seq("world");
199
200 // Perform the same parse as demonstrated above....
201 CHECK (not syntax.hasResult());
202 syntax.parse(s1);
203 CHECK (not syntax.success());
204 syntax.parse(s2);
205 CHECK (syntax);
206 SeqRes seqModel = syntax.getResult();
207 CHECK (get<0>(seqModel).str() == "hello"_expect);
208 CHECK (get<1>(seqModel).str() == "world"_expect);
209
210
211 // can build extended clause from existing one
212 auto syntax2 = accept(syntax).seq("trade"); // Warning: seq() moves the parse function (but accept() has created a copy)
213 CHECK (not syntax2.hasResult());
214 CHECK ( syntax.hasResult()); // ...so the syntax2 is indeed an independent instance now
215 syntax2.parse(s2);
216 CHECK (not syntax2.success());
217 syntax2.parse(s3);
218 CHECK (syntax2.success());
219 auto seqModel2 = syntax2.getResult(); // Note: model of consecutive sequence is flattened into a single tuple
220 CHECK (get<0>(seqModel2).str() == "hello"_expect);
221 CHECK (get<1>(seqModel2).str() == "world"_expect);
222 CHECK (get<2>(seqModel2).str() == "trade"_expect);
223 }
224
225
226
234 void
235 acceptAlternatives()
236 { //_______________________________
237 // Demonstrate Alt-Model mechanics
238 using R1 = char;
239 using R2 = string;
240 using R3 = double;
241
242 // build Model-Alternatives incrementally
243 using A1 = AltModel<R1>;
244 CHECK (showType<A1>() == "parse::AltModel<char>"_expect);
245
246 using A2 = A1::Additionally<R2>;
247 CHECK (showType<A2>() == "parse::AltModel<char, string>"_expect);
248
249 // create instance to represent this second branch...
250 A2 model2 = A2::mark_right ("seduced");
251 CHECK (sizeof(A2) >= sizeof(string)+sizeof(size_t));
252 CHECK (model2.SIZ == sizeof(string));
253 CHECK (model2.TOP == 1);
254 CHECK (model2.selected() == 1);
255 CHECK (model2.get<1>() == "seduced");
256
257 using A3 = A2::Additionally<R3>;
258 A3 model3 = A3::mark_left (move (model2));
259 CHECK (showType<A3>() == "parse::AltModel<char, string, double>"_expect);
260 CHECK (sizeof(A3) == sizeof(A2));
261 CHECK (model3.TOP == 2);
262 CHECK (model3.selected() == 1);
263 CHECK (model3.get<1>() == "seduced");
264
265 auto res = move(model3);
266 CHECK (showType<decltype(res)>() == "parse::AltModel<char, string, double>"_expect);
267 CHECK (sizeof(res) == sizeof(A2));
268 CHECK (res.selected() == 1);
269 CHECK (res.get<1>() == "seduced");
270
271
272 // AltModel with homogeneous types are special
273 auto hom = AltModel<int,int>::mark_right(42);
274 CHECK (hom.getAny() == 42);
275 CHECK (hom.selected() == 1 );
276
277 hom = AltModel<int,int>::mark_left(55);
278 CHECK (hom.getAny() == 55);
279 CHECK (hom.selected() == 0 );
280
281
282
283 //_____________________________________________
284 // Demonstration: how branch combinator works....
285 auto term1 = buildConnex ("brazen");
286 auto term2 = buildConnex ("bragging");
287 auto parseAlt = [&](StrView toParse)
288 {
289 using R1 = decltype(term1)::Result;
290 using R2 = decltype(term2)::Result;
291 using SumResult = AltModel<R1,R2>;
292 using SumEval = Eval<SumResult>;
293 auto eval1 = term1.parse (toParse);
294 if (eval1.result)
295 {
296 uint endBranch1 = eval1.consumed;
297 return SumEval{SumResult::mark_left (move(*eval1.result))
298 ,endBranch1
299 };
300 }
301 auto eval2 = term2.parse (toParse);
302 if (eval2.result)
303 {
304 uint endBranch2 = eval2.consumed;
305 return SumEval{SumResult::mark_right (move(*eval2.result))
306 ,endBranch2
307 };
308 }
309 return SumEval{std::nullopt};
310 };
311 string s1{"decent contender"};
312 string s2{"brazen dicktator"};
313
314 auto e1 = parseAlt(s1);
315 CHECK (not e1.result); // does not compute....
316 auto e2 = parseAlt(s2); // one hell of a match!
317 CHECK ( e2.result);
318 CHECK (e2.result->selected() == 0); // Selector-ID of the first matching branch (here #0)
319 CHECK (e2.result->get<0>().str() == "brazen"); // We know that branch#0 holds a RegExp-Matcher (from term1)
320 CHECK (e2.result->get<0>().suffix() == " dicktator");
321 CHECK (e2.consumed == 6);
322 CHECK (s2.substr(e2.consumed) == " dicktator");
323
324
325 //________________________________________________
326 // DSL parse clause builder: alternative branches...
327 auto syntax = accept("brazen").alt("bragging");
328
329 // Perform the same parse as demonstrated above....
330 CHECK (not syntax.hasResult());
331 syntax.parse(s1);
332 CHECK (not syntax.success());
333 syntax.parse(s2);
334 CHECK (syntax);
335 auto altModel = syntax.getResult();
336 CHECK (altModel.selected() == 0);
337 CHECK (altModel.get<0>().str() == "brazen");
338
339 // can build extended clause from existing one
340 auto syntax2 = accept(syntax).alt("smarmy (\\w+)");
341 CHECK (not syntax2.hasResult());
342 syntax2.parse(s1);
343 CHECK (not syntax2.success());
344 syntax2.parse(s2);
345 CHECK (syntax2.success());
346 CHECK (syntax2.getResult().N == 3); // Note: further branch has been folded into an extended AltModel
347 CHECK (syntax2.getResult().selected() == 0); // ... string s2 still matched the same branch (#0)
348 CHECK (syntax2.getResult().get<0>().str() == "brazen");
349
350 syntax2.parse("smarmy saviour");
351 CHECK (syntax2.success());
352 auto altModel2 = syntax2.getResult();
353 CHECK (syntax2.getResult().selected() == 2); // ... but another string can match the added branch #2
354 CHECK (syntax2.getResult().get<2>().str() == "smarmy saviour");
355 CHECK (syntax2.getResult().get<2>().str(1) == "saviour");
356 } // Note: syntax for this branch #2 captured an additional word
357
358
359
365 void
366 acceptIterWithDelim()
367 { //_______________________________________________
368 // Demonstration: how repetitive sequence works....
369 auto sep = buildConnex (",");
370 auto word = buildConnex ("\\w+");
371 auto parseSeq = [&](StrView toParse)
372 {
373 using Res = decltype(word)::Result;
374 using IterResult = std::vector<Res>;
375 using IterEval = Eval<IterResult>;
376 uint consumed{0};
377 IterResult results;
378 auto hasResults = [&]{ return not results.empty(); };
379 while (true)
380 {
381 uint offset{0};
382 if (hasResults())
383 {
384 auto delim = sep.parse (toParse);
385 if (not delim.result)
386 break;
387 offset += delim.consumed;
388 }
389 auto eval = word.parse (toParse.substr(offset));
390 if (not eval.result)
391 break;
392 offset += eval.consumed;
393 results.emplace_back (move(*eval.result));
394 toParse = toParse.substr(offset);
395 consumed += offset;
396 }
397 return hasResults()? IterEval{move(results), consumed}
398 : IterEval{std::nullopt};
399 };
400 string s1{"seid umschlungen, Millionen"};
401 string s2{"beguile, extort, profit"};
402
403 auto e1 = parseSeq(s1);
404 CHECK (e1.result);
405 CHECK (e1.result->size() == 1);
406 CHECK (e1.result->at(0).str() == "seid");
407 CHECK (e1.result->at(0).suffix() == " umschlungen, Millionen");
408 CHECK (e1.consumed == 4);
409
410 auto e2 = parseSeq(s2);
411 CHECK (e2.result);
412 CHECK (e2.result->size() == 3);
413 CHECK (e2.result->at(0).str() == "beguile");
414 CHECK (e2.result->at(1).str() == "extort" );
415 CHECK (e2.result->at(2).str() == "profit" );
416 CHECK (e2.result->at(0).suffix() == ", extort, profit");
417 CHECK (e2.result->at(1).suffix() == ", profit");
418 CHECK (e2.result->at(2).suffix() == "" );
419 CHECK (e2.consumed == s2.length());
420
421
422 //______________________________________________
423 // DSL parse clause builder: iterative sequence...
424 auto syntax1 = accept_repeated(",", word);
425
426 // Perform the same parse as demonstrated above....
427 CHECK (not syntax1.hasResult());
428 syntax1.parse(s1);
429 CHECK (syntax1.success());
430 auto res1 = syntax1.getResult();
431 CHECK (res1.size() == 1);
432 CHECK (res1.get(0).str() == "seid");
433
434 syntax1.parse(s2);
435 CHECK (syntax1.success());
436 res1 = syntax1.getResult();
437 CHECK (res1.size() == 3);
438 CHECK (res1[0].str() == "beguile");
439 CHECK (res1[1].str() == "extort" );
440 CHECK (res1[2].str() == "profit" );
441
442 auto syntax2 = accept_repeated(1,2,",", word);
443 auto syntax3 = accept_repeated( 4,",", word);
444 syntax2.parse(s2);
445 syntax3.parse(s2);
446 CHECK ( syntax2);
447 CHECK (not syntax3);
448 CHECK (syntax2.getResult().size() == 2);
449 CHECK (s2.substr(syntax2.consumed()) == ", profit");
450
451 auto sx = s2 + " , \tdump";
452 syntax3.parse(sx);
453 CHECK (syntax3);
454 CHECK (syntax3.getResult().size() == 4);
455 CHECK (syntax3.getResult()[0].str() == "beguile");
456 CHECK (syntax3.getResult()[1].str() == "extort" );
457 CHECK (syntax3.getResult()[2].str() == "profit" );
458 CHECK (syntax3.getResult()[3].str() == "dump" );
459
460 auto syntax4 = accept_repeated(word);
461 syntax4.parse(s1);
462 CHECK (syntax4.success());
463 CHECK (syntax4.getResult().size() == 2);
464 CHECK (syntax4.getResult()[0].str() == "seid");
465 CHECK (syntax4.getResult()[1].str() == "umschlungen" );
466 CHECK (s1.substr(syntax4.consumed()) == ", Millionen");
467 }
468
469
470
480 void
481 acceptOptionally()
482 {
483 auto syntax = accept_repeated(",", "\\w+") // first we look for comma separated words
484 .opt(accept("and") // then (implicitly sequenced) an optional clause
485 .repeat("\\w+")); // ... comprising "and" followed by several words
486 using Model = decay_t<decltype(syntax.getResult())>;
487
488 string s1{"fearmongering, scapegoating, intimidation"};
489 string s2{"charisma and divine blessing"};
490
491 CHECK (not syntax.hasResult());
492 syntax.parse(s1);
493 CHECK (syntax.success());
494
495 Model res1 = syntax.getResult();
496 CHECK (typeSymbol(res1) == "SeqModel");
497 CHECK (typeSymbol(res1.get<0>()) == "IterModel");
498 CHECK (typeSymbol(res1.get<1>()) == "optional");
499
500 CHECK (res1.N == 2); // 2-component tuple at top
501 CHECK (res1.get<0>().size() == 3); // sequence in 1st component matched 3 elements
502 CHECK (res1.get<0>()[0].str() == "fearmongering"); // elements in the sequence...
503 CHECK (res1.get<0>()[1].str() == "scapegoating");
504 CHECK (res1.get<0>()[2].str() == "intimidation");
505 CHECK (res1.get<1>() == std::nullopt); // the optional clause did not match
506
507 syntax.parse(s2);
508 CHECK (syntax.success());
509
510 Model res2 = syntax.getResult();
511 CHECK (typeSymbol(res2) == "SeqModel"); // Syntax SeqModel
512 CHECK (typeSymbol(res2.get<0>()) == "IterModel"); // repeat(word) opt IterModel optional
513 CHECK (typeSymbol(res2.get<1>()) == "optional"); // | |
514 CHECK (typeSymbol(*res2.get<1>()) == "SeqModel"); // Syntax SeqModel
515 CHECK (typeSymbol(res2.get<1>()->get<0>()) == "match_results"); // "and" repeat(word) Terminal IterModel
516 CHECK (typeSymbol(res2.get<1>()->get<1>()) == "IterModel"); //
517
518 CHECK (res2.get<0>().size() == 1);
519 CHECK (res2.get<0>()[0].str() == "charisma");
520 CHECK (res2.get<1>() != std::nullopt);
521 CHECK (res2.get<1>()->N == 2);
522 CHECK (res2.get<1>()->get<0>().str() == "and");
523 CHECK (res2.get<1>()->get<1>().size() == 2 );
524 CHECK (res2.get<1>()->get<1>()[0].str() == "divine" );
525 CHECK (res2.get<1>()->get<1>()[1].str() == "blessing" );
526
527 string s3{s1+" , "+s2};
528 syntax.parse(s3);
529 CHECK (syntax.success());
530
531 Model res3 = syntax.getResult();
532 CHECK (typeSymbol(res3) == "SeqModel");
533 CHECK (res3.get<0>().size() == 4);
534 CHECK (res3.get<0>()[0].str() == "fearmongering");
535 CHECK (res3.get<0>()[1].str() == "scapegoating");
536 CHECK (res3.get<0>()[2].str() == "intimidation");
537 CHECK (res3.get<0>()[3].str() == "charisma");
538 CHECK (res3.get<1>() != std::nullopt);
539 CHECK (res3.get<1>()->N == 2);
540 CHECK (res3.get<1>()->get<0>().str() == "and");
541 CHECK (res3.get<1>()->get<1>().size() == 2);
542 CHECK (res3.get<1>()->get<1>()[0].str() == "divine");
543 CHECK (res3.get<1>()->get<1>()[1].str() == "blessing");
544 }
545
546
547
549 void
550 acceptBracketed()
551 {
552 string word{"\\w+"};
553
554 CHECK (not accept(word).bracket(word) .parse("so sad"));
555 CHECK ( accept(word).bracketOpt(word).parse("so sad"));
556 CHECK ( accept(word).bracketOpt(word).parse("so (sad)"));
557
558 CHECK (accept_bracket(word).parse(" ( again ) ").getResult().str() == "again");
559
560 CHECK (not accept_bracket(word) .parse("(again"));
561 CHECK (not accept_bracketOpt(word).parse("(again"));
562 CHECK ( accept_bracketOpt(word).parse("again)")); // just stops before the trailing ')'
563 CHECK ( accept_bracketOpt(word).parse("again)").consumed() == 5);
564 CHECK ( accept_bracketOpt(word).parse(" again")); // backtracks also over the whitespace
565
566 CHECK (not accept_bracket("[]",word).parse("(again)"));
567 CHECK (not accept_bracket("[]",word).parse("[again)"));
568 CHECK (not accept_bracket("[]",word).parse("(again]"));
569 CHECK ( accept_bracket("[]",word).parse("[again]"));
570 CHECK ( accept_bracket("a","n","...").parse("again")); // arbitrary expressions for open / close
571 CHECK (not accept_bracket("a","n","...").parse(" gain")); // opening expression "a" missing
572 CHECK (not accept_bracket("a","n", word).parse("again")); // "\\w+" consumes eagerly => closing expression not found
573 }
574
575
576
580 void
581 verify_modelBinding()
582 {
583 auto word{"\\w+"};
584 auto syntax1 = accept(word).seq(word) // get a tuple with two RegExp-Matchers
585 .bind([](SeqModel<smatch,smatch> res)
586 {
587 return res.get<0>().str() +"-"+ res.get<1>().str();
588 });
589
590 string s1{"ham actor"};
591 CHECK (not syntax1.hasResult());
592 syntax1.parse(s1);
593 CHECK (syntax1.success());
594 auto res1 = syntax1.getResult();
595 CHECK (showType<decltype(res1)>() == "string"); // surprise! it's a simple string (as returned from λ)
596 CHECK (res1 == "ham-actor"_expect);
597
598 // 💡 shortcut for RegExp match groups...
599 auto syntax1b = accept("(\\w+) (\\w+)");
600 CHECK (accept(syntax1b).bindMatch( ).parse(s1).getResult() == "ham actor"_expect );
601 CHECK (accept(syntax1b).bindMatch(1).parse(s1).getResult() == "ham"_expect );
602 CHECK (accept(syntax1b).bindMatch(2).parse(s1).getResult() == "actor"_expect );
603 CHECK (accept(syntax1b).bindMatch(3).parse(s1).getResult() == ""_expect );
604
605 auto wordEx = accept(word).bindMatch();
606 auto syntax1c = accept(wordEx)
607 .seq(wordEx) // sub-expressions did already transform to string
608 .bind([](SeqModel<string,string> res)
609 { return res.get<0>() +"-"+ res.get<1>(); });
610
611 CHECK (syntax1c.parse("ham actor").getResult() == "ham-actor");
612 CHECK (syntax1c.parse("con artist").getResult() == "con-artist");
613
614 auto syntax1d = accept(word).seq(word)
615 .bindMatch(); // generic shortcut: ignore model, yield accepted part of input
616 CHECK (syntax1d.parse("ham actor").getResult() == "ham actor");
617 CHECK (syntax1d.parse(" ham actor").getResult() == "ham actor");
618
619 // another example to demonstrate arbitrary transformations:
620 // each sub-expr counts the letters, and the top-level binding sums those up
621 auto letterCnt = accept(word).bindMatch().bind([](string s){ return s.size(); });
622 auto syntax1e = accept(letterCnt)
623 .seq(letterCnt)
624 .bind([](auto m){ auto [l1,l2] = m; return l1+l2; });
625 // note this time we provide a λ-generic and use a structured binding
626 CHECK (syntax1e.parse("ham actor").getResult() == 8);
627 CHECK (syntax1e.parse("con artist").getResult() == 9);
628 }
629
630
631
642 void
643 verify_recursiveSyntax()
644 {
645 auto recurse = expectResult<int>();
646 CHECK (not recurse.canInvoke());
647
648 recurse = accept("great")
649 .opt(accept("!")
650 .seq(recurse))
651 .bind([](auto m) -> int
652 {
653 auto& [_,r] = m;
654 return 1 + (r? get<1>(*r):0);
655 });
656 CHECK (recurse.canInvoke());
657
658 recurse.parse("great ! great ! great");
659 CHECK (recurse.success());
660 CHECK (recurse.getResult() == 3 );
661
662 CHECK (not recurse.parse(" ! great"));
663 CHECK (recurse.parse("great ! great actor").getResult() == 2);
664 CHECK (recurse.parse("great ! great ! actor").getResult() == 2);
665
666
667 //_____________________________________________
668 // Build a recursive numeric expression syntax...
669 auto num = accept("\\d+") .bindMatch().bind([](auto num){ return std::stod(num); });
670 auto sqrt = accept("√").seq(num) .bind([](auto seq){ return std::sqrt(get<1>(seq)); });
671
672 CHECK (sqrt.parse(" √x ").getResult() == 0 );
673 CHECK (sqrt.parse(" √2 ").getResult() == "1.4142136"_expect);
674
675 // E ::= T [ + E ]
676 // T ::= F [ / F ]
677 // F ::= ( E ) | V
678 // V ::= num | √ num
679 auto expr = expectResult<double>();
680
681 auto valu = accept(num).alt(sqrt) .bind([](auto alt){ return alt.getAny(); });
682 auto fact = accept_bracket(expr).alt(valu) .bind([](auto alt){ return alt.getAny(); });
683 auto term = accept(fact).opt(accept("/") .seq(fact)) .bind([](auto seq){ auto [f1,f2] = seq; return f1 / (f2? get<1>(*f2) : 1.0); });
684 expr = accept(term).opt(accept("\\+").seq(expr)) .bind([](auto exp){ auto [s1,s2] = exp; return s1 + (s2? get<1>(*s2) : 0.0); });
685
686 CHECK (expr.canInvoke());
687 CHECK (not expr.hasResult());
688
689 expr.parse(" 42 forever");
690 CHECK (expr.success());
691 CHECK (expr.getResult() == 42 );
692
693 expr.parse(" 42 + 13 =?");
694 CHECK (expr.success());
695 CHECK (expr.getResult() == 55 );
696
697 expr.parse(" 1 + 4/3 ");
698 CHECK (expr.success());
699 CHECK (expr.getResult() == "2.3333333"_expect);
700
701 expr.parse("(2+2)/(2+1) + 4/2");
702 CHECK (expr.success());
703 CHECK (expr.getResult() == "3.3333333"_expect);
704
705 expr.parse("(1 + √5) / 2 ");
706 CHECK (expr.success());
707 CHECK (expr.getResult() == "1.618034"_expect);
708 }
709
710
711
720 void
721 verify_nestedSpecTerms()
722 {
723 auto content = accept(R"_([^,\\\‍(\)\[\]{}<>"]+)_");
724 auto escape = accept(R"_(\\.)_");
725
726 auto nonQuot = accept(R"_([^"\\]+)_");
727 auto quoted = accept_repeated(accept(nonQuot).alt(escape));
728 auto quote = accept_bracket("\"\"", quoted);
729
730 auto paren = expectResult<Nil>();
731 auto nonParen = accept(R"_([^\\\‍(\)"]+)_");
732 auto parenCont = accept_repeated(accept(nonParen)
733 .alt(escape)
734 .alt(quote)
735 .alt(paren));
736 paren = accept_bracket("()", parenCont).bind([](auto){ return Nil{}; });
737
738 auto spec = accept_repeated(accept(content)
739 .alt(escape)
740 .alt(quote)
741 .alt(paren));
742
743 // abbreviation for the test...
744 auto apply = [](auto& syntax)
745 { return [&](auto const& str)
746 { return accept(syntax).bindMatch()
747 .parse(str)
748 .getResult();
749 };
750 };
751
752 CHECK (apply(content)("prey .. haul .. loot") == "prey .. haul .. loot"_expect );
753 CHECK (apply(content)("prey .. haul ,. loot") == "prey .. haul "_expect );
754 CHECK (apply(content)("prey .( haul ,. loot") == "prey ."_expect );
755
756 CHECK (apply(quote)("\"prey .( haul ,\"loot") == "\"prey .( haul ,\""_expect );
757 CHECK (apply(quote)("\"prey \\ haul ,\"loot") == "\"prey \\ haul ,\""_expect );
758 CHECK (apply(quote)("\"prey\\\"haul ,\"loot") == "\"prey\\\"haul ,\""_expect );
759
760 CHECK (apply(paren)("(prey) .. haul .. loot") == "(prey)"_expect );
761 CHECK (apply(paren)("(prey .. haul .. loot)") == "(prey .. haul .. loot)"_expect );
762 CHECK (apply(paren)("(prey(..(haul)..)loot)") == "(prey(..(haul)..)loot)"_expect );
763 CHECK (apply(paren)("(prey \" haul)\" loot)") == "(prey \" haul)\" loot)"_expect );
764 CHECK (apply(paren)("(prey\\( haul)\" loot)") == "(prey\\( haul)"_expect );
765
766 CHECK (apply(spec)("\"prey .( haul ,\"loot!") == "\"prey .( haul ,\"loot!"_expect);
767 CHECK (apply(spec)("\"prey .( haul \",loot!") == "\"prey .( haul \""_expect );
768 CHECK (apply(spec)(" prey .( haul \",loot!") == "prey ."_expect );
769 CHECK (apply(spec)(" prey .( haul,)\"loot!") == "prey .( haul,)"_expect );
770 CHECK (apply(spec)(" (prey\\( haul }, loot)") == "(prey\\( haul }, loot)"_expect );
771 }
772 };
773
774 LAUNCHER (Parse_test, "unit common");
775
776
777}}} // namespace util::parse::test
778
void bye(const char *m)
#define _(String)
Definition gtk-base.hpp:68
unsigned int uint
Definition integral.hpp:29
string typeSymbol()
Short readable type identifier, not necessarily unique or complete.
Definition genfunc.hpp:78
std::string typeSymbol(TY const *obj=nullptr)
simple expressive symbol to designate a type
constexpr decltype(auto) apply(FUN &&f, TUP &&tup) noexcept(can_nothrow_invoke_tup< FUN, TUP >)
Replacement for std::apply — yet applicable to tuple-like custom types.
string showType()
diagnostic type output, including const and similar adornments
Test runner and basic definitions for tests.
auto accept_bracketOpt(string bracketSpec, SPEC &&bodyDef)
Start Syntax with a sub-clause, optionally enclosed into brackets.
Definition parse.hpp:1017
auto accept_repeated(uint min, uint max, SPEC1 &&delimDef, SPEC2 &&clauseDef)
Start Syntax clause with a repeated sub-clause, with separator and repetition limit; repetitions ∊ [m...
Definition parse.hpp:919
auto accept(SPEC &&clauseDef)
===== Syntax clause builder DSL =====
Definition parse.hpp:891
auto buildConnex(Nil)
»Null-Connex« which always successfully accepts the empty sequence
Definition parse.hpp:226
auto accept_bracket(SPEC1 &&openDef, SPEC2 &&closeDef, SPEC3 &&bodyDef)
Start Syntax with a sub-clause enclosed into a bracketing construct.
Definition parse.hpp:978
Parser(Nil) -> Parser< NulP >
std::string_view StrView
Definition parse.hpp:174
boost::program_options::options_description Syntax
Definition option.cpp:27
Convenience wrappers and definitions for parsing structured definitions.
Simplistic test class runner.
#define LAUNCHER(_TEST_CLASS_, _GROUPS_)
Definition run.hpp:116
trait to detect tuple types
A collection of frequently used helper functions to support unit testing.
Metaprogramming with tuples-of-types and the std::tuple record.