From 5fed95b9292a4c021b42e8d11e6ede1f22ba78cb Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 22 Jan 2025 22:31:25 +0100 Subject: [PATCH] Library: integrate repeated clauses into the DSL Meanwhile, some kind of style scheme has emerged for the DSL: We're working much with postfix-decorating operators, which augment or extend the ''whole syntax clauses defined thus far'' In accordance with this scheme, I decided also to treat repeated expression as a postfix operator (other than initially planned). This means, the actual body to be repeated is ''the syntax clause defined thus far'', and the repeat()-operator only details the number of repetitions and an optional delimiter. --- src/lib/parse.hpp | 120 ++++++++++++++++++++++++++----- tests/library/parse-test.cpp | 49 +++++++++++-- wiki/thinkPad.ichthyo.mm | 136 +++++++++++++++++++++++------------ 3 files changed, 235 insertions(+), 70 deletions(-) diff --git a/src/lib/parse.hpp b/src/lib/parse.hpp index 734e77459..49a186b9b 100644 --- a/src/lib/parse.hpp +++ b/src/lib/parse.hpp @@ -27,7 +27,9 @@ #define LIB_PARSE_H +#include "lib/error.hpp" #include "lib/branch-case.hpp" +#include "lib/format-string.hpp" #include "lib/meta/variadic-rebind.hpp" #include "lib/meta/function.hpp" #include "lib/meta/trait.hpp" @@ -41,6 +43,7 @@ namespace util { namespace parse { + namespace err = lumiera::error; using std::move; using std::forward; @@ -52,11 +55,14 @@ namespace util { using std::decay_t; using std::tuple; using std::array; + using util::_Fmt; using StrView = std::string_view; /** + * Parse evaluation result + * @tparam RES model type to bind */ template struct Eval @@ -240,15 +246,16 @@ namespace util { /** Special case Product Model to represent iterative sequence */ template struct IterModel + : std::vector { - + RES& get (size_t i) { return this->at(i); } }; /** Marker-Tag for the result from a sub-expression, not to be joined */ template struct SubModel { - + RES model; }; /** Standard case : combinator of two model branches */ @@ -282,12 +289,6 @@ namespace util { using Result = TAG; }; - /** Special Case : absorb further similar elements into IterModel */ - template - struct _Join, RES> - { - using Result = IterModel; - }; /** accept sequence of two parse functions */ @@ -359,6 +360,49 @@ namespace util { } + /** repeatedly accept parse-function, optionally delimited. */ + template + auto + repeatedConnex (uint min, uint max + ,C1&& bodyConnex, C2&& delimConnex) + { + using Res = typename decay_t::Result; + using IterResult = IterModel; + using IterEval = Eval; + return Connex{[sep = forward(delimConnex) + ,body = forward(bodyConnex) + ,min,max + ] + (StrView toParse) -> IterEval + { + uint consumed{0}; + IterResult results; + do + { + uint offset{0}; + if (not results.empty()) + { // look for delimiter within sequence + auto delim = sep.parse (toParse); + if (not delim.result) + break; + offset += delim.consumed; + } + auto eval = body.parse (toParse.substr(offset)); + if (not eval.result) + break; + offset += eval.consumed; + results.emplace_back (move(*eval.result)); + toParse = toParse.substr(offset); + consumed += offset; + } + while (results.size() < max); + return results.size() >= min? IterEval{move(results), consumed} + : IterEval{std::nullopt}; + }}; + } + + + template class Syntax; @@ -424,10 +468,11 @@ using Sigi = typename _Fun::Sig; using Connex = typename PAR::Connex; using Result = typename PAR::Result; - bool success() const { return bool(Syntax::result); } - bool hasResult() const { return bool(Syntax::result); } - Result& getResult() { return * Syntax::result; } - Result&& extractResult(){ return move(getResult()); } + bool success() const { return bool(Syntax::result); } + bool hasResult() const { return bool(Syntax::result); } + size_t consumed() const { return Eval::consumed;} + Result& getResult() { return * Syntax::result; } + Result&& extractResult(){ return move(getResult()); } Syntax() : parse_{NullType()} @@ -457,6 +502,9 @@ using Sigi = typename _Fun::Sig; return parse_; } + + /** ===== Syntax clause builder DSL ===== */ + template auto seq (SPEC&& clauseDef) @@ -475,14 +523,47 @@ using Sigi = typename _Fun::Sig; ,Parser{forward (clauseDef)})); } - private: - Eval& - eval() + auto + repeat(uint cnt =uint(-1)) { - return *this; + return repeat (1,cnt, NullType{}); } + + template + auto + repeat (SPEC&& delimDef) + { + return repeat (1,uint(-1), forward (delimDef)); + } + + template + auto + repeat (uint cnt, SPEC&& delimDef) + { + return repeat (cnt,cnt, forward (delimDef)); + } + + template + auto + repeat (uint min, uint max, SPEC&& delimDef) + { + if (max max:%d"} + % min % max }; + if (max == 0) + throw err::Invalid{"Invalid repeat with max ≡ 0 repetitions"}; + + return accept( + repeatedConnex (min,max + ,move(parse_) + ,Parser{forward (delimDef)})); + } + + private: + Eval& eval() { return *this;} }; - + + template auto accept (SPEC&& clauseDef) @@ -490,8 +571,9 @@ using Sigi = typename _Fun::Sig; return Syntax{Parser{forward (clauseDef)}}; } - // template - // Parser(Syntax const&) -> Parser; + /** empty syntax clause to start further definition */ + auto accept() { return Syntax>{}; } + }// namespace parse diff --git a/tests/library/parse-test.cpp b/tests/library/parse-test.cpp index a6ee688b2..b894f1759 100644 --- a/tests/library/parse-test.cpp +++ b/tests/library/parse-test.cpp @@ -20,7 +20,6 @@ #include "lib/test/run.hpp" #include "lib/test/test-helper.hpp" #include "lib/parse.hpp" -//#include "lib/iter-explorer.hpp" //#include "lib/format-util.hpp" #include "lib/meta/tuple-helper.hpp" #include "lib/test/diagnostic-output.hpp"//////////////////TODO @@ -382,13 +381,13 @@ namespace test { return hasResults()? IterEval{move(results), consumed} : IterEval{std::nullopt}; }; - string s1{"Seit umschlungen, Millionen"}; + string s1{"seid umschlungen, Millionen"}; string s2{"beguile, extort, profit"}; auto e1 = parseSeq(s1); CHECK (e1.result); CHECK (e1.result->size() == 1); - CHECK (e1.result->at(0).str() == "Seit"); + CHECK (e1.result->at(0).str() == "seid"); CHECK (e1.result->at(0).suffix() == " umschlungen, Millionen"); CHECK (e1.consumed == 4); @@ -406,7 +405,49 @@ namespace test { //______________________________________________ // DSL parse clause builder: iterative sequence... - auto syntax1 = accept("brazen").alt("bragging"); + auto syntax1 = accept(term).repeat(","); + + // Perform the same parse as demonstrated above.... + CHECK (not syntax1.hasResult()); + syntax1.parse(s1); + CHECK (syntax1.success()); + auto res1 = syntax1.getResult(); + CHECK (res1.size() == 1); + CHECK (res1.get(0).str() == "seid"); + + syntax1.parse(s2); + CHECK (syntax1.success()); + res1 = syntax1.getResult(); + CHECK (res1.size() == 3); + CHECK (res1[0].str() == "beguile"); + CHECK (res1[1].str() == "extort" ); + CHECK (res1[2].str() == "profit" ); + + auto syntax2 = accept(term).repeat(1,2,","); + auto syntax3 = accept(term).repeat(4,","); + syntax2.parse(s2); + syntax3.parse(s2); + CHECK ( syntax2); + CHECK (not syntax3); + CHECK (syntax2.getResult().size() == 2); + CHECK (s2.substr(syntax2.consumed()) == ", profit"); + + auto sx = s2 + " , \tdump"; + syntax3.parse(sx); + CHECK (syntax3); + CHECK (syntax3.getResult().size() == 4); + CHECK (syntax3.getResult()[0].str() == "beguile"); + CHECK (syntax3.getResult()[1].str() == "extort" ); + CHECK (syntax3.getResult()[2].str() == "profit" ); + CHECK (syntax3.getResult()[3].str() == "dump" ); + + auto syntax4 = accept(term).repeat(); + syntax4.parse(s1); + CHECK (syntax4.success()); + CHECK (syntax4.getResult().size() == 2); + CHECK (syntax4.getResult()[0].str() == "seid"); + CHECK (syntax4.getResult()[1].str() == "umschlungen" ); + CHECK (s1.substr(syntax4.consumed()) == ", Millionen"); } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 602b441e7..904498ffc 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -55197,7 +55197,18 @@ - + + + + + + + + + + + + @@ -55924,8 +55935,7 @@ ⟹ Resultat: man kann in den jeweiligen «Slot» nur mit einem kompatiblen Typ rein, und Typsicherheit ist gewährleistet (im Parser; wenn ein Client falsch zugreift, ist er selber schuld)

- - +
@@ -56533,7 +56543,7 @@
- + @@ -56553,8 +56563,7 @@ - - + @@ -56568,8 +56577,7 @@ linker Zweig: ein sub-Model, in dem irgend ein Zweig gematched hat

- - +
@@ -56600,8 +56608,7 @@ Der Argument-Pack muß stets am Ende stehen

- - +
@@ -56630,8 +56637,7 @@ Grundidee: man baut die neue, umgebaute Typ-Sequenz in den variadischen Argumenten eines beliebigen Templates, das selbst als Template-Template-Parameter gegeben wird. Damit kann man unmittelbar in einem einzigen Zug das redefinierte Ziel-Template konstruieren, ohne erst in eine andere Verarbeitungs-Domäne (tuple, Typsequenz, Typliste) mappen zu müssen. Zudem kann das gleiche Verarbeitungs-Template auch Spezial-Belegungen für Hilfs-Operationen mit anbieten, und man kann gleich die häufigsten verwandten Tools in einer einzigen Definition zur Verfügung stellen.

- - +
@@ -56653,8 +56659,7 @@ da zeichnet sich ein Schema ab, das die bekannten Sequenz-Umordnungen sehr direkt ausführt, ohne erst in eine andere Repräsentation (wie Typelist) zu mappen. Trotzdem ist der Aufwand O(n), für das Umkehren der Sequenz sogar O(n²)

- - + @@ -56674,7 +56679,7 @@
- + @@ -56692,8 +56697,7 @@ ...Hinweis darauf ist der Umstand, daß ich gar nicht mehr viel auswerten / prüfen muß, sondern direkt der Match auf die Konstruktor-Argumente den Rest der Logik erledigt.

- - +
@@ -56716,8 +56720,7 @@ da geht nämlich eine Branch-ID ein, und die muß <= TOP sein

- - +
@@ -56735,8 +56738,9 @@
- - + + + @@ -56749,8 +56753,7 @@ wenn am Ende keine weitere Iteration akzeptiert werden kann, ist das kein Fehler, sondern wir stehen hinter der zuletzt akzeptierten Iteration

- - +
@@ -56762,8 +56765,7 @@ ein Trenner muß nicht gegeben sein (dann wird lediglich der Rumpf iteriert); wenn aber ein Trenner gegeben ist, dann wird er beim 1.Mal explizit übersprungen (darf also nicht da sein), bei allen anderen Iterationen wird er zu Beginn der Iteration erwartet

- -
+ @@ -56780,8 +56782,7 @@ man kann sich Situationen denken.... aber dann hätte man auch stets dieses Ergebnis-Tupel zu handhaben.

- - +
@@ -56790,15 +56791,15 @@
- + - - + + @@ -56809,8 +56810,7 @@ weil wir bisher keinen generischen Rekursions-Mechanismus vorsehen und ansonsten die Zahl der Iterationen erst in einem Post-Proecssing-Schritt geprüft werden könnte.

- -
+
@@ -56822,8 +56822,7 @@ Es würde sich um einige Randfälle handeln, denn im Regelfall ist eine Iteration offen / abzählbar. Und wir müßten in eine derart performance-kritische Situation vorstoßen, in der eine Heap-Allokation prohibitiv wäre

- -
+
@@ -56840,9 +56839,9 @@
- - - + + + @@ -56854,10 +56853,42 @@ + + + + + + + + + +

+ dem inzwischen etablierten Schema der Syntax-Klauseln zufolge muß jeder Junktor etwas mit der aktuellen Syntax-Spec anknüpfen. Wenn nun ein iter()-Prädikat essentiell eine neue Syntax anfängt, wäre eine implizite Konvention zu treffen, was mit der bestehenden Klausel passiert. Naheliegend wäre eine Sequenz; dann besteht aber die Gefahr, daß in der Praxis oft eine leere Sequenz spezifiziert wird (wäre noch akzeptabel) — und man zur Model-Anknüpfung stets dieses Tupel aufmachen müßte (schröcklich) +

+ +
+
- - - + + + + + + +

+ dann muß man aber die Postfix-takes-all-Logik akzeptieren +

+ +
+ + + +
+
+
+ + + @@ -56867,7 +56898,7 @@ - + @@ -56879,14 +56910,23 @@ der Implementierung nach ist es ein Dekorator

- -
+ - - + + + + + + + + + + + + @@ -56946,13 +56986,15 @@ - - + + + +