/* PARSE.hpp - helpers for parsing textual specifications Copyright (C) 2024, Hermann Vosseler   **Lumiera** is free software; you can redistribute it and/or modify it   under the terms of the GNU General Public License as published by the   Free Software Foundation; either version 2 of the License, or (at your   option) any later version. See the file COPYING for further details. */ /** @file parse.hpp ** Convenience wrappers and definitions for parsing structured definitions. ** Whenever a specification syntax entails nested structures, extracting contents ** with regular expressions alone becomes tricky. Without much sophistication, a ** directly implemented simple recursive descent parser is often less brittle and ** easier to understand and maintain. With some helper abbreviations, notably ** a combinator scheme to work from building blocks, a hand-written solution ** can benefit from taking short-cuts, especially related to result bindings. */ #ifndef LIB_PARSE_H #define LIB_PARSE_H #include "lib/iter-adapter.hpp" #include "lib/meta/function.hpp" #include "lib/meta/trait.hpp" #include "lib/regex.hpp" #include #include namespace util { namespace parse { using std::move; using std::forward; using std::optional; using lib::meta::_Fun; using lib::meta::has_Sig; using lib::meta::NullType; using std::decay_t; using std::tuple; using StrView = std::string_view; /** */ template struct Eval { using Result = RES; optional result; size_t consumed{0}; }; template struct Connex : util::NonAssign { using PFun = FUN; PFun parse; using Result = typename _Fun::Ret::Result; Connex (FUN&& pFun) : parse{move(pFun)} { } }; auto buildConnex(NullType) { return Connex{[](StrView) -> Eval { return {NullType{}}; }}; } using NulP = decltype(buildConnex (NullType())); auto buildConnex (regex rex) { return Connex{[regEx = move(rex)] (StrView toParse) -> Eval { // skip leading whitespace... size_t pre = leadingWhitespace (toParse); toParse = toParse.substr(pre); auto result{matchAtStart (toParse,regEx)}; size_t consumed = result? pre+result->length() : 0; return {move(result), consumed}; }}; } using Term = decltype(buildConnex (std::declval())); Term buildConnex (string const& rexDef) { return buildConnex (regex{rexDef}); } template auto buildConnex (Connex const& anchor) { return Connex{anchor}; } template auto buildConnex (Connex && anchor) { return Connex{move(anchor)}; } template auto adaptConnex (CON&& connex, BIND&& modelBinding) { using RX = typename CON::Result; using Arg = lib::meta::_FunArg; static_assert (std::is_constructible_v, "Model binding must accept preceding model result."); using AdaptedRes = typename _Fun::Ret; return Connex{[origConnex = forward(connex) ,binding = forward(modelBinding) ] (StrView toParse) -> Eval { auto eval = origConnex.parse (toParse); if (eval.result) return {binding (move (*eval.result))}; else return {std::nullopt}; }}; } /* ===== building structured models ===== */ /** * Product Model : results from a conjunction of parsing clauses, * which are to be accepted in sequence, one after the other. */ template struct SeqModel : tuple { static constexpr size_t SIZ = sizeof...(RESULTS); using Seq = lib::meta::TySeq; using Tup = std::tuple; SeqModel() = default; template SeqModel (SeqModel&& seq, XX&& extraElm) : Tup{std::tuple_cat (seq.extractTuple() ,make_tuple (forward (extraElm)) )} { } template SeqModel (X1&& res1, X2&& res2) : Tup{move(res1), move(res2)} { } Tup&& extractTuple() { return move(*this); } }; /** * Sum Model : results from a disjunction of parsing clauses, * which are are tested and accepted as alternatives, one at least. */ template struct AltModel { }; /** Special case Product Model to represent iterative sequence */ template struct IterModel { }; /** Marker-Tag for the result from a sub-expression, not to be joined */ template struct SubModel { }; /** Standard case : combinator of two model branches */ template class TAG, class R1, class R2 =void> struct _Join { using Result = TAG; }; /** Generic case : extend a structured model by further branch */ template class TAG, class...RS, class R2> struct _Join,R2> { using Result = TAG; }; /** Special Case : absorb sub-expression without flattening */ template class TAG, class R1, class R2> struct _Join,R2> { using Result = TAG; }; template class TAG, class R1, class R2> struct _Join> { using Result = TAG; }; template class TAG, class R1, class R2> struct _Join,SubModel> { using Result = TAG; }; /** Special Case : absorb further similar elements into IterModel */ template struct _Join, RES> { using Result = IterModel; }; /** accept sequence of two parse functions */ template auto sequenceConnex (C1&& connex1, C2&& connex2) { using R1 = typename decay_t::Result; using R2 = typename decay_t::Result; using ProductResult = typename _Join::Result; using ProductEval = Eval; return Connex{[conL = forward(connex1) ,conR = forward(connex2) ] (StrView toParse) -> ProductEval { auto eval1 = conL.parse (toParse); if (eval1.result) { size_t posAfter1 = eval1.consumed; StrView restInput = toParse.substr(posAfter1); auto eval2 = conR.parse (restInput); if (eval2.result) { uint consumedOverall = posAfter1 + eval2.consumed; return ProductEval{ProductResult{move(*eval1.result) ,move(*eval2.result)} ,consumedOverall }; } } return ProductEval{std::nullopt}; }}; } template class Syntax; template class Parser : public CON { using PFun = typename CON::PFun; static_assert (_Fun(), "Connex must define a parse-function"); public: using Connex = CON; using Result = typename CON::Result; using Sigi = typename _Fun::Sig; //lib::test::TypeDebugger buggi; //lib::test::TypeDebugger guggi; static_assert (has_Sig(StrView)>() ,"Signature of the parse-function not suitable"); Eval operator() (StrView toParse) { return CON::parse (toParse); } template Parser (SPEC&& spec) : CON{buildConnex (forward (spec))} { } // template // Parser (Syntax const& anchor) // : CON{anchor} // { } // template // Parser (CON const& anchor) // : CON{anchor} // { } }; Parser(NullType) -> Parser; Parser(regex &&) -> Parser; Parser(regex const&) -> Parser; Parser(string const&) -> Parser; template Parser(Connex const&) -> Parser>; // // template // Parser(Syntax const&) -> Parser; template class Syntax : public Eval { PAR parse_; public: 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()); } Syntax() : parse_{NullType()} { } explicit Syntax (PAR&& parser) : parse_{move (parser)} { } explicit operator bool() const { return success(); } Syntax&& parse (StrView toParse) { eval() = parse_(toParse); return move(*this); } Connex const& getConny() const { return parse_; } template auto seq (SPEC&& clauseDef) { return accept( sequenceConnex (move(parse_) ,Parser{forward (clauseDef)})); } private: Eval& eval() { return *this; } }; template auto accept (SPEC&& clauseDef) { return Syntax{Parser{forward (clauseDef)}}; } // template // Parser(Syntax const&) -> Parser; }// namespace parse using parse::accept; }// namespace util namespace lib { }// namespace lib #endif/*LIB_PARSE_H*/