48 class Parse_test :
public Test
59 acceptIterWithDelim();
63 verify_modelBinding();
64 verify_recursiveSyntax();
65 verify_nestedSpecTerms();
73 using Model = std::pair<string, vector<string>>;
75 auto word =
accept(
"\\w+").bindMatch();
78 .bind([](
auto res){
return Model{get<0>(res),get<1>(res)}; });
80 CHECK (not term.hasResult());
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" );
100 auto parse =
Parser{
"hello (\\w+) world"};
101 string toParse{
"hello vile world of power"};
102 auto eval = parse (toParse);
104 smatch res = *eval.result;
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 );
112 auto syntax =
Syntax{move (parse)};
113 CHECK (not syntax.hasResult());
114 syntax.parse (toParse);
115 CHECK (syntax.success());
116 CHECK (syntax.getResult()[1] ==
"vile"_expect);
119 auto syntax2 =
accept (
"(\\w+) world");
120 CHECK (not syntax2.hasResult());
121 syntax2.parse (toParse);
122 CHECK (not syntax2.success());
124 string bye{
"cruel world"};
126 CHECK (syntax2.success());
127 CHECK (syntax2.getResult()[1] ==
"cruel"_expect);
130 auto parse2 =
Parser{syntax2};
131 CHECK (eval.result->str(1) ==
"vile");
132 eval = parse2 (toParse);
133 CHECK (not eval.result);
135 CHECK (eval.result->str(1) ==
"cruel");
153 auto parseSeq = [&](
StrView toParse)
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);
162 uint end1 = eval1.consumed;
163 StrView restInput = toParse.substr(end1);
164 auto eval2 = term2.parse (restInput);
167 uint consumedOverall = end1 + eval2.consumed;
168 return ProductEval{ProductResult{move(*eval1.result)
169 ,move(*eval2.result)}
174 return ProductEval{std::nullopt};
176 string s1{
"hello millions"};
177 string s2{
"hello world"};
178 string s3{
" hello world trade "};
180 auto e1 = parseSeq(s1);
181 CHECK (not e1.result);
182 auto e2 = parseSeq(s2);
185 using SeqRes =
decltype(e2)::Result;
186 CHECK (is_Tuple<SeqRes>());
187 auto& [r1,r2] = *e2.result;
188 CHECK (r1.str() ==
"hello"_expect);
189 CHECK (r2.str() ==
"world"_expect);
191 CHECK (term2.parse(
" world").result);
192 CHECK (term2.parse(
"\n \t world ").result);
193 CHECK (not term2.parse(
" old ").result);
198 auto syntax =
accept(
"hello").seq(
"world");
201 CHECK (not syntax.hasResult());
203 CHECK (not syntax.success());
206 SeqRes seqModel = syntax.getResult();
207 CHECK (get<0>(seqModel).str() ==
"hello"_expect);
208 CHECK (get<1>(seqModel).str() ==
"world"_expect);
212 auto syntax2 =
accept(syntax).seq(
"trade");
213 CHECK (not syntax2.hasResult());
214 CHECK ( syntax.hasResult());
216 CHECK (not syntax2.success());
218 CHECK (syntax2.success());
219 auto seqModel2 = syntax2.getResult();
220 CHECK (get<0>(seqModel2).str() ==
"hello"_expect);
221 CHECK (get<1>(seqModel2).str() ==
"world"_expect);
222 CHECK (get<2>(seqModel2).str() ==
"trade"_expect);
243 using A1 = AltModel<R1>;
244 CHECK (showType<A1>() ==
"parse::AltModel<char>"_expect);
246 using A2 = A1::Additionally<R2>;
247 CHECK (showType<A2>() ==
"parse::AltModel<char, string>"_expect);
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");
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");
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");
273 auto hom = AltModel<int,int>::mark_right(42);
274 CHECK (hom.getAny() == 42);
275 CHECK (hom.selected() == 1 );
277 hom = AltModel<int,int>::mark_left(55);
278 CHECK (hom.getAny() == 55);
279 CHECK (hom.selected() == 0 );
287 auto parseAlt = [&](
StrView toParse)
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);
296 uint endBranch1 = eval1.consumed;
297 return SumEval{SumResult::mark_left (move(*eval1.result))
301 auto eval2 = term2.parse (toParse);
304 uint endBranch2 = eval2.consumed;
305 return SumEval{SumResult::mark_right (move(*eval2.result))
309 return SumEval{std::nullopt};
311 string s1{
"decent contender"};
312 string s2{
"brazen dicktator"};
314 auto e1 = parseAlt(s1);
315 CHECK (not e1.result);
316 auto e2 = parseAlt(s2);
318 CHECK (e2.result->selected() == 0);
319 CHECK (e2.result->get<0>().str() ==
"brazen");
320 CHECK (e2.result->get<0>().suffix() ==
" dicktator");
321 CHECK (e2.consumed == 6);
322 CHECK (s2.substr(e2.consumed) ==
" dicktator");
327 auto syntax =
accept(
"brazen").alt(
"bragging");
330 CHECK (not syntax.hasResult());
332 CHECK (not syntax.success());
335 auto altModel = syntax.getResult();
336 CHECK (altModel.selected() == 0);
337 CHECK (altModel.get<0>().str() ==
"brazen");
340 auto syntax2 =
accept(syntax).alt(
"smarmy (\\w+)");
341 CHECK (not syntax2.hasResult());
343 CHECK (not syntax2.success());
345 CHECK (syntax2.success());
346 CHECK (syntax2.getResult().N == 3);
347 CHECK (syntax2.getResult().selected() == 0);
348 CHECK (syntax2.getResult().get<0>().str() ==
"brazen");
350 syntax2.parse(
"smarmy saviour");
351 CHECK (syntax2.success());
352 auto altModel2 = syntax2.getResult();
353 CHECK (syntax2.getResult().selected() == 2);
354 CHECK (syntax2.getResult().get<2>().str() ==
"smarmy saviour");
355 CHECK (syntax2.getResult().get<2>().str(1) ==
"saviour");
366 acceptIterWithDelim()
371 auto parseSeq = [&](
StrView toParse)
373 using Res =
decltype(word)::Result;
374 using IterResult = std::vector<Res>;
375 using IterEval = Eval<IterResult>;
378 auto hasResults = [&]{
return not results.empty(); };
384 auto delim = sep.parse (toParse);
385 if (not delim.result)
387 offset += delim.consumed;
389 auto eval = word.parse (toParse.substr(offset));
392 offset += eval.consumed;
393 results.emplace_back (move(*eval.result));
394 toParse = toParse.substr(offset);
397 return hasResults()? IterEval{move(results), consumed}
398 : IterEval{std::nullopt};
400 string s1{
"seid umschlungen, Millionen"};
401 string s2{
"beguile, extort, profit"};
403 auto e1 = parseSeq(s1);
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);
410 auto e2 = parseSeq(s2);
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());
427 CHECK (not syntax1.hasResult());
429 CHECK (syntax1.success());
430 auto res1 = syntax1.getResult();
431 CHECK (res1.size() == 1);
432 CHECK (res1.get(0).str() ==
"seid");
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" );
448 CHECK (syntax2.getResult().size() == 2);
449 CHECK (s2.substr(syntax2.consumed()) ==
", profit");
451 auto sx = s2 +
" , \tdump";
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" );
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");
486 using Model = decay_t<
decltype(syntax.getResult())>;
488 string s1{
"fearmongering, scapegoating, intimidation"};
489 string s2{
"charisma and divine blessing"};
491 CHECK (not syntax.hasResult());
493 CHECK (syntax.success());
495 Model res1 = syntax.getResult();
497 CHECK (
typeSymbol(res1.get<0>()) ==
"IterModel");
498 CHECK (
typeSymbol(res1.get<1>()) ==
"optional");
501 CHECK (res1.get<0>().size() == 3);
502 CHECK (res1.get<0>()[0].str() ==
"fearmongering");
503 CHECK (res1.get<0>()[1].str() ==
"scapegoating");
504 CHECK (res1.get<0>()[2].str() ==
"intimidation");
505 CHECK (res1.get<1>() == std::nullopt);
508 CHECK (syntax.success());
510 Model res2 = syntax.getResult();
512 CHECK (
typeSymbol(res2.get<0>()) ==
"IterModel");
513 CHECK (
typeSymbol(res2.get<1>()) ==
"optional");
514 CHECK (
typeSymbol(*res2.get<1>()) ==
"SeqModel");
515 CHECK (
typeSymbol(res2.get<1>()->get<0>()) ==
"match_results");
516 CHECK (
typeSymbol(res2.get<1>()->get<1>()) ==
"IterModel");
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" );
527 string s3{s1+
" , "+s2};
529 CHECK (syntax.success());
531 Model res3 = syntax.getResult();
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");
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)"));
558 CHECK (
accept_bracket(word).parse(
" ( again ) ").getResult().str() ==
"again");
581 verify_modelBinding()
584 auto syntax1 =
accept(word).seq(word)
585 .bind([](SeqModel<smatch,smatch> res)
587 return res.get<0>().str() +
"-"+ res.get<1>().str();
590 string s1{
"ham actor"};
591 CHECK (not syntax1.hasResult());
593 CHECK (syntax1.success());
594 auto res1 = syntax1.getResult();
595 CHECK (showType<
decltype(res1)>() ==
"string");
596 CHECK (res1 ==
"ham-actor"_expect);
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 );
605 auto wordEx =
accept(word).bindMatch();
606 auto syntax1c =
accept(wordEx)
608 .bind([](SeqModel<string,string> res)
609 {
return res.get<0>() +
"-"+ res.get<1>(); });
611 CHECK (syntax1c.parse(
"ham actor").getResult() ==
"ham-actor");
612 CHECK (syntax1c.parse(
"con artist").getResult() ==
"con-artist");
614 auto syntax1d =
accept(word).seq(word)
616 CHECK (syntax1d.parse(
"ham actor").getResult() ==
"ham actor");
617 CHECK (syntax1d.parse(
" ham actor").getResult() ==
"ham actor");
621 auto letterCnt =
accept(word).bindMatch().bind([](
string s){
return s.size(); });
622 auto syntax1e =
accept(letterCnt)
624 .bind([](
auto m){
auto [l1,l2] = m;
return l1+l2; });
626 CHECK (syntax1e.parse(
"ham actor").getResult() == 8);
627 CHECK (syntax1e.parse(
"con artist").getResult() == 9);
643 verify_recursiveSyntax()
645 auto recurse = expectResult<int>();
646 CHECK (not recurse.canInvoke());
651 .bind([](
auto m) ->
int
654 return 1 + (r? get<1>(*r):0);
656 CHECK (recurse.canInvoke());
658 recurse.parse(
"great ! great ! great");
659 CHECK (recurse.success());
660 CHECK (recurse.getResult() == 3 );
662 CHECK (not recurse.parse(
" ! great"));
663 CHECK (recurse.parse(
"great ! great actor").getResult() == 2);
664 CHECK (recurse.parse(
"great ! great ! actor").getResult() == 2);
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)); });
672 CHECK (sqrt.parse(
" √x ").getResult() == 0 );
673 CHECK (sqrt.parse(
" √2 ").getResult() ==
"1.4142136"_expect);
679 auto expr = expectResult<double>();
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); });
686 CHECK (expr.canInvoke());
687 CHECK (not expr.hasResult());
689 expr.parse(
" 42 forever");
690 CHECK (expr.success());
691 CHECK (expr.getResult() == 42 );
693 expr.parse(
" 42 + 13 =?");
694 CHECK (expr.success());
695 CHECK (expr.getResult() == 55 );
697 expr.parse(
" 1 + 4/3 ");
698 CHECK (expr.success());
699 CHECK (expr.getResult() ==
"2.3333333"_expect);
701 expr.parse(
"(2+2)/(2+1) + 4/2");
702 CHECK (expr.success());
703 CHECK (expr.getResult() ==
"3.3333333"_expect);
705 expr.parse(
"(1 + √5) / 2 ");
706 CHECK (expr.success());
707 CHECK (expr.getResult() ==
"1.618034"_expect);
721 verify_nestedSpecTerms()
723 auto content =
accept(R
"_([^,\\\(\)\[\]{}<>"]+)_");
724 auto escape =
accept(R
"_(\\.)_");
726 auto nonQuot =
accept(R
"_([^"\\]+)_");
730 auto paren = expectResult<Nil>();
731 auto nonParen =
accept(R
"_([^\\\(\)"]+)_");
736 paren =
accept_bracket(
"()", parenCont).bind([](
auto){
return Nil{}; });
744 auto apply = [](
auto& syntax)
745 {
return [&](
auto const& str)
746 {
return accept(syntax).bindMatch()
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 );
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 );
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 );
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 );
774 LAUNCHER (Parse_test,
"unit common");
string typeSymbol()
Short readable type identifier, not necessarily unique or complete.
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.
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...
auto accept(SPEC &&clauseDef)
===== Syntax clause builder DSL =====
auto buildConnex(Nil)
»Null-Connex« which always successfully accepts the empty sequence
auto accept_bracket(SPEC1 &&openDef, SPEC2 &&closeDef, SPEC3 &&bodyDef)
Start Syntax with a sub-clause enclosed into a bracketing construct.
Parser(Nil) -> Parser< NulP >
boost::program_options::options_description Syntax
Convenience wrappers and definitions for parsing structured definitions.
Simplistic test class runner.
#define LAUNCHER(_TEST_CLASS_, _GROUPS_)
A collection of frequently used helper functions to support unit testing.
Metaprogramming with tuples-of-types and the std::tuple record.