/* 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/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" #include "lib/regex.hpp" #include #include #include #include #include namespace util { namespace parse { namespace err = lumiera::error; using std::move; using std::forward; using std::optional; using lib::meta::_Fun; using lib::meta::has_Sig; using lib::meta::NullType; using lib::meta::_Vari; 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 { 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 N = 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); } template auto get() { return std::get (*this); } }; /** * Sum Model : results from a disjunction of parsing clauses, * which are are tested and accepted as alternatives, one at least. */ template struct AltModel : lib::BranchCase { using Alt = lib::BranchCase; static constexpr size_t N = Alt::TOP; template using Additionally = AltModel; template Additionally addBranch() ///< mark-up existing model to add a further branch-case { Additionally& upFaked = reinterpret_cast&> (*this); return {move (upFaked)}; } // this trick works due to similar storage layout /* === Builder functions to mark which side of the combinator to pick === */ using SubSeq = typename _Vari::Prefix; ///< a nested sub-model to extend using Penult = typename _Vari::Penult; ///< plain value expected for left-branch using Ultima = typename _Vari::Ultima; ///< plain value expected for right-branch static AltModel mark_left (SubSeq&& leftCases) { return {leftCases.template addBranch()}; } static AltModel mark_left (Penult&& leftCase) { return {Alt::TOP-1, move(leftCase)}; } static AltModel mark_right (Ultima&& rightCase) { return {Alt::TOP, move(rightCase)}; } private: template AltModel (size_t branchID, INIT&& init) : Alt{branchID, forward (init)} { } }; /** 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 */ 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; }; /** 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}; }}; } /** accept sequence of two parse functions */ template auto branchedConnex (C1&& connex1, C2&& connex2) { using R1 = typename decay_t::Result; using R2 = typename decay_t::Result; using SumResult = typename _Join::Result; using SumEval = Eval; return Connex{[conL = forward(connex1) ,conR = forward(connex2) ] (StrView toParse) -> SumEval { auto eval1 = conL.parse (toParse); if (eval1.result) { uint endBranch1 = eval1.consumed; return SumEval{SumResult::mark_left (move(*eval1.result)) ,endBranch1 }; } auto eval2 = conR.parse (toParse); if (eval2.result) { uint endBranch2 = eval2.consumed; return SumEval{SumResult::mark_right (move(*eval2.result)) ,endBranch2 }; } return SumEval{std::nullopt}; }}; } /** 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; 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); } size_t consumed() const { return Eval::consumed;} 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_; } /** ===== Syntax clause builder DSL ===== */ template auto seq (SPEC&& clauseDef) { return accept( sequenceConnex (move(parse_) ,Parser{forward (clauseDef)})); } template auto alt (SPEC&& clauseDef) { return accept( branchedConnex (move(parse_) ,Parser{forward (clauseDef)})); } auto repeat(uint cnt =uint(-1)) { 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) { return Syntax{Parser{forward (clauseDef)}}; } /** empty syntax clause to start further definition */ auto accept() { return Syntax>{}; } }// namespace parse using parse::accept; }// namespace util namespace lib { }// namespace lib #endif/*LIB_PARSE_H*/