diff --git a/src/common/config-rules.hpp b/src/common/config-rules.hpp index f9fe4a7f4..84262a253 100644 --- a/src/common/config-rules.hpp +++ b/src/common/config-rules.hpp @@ -166,7 +166,7 @@ namespace lumiera { * @query any goals to be fulfilled by the solution. * @return false if resolution failed. In this case, solution ptr is empty. */ - virtual bool resolve (P& solution, const Query& q) = 0; + virtual bool resolve (P& solution, Query const& q) = 0; }; // TODO: the Idea is to provide specialisations for the concrete types @@ -221,8 +221,8 @@ namespace lumiera { * will magically succeed with every candidate object provided. This * is currently necessary to get objects into the defaults manager, * as the query system is not able to do real query resolution */ - void setFakeBypass(string const& q); - bool isFakeBypass (string const& q); + void setFakeBypass(lumiera::QueryKey const& q); + bool isFakeBypass (lumiera::QueryKey const& q); /////////////////////////////////////////////////////////////////////////////TICKET 710 } // namespace query diff --git a/src/common/query.hpp b/src/common/query.hpp index 3fbe028e6..7e4d43564 100644 --- a/src/common/query.hpp +++ b/src/common/query.hpp @@ -87,6 +87,12 @@ namespace lumiera { { Kind kind; IxID type; + + explicit + QueryID(Kind k =EMPTY, IxID t=1) + : kind(k) + , type(t) + { } }; QueryID const& @@ -151,15 +157,18 @@ namespace lumiera { - /** Context used for generating type-IDs to denote - * the specific result types of issued queries */ - typedef lib::TypedContext ResultType; - - template - inline IxID - getResultTypeID() ///< @return unique ID denoting result type RES - { - return ResultType::ID::get(); + namespace { + /** Context used for generating type-IDs to denote + * the specific result types of issued queries */ + typedef lib::TypedContext ResultType; + + template + inline IxID + getResultTypeID() ///< @return unique ID denoting result type RES + { + return ResultType::ID::get(); + } + } @@ -204,7 +213,7 @@ namespace lumiera { static QueryID defineQueryTypeID (Kind queryType = Goal::GENERIC) { - QueryID id = {queryType, getResultTypeID() }; + QueryID id(queryType, getResultTypeID()); return id; } @@ -249,17 +258,14 @@ namespace lumiera { , def_(querySpec) { } - static Builder - build (Kind queryType = Goal::GENERIC); - - Builder - rebuild() const; - - string - extractID (Symbol predicate) const; - operator QueryKey() const; + static Builder build (Kind queryType = Goal::GENERIC); + Builder rebuild() const; + + string extractID (Symbol predicate) const; + bool usesPredicate (Symbol predicate) const; + /* results retrieval */ @@ -290,12 +296,6 @@ namespace lumiera { { return hash_value (q.def_); } - - friend bool - operator== (Query const& q1, Query const& q2) - { - return q1.def_ == q2.def_; - } }; @@ -318,6 +318,12 @@ namespace lumiera { , def_(q) { } + /** the empty or bottom query key */ + QueryKey() + : id_() + , def_("NIL") + { } + // default copyable template @@ -348,6 +354,12 @@ namespace lumiera { return def_.degree_of_constriction(); } + bool + empty() const + { + return Goal::EMPTY == id_.kind; + } + friend bool operator< (QueryKey const& q1, QueryKey const& q2) @@ -358,6 +370,12 @@ namespace lumiera { ||(d1 == d2 && q1.def_ < q2.def_); } + friend bool + operator== (QueryKey const& q1, QueryKey const& q2) + { + return q1.def_ == q2.def_; + } + friend size_t hash_value (QueryKey const& q) { @@ -439,6 +457,14 @@ namespace lumiera { return *this; } + Builder& + prependConditions (string additionalQueryPredicates) + { + this->predicateForm_ = + lib::query::appendTerms(additionalQueryPredicates, this->predicateForm_); + return *this; + } + Builder& fromText (string queryPredicates) { @@ -481,6 +507,14 @@ namespace lumiera { } + template + inline bool + Query::usesPredicate (Symbol predicate) const + { + return lib::query::hasTerm(predicate, this->def_); + } + + /** automatic conversion from Query to QueryKey for indexing and ordering. * By defining a parameter of type QueryKey, any provided Query will be * automatically transformed into an generic representation usable for diff --git a/src/common/query/config-rules.cpp b/src/common/query/config-rules.cpp index f18b7b1c3..955fdc781 100644 --- a/src/common/query/config-rules.cpp +++ b/src/common/query/config-rules.cpp @@ -23,9 +23,11 @@ #include "lib/error.hpp" +#include "common/query.hpp" #include "common/config-rules.hpp" #include "proc/mobject/session/query/fake-configrules.hpp" +using lumiera::QueryKey; namespace lumiera { @@ -42,11 +44,11 @@ namespace lumiera { namespace query { namespace { // local definitions: implementing a backdoor for tests - string fakeBypass; + QueryKey fakeBypass; } - void setFakeBypass(string const& q) { fakeBypass = q; } - bool isFakeBypass (string const& q) { return q == fakeBypass; } + void setFakeBypass(QueryKey const& q) { fakeBypass = q; } + bool isFakeBypass (QueryKey const& q) { return q == fakeBypass; } /////////////////////////////////////////////////////////////////////////////TICKET 710 }// namespace query diff --git a/src/lib/query-util.cpp b/src/lib/query-util.cpp index 7fea96829..20dedb0fd 100644 --- a/src/lib/query-util.cpp +++ b/src/lib/query-util.cpp @@ -92,7 +92,7 @@ namespace lib { * \code extractID ("stream", "id(abc), stream(mpeg)") \endcode * yields \c "mpeg" */ - const string + string extractID (Symbol sym, const string& termString) { smatch match; @@ -106,20 +106,30 @@ namespace lib { /** (preliminary) helper: cut a term with the given symbol. * The term is matched, removed from the original string and returned * @note parameter termString will be modified! + * @todo as it seems we're not using the extracted term anymore, + * we could save the effort of rebuilding that term. */ - const string - removeTerm (Symbol sym, string& termString) + string + removeTerm (Symbol sym, string& queryString) { smatch match; - if (regex_search (termString, match, getTermRegex (sym))) + if (regex_search (queryString, match, getTermRegex (sym))) { string res (sym); res += "("+match[1]+")"; - termString.erase (match.position(), match[0].length()); + queryString.erase (match.position(), match[0].length()); return res; } else return ""; - } + } + + + bool + hasTerm (Symbol sym, string const& queryString) + { + smatch match; + return regex_search (queryString, match, getTermRegex (sym)); + } /** @note this is a very hackish preliminary implementation. @@ -146,6 +156,7 @@ namespace lib { appendTerms (string const& pred1, string const& pred2) { return isnil(pred1)? pred2 + : isnil(pred2)? pred1 : pred1 + ", " + pred2; } diff --git a/src/lib/query-util.hpp b/src/lib/query-util.hpp index f83bfd3a1..9a1de062a 100644 --- a/src/lib/query-util.hpp +++ b/src/lib/query-util.hpp @@ -58,9 +58,10 @@ namespace lib { uint countPred (const string&); - const string extractID (Symbol, string const& termString); + string extractID (Symbol, string const& termString); - const string removeTerm (Symbol, string& termString); + string removeTerm (Symbol, string& queryString); + bool hasTerm (Symbol sym, string const& queryString); string appendTerms (string const& pred1, string const& pred2); diff --git a/src/proc/config-resolver.hpp b/src/proc/config-resolver.hpp index b353b8c13..75b0e47f9 100644 --- a/src/proc/config-resolver.hpp +++ b/src/proc/config-resolver.hpp @@ -25,6 +25,12 @@ /** @file config-resolver.hpp ** Definition of the concrete frontend for rule based configuration within the session. ** + ** @remarks This code will act as a hub to pull in, instrument and activate a lot of further code. + ** All the types mentioned in the #InterfaceTypes typelist will be prepared to be used + ** in rules based setup and configuration; this definition will drive the generation of + ** all the necessary bindings and registration entries to make this work. This is in + ** accordance with the principle of generic programming: Instead of making things + ** uniform, we use related things in a similar manner. ** @note this is placeholder code using a preliminary/mock implementation... don't take this code too literal! ** @todo clarify the relation of config query and query-for-defaults ///////////////TICKET #705 ** diff --git a/src/proc/mobject/session/placement-index-query-resolver.cpp b/src/proc/mobject/session/placement-index-query-resolver.cpp index 4b4b9bb93..4c3678a5c 100644 --- a/src/proc/mobject/session/placement-index-query-resolver.cpp +++ b/src/proc/mobject/session/placement-index-query-resolver.cpp @@ -275,7 +275,7 @@ namespace session { QueryID whenQueryingFor() { - QueryID qID = {Goal::DISCOVERY, getResultTypeID >()}; + QueryID qID(Goal::DISCOVERY, getResultTypeID >()); return qID; } diff --git a/src/proc/mobject/session/query/fake-configrules.cpp b/src/proc/mobject/session/query/fake-configrules.cpp index dd3421de9..c6c10208c 100644 --- a/src/proc/mobject/session/query/fake-configrules.cpp +++ b/src/proc/mobject/session/query/fake-configrules.cpp @@ -53,6 +53,7 @@ namespace session { using asset::PProcPatt; // using lib::query::extractID; + using lib::query::extractID; ///////////////TODO dto using lib::query::removeTerm; @@ -128,7 +129,7 @@ namespace session { /** special case: create a new pipe with matching pipe and stream IDs on the fly when referred... */ bool - MockTable::fabricate_matching_new_Pipe (Query& q, string const& pipeID, string const& streamID) + MockTable::fabricate_matching_new_Pipe (Query const& q, string const& pipeID, string const& streamID) { typedef WrapReturn::Wrapper Ptr; @@ -139,7 +140,7 @@ namespace session { /** special case: create a new pipe for a specific stream ID */ bool - MockTable::fabricate_just_new_Pipe (Query& q ) + MockTable::fabricate_just_new_Pipe (Query const& q ) { typedef WrapReturn::Wrapper Ptr; @@ -150,7 +151,7 @@ namespace session { /** special case: create/retrieve new processing pattern for given stream ID... */ bool - MockTable::fabricate_ProcPatt_on_demand (Query& q) + MockTable::fabricate_ProcPatt_on_demand (Query const& q) { typedef const ProcPatt cPP; typedef WrapReturn::Wrapper Ptr; @@ -165,18 +166,22 @@ namespace session { * the session's timelines / sequences to retrieve an existing object * with matching ID... */ bool - MockTable::fabricate_Timeline_on_demand (Query& query) + MockTable::fabricate_Timeline_on_demand (Query const& query) { - typedef asset::Timeline aTl; - typedef WrapReturn::Wrapper Ptr; - - UNIMPLEMENTED ("generic query remolding");////////////////////////////////////////////////////////////////////////////////////////////TODO - string nameID = "TODO";//removeTerm ("id", query);////////////////////////////////////////////////////////////////////////////////////////////TODO + typedef asset::Timeline aTL; + typedef WrapReturn::Wrapper Ptr; + + string nameID = query.extractID("id"); if (isnil (nameID)) - nameID = "TODO";//removeTerm ("timeline", query);////////////////////////////////////////////////////////////////////////////////////////////TODO + nameID = query.extractID("timeline"); if (isnil (nameID)) nameID = "prime"; -// query.insert (0, "id("+nameID+"), "); + + Query normalisedQuery = + query.rebuild() + .removeTerm("id") + .removeTerm("timeline") + .prependConditions("id("+nameID+")"); // try to find an existing one with the desired id Ptr newTimeline; @@ -189,26 +194,29 @@ namespace session { } if (!newTimeline) - newTimeline = Struct::retrieve.made4fake (query); // no suitable Timeline found: create and attach new one - - answer_->insert (entry (query, newTimeline)); // "learn" the found/created Timeline as new solution + newTimeline = Struct::retrieve.made4fake (normalisedQuery); // no suitable Timeline found: create and attach new one + answer_->insert (entry (normalisedQuery, newTimeline)); // "learn" the found/created Timeline as new solution return true; } /** special case: fabricate new Timeline, maybe using ID specs from the query... */ bool - MockTable::fabricate_Sequence_on_demand (Query& query) + MockTable::fabricate_Sequence_on_demand (Query const& query) { - typedef asset::Sequence aSq; - typedef WrapReturn::Wrapper Ptr; - - UNIMPLEMENTED ("generic Query remolding");////////////////////////////////////////////////////////////////////////////////////////////TODO - string nameID = "TODO";//removeTerm ("id", query);////////////////////////////////////////////////////////////////////////////////////////////TODO + typedef asset::Sequence aSeq; + typedef WrapReturn::Wrapper Ptr; + + string nameID = query.extractID("id"); if (isnil (nameID)) - nameID = "TODO";//removeTerm ("sequence", query);////////////////////////////////////////////////////////////////////////////////////////////TODO + nameID = query.extractID("sequence"); if (isnil (nameID)) nameID = "first"; -// query.insert (0, "id("+nameID+"), "); + + Query normalisedQuery = + query.rebuild() + .removeTerm("id") + .removeTerm("sequence") + .prependConditions("id("+nameID+")"); // try to find an existing sequence with the desired id Ptr newSequence; @@ -220,10 +228,9 @@ namespace session { break; } - if (!newSequence) - newSequence = Struct::retrieve.made4fake (query); // no suitable found: create and attach new Sequence - - answer_->insert (entry (query, newSequence)); + if (!newSequence) + newSequence = Struct::retrieve.made4fake (normalisedQuery); // no suitable found: create and attach new Sequence + answer_->insert (entry (normalisedQuery, newSequence)); // "learn" the found/created new solution return true; } @@ -231,7 +238,7 @@ namespace session { /** for entering "valid" solutions on-the-fly from tests */ template bool - MockTable::set_new_mock_solution (Query& q, typename WrapReturn::Wrapper& obj) + MockTable::set_new_mock_solution (Query const& q, typename WrapReturn::Wrapper& obj) { UNIMPLEMENTED ("generic query-key");////////////////////////////////////////////////////////////////////////////////////////////TODO // answer_->erase (q.asKey());////////////////////////////////////////////////////////////////////////////////////////////TODO @@ -239,7 +246,7 @@ namespace session { return true; } // generate the necessary specialisations----------------------------- - template bool MockTable::set_new_mock_solution (Query&, PPipe&); + template bool MockTable::set_new_mock_solution (Query const&, PPipe&); diff --git a/src/proc/mobject/session/query/fake-configrules.hpp b/src/proc/mobject/session/query/fake-configrules.hpp index 75c882a26..8dee1c10b 100644 --- a/src/proc/mobject/session/query/fake-configrules.hpp +++ b/src/proc/mobject/session/query/fake-configrules.hpp @@ -29,6 +29,12 @@ ** -- later on, when we use a real Prolog interpreter, it still may be useful for ** testing and debugging. ** + ** @remarks the primary purpose of this header and fake-configrules.cpp is to define + ** the type specialisations of the \c QueryHandler::resolve(solution,query) + ** function(s). Below, there is a really confusing and ugly ping-pong game involving + ** the faked solutions and the mocked defaults manager. This is spaghetti code, written + ** for the reason everyone writes spaghetti code: to get away with it. So please look + ** away, some day the real thing will be there, displacing this mess without further notice. ** @todo to be removed in Alpha, when integrating a real resolution engine /////////////////TICKET #710 ** ** @see lumiera::Query @@ -68,8 +74,7 @@ namespace session { using lib::P; using lumiera::Query; - using lib::query::removeTerm; //////////////TODO better use Query::Builder - using lib::query::extractID; ///////////////TODO dto + using lumiera::QueryKey; using lumiera::query::isFakeBypass; /////////TODO placeholder until there is a real resolution engine using util::contains; @@ -95,10 +100,11 @@ namespace session { /** helper detecting if a query actually intended to retrieve a "default" object. * This implementation is quite crude, of course it would be necessary actually to * parse and evaluate the query. @note query is modified if "default" ... */ + template inline bool - treat_as_defaults_query (string& querySpec) + is_defaults_query (Query const& querySpec) { - return !isnil (removeTerm ("default", querySpec)); + return querySpec.usesPredicate ("default"); } } // details (end) @@ -107,7 +113,12 @@ namespace session { /** * the actual table holding preconfigured answers - * packaged as boost::any objects. + * packaged as boost::any objects. MockTable is the implementation base; + * subclasses for the individual types are layered below to define the + * \c resolve(..) functions. Finally #MockConfigRules wraps things up. + * + * The implementation relies on boost::any records to stash the objects + * in automatically managed heap memory. */ class MockTable : public proc::ConfigResolver @@ -124,15 +135,15 @@ namespace session { // special cases.... template - bool detect_case (typename WrapReturn::Wrapper&, Query& q); - bool fabricate_matching_new_Pipe (Query& q, string const& pipeID, string const& streamID); - bool fabricate_just_new_Pipe (Query& q); - bool fabricate_ProcPatt_on_demand (Query& q); - bool fabricate_Timeline_on_demand (Query& q); - bool fabricate_Sequence_on_demand (Query& q); + bool detect_case (typename WrapReturn::Wrapper&, Query const&); + bool fabricate_matching_new_Pipe (Query const& q, string const& pipeID, string const& streamID); + bool fabricate_just_new_Pipe (Query const& q); + bool fabricate_ProcPatt_on_demand (Query const& q); + bool fabricate_Timeline_on_demand (Query const& q); + bool fabricate_Sequence_on_demand (Query const& q); template - bool set_new_mock_solution (Query& q, typename WrapReturn::Wrapper& candidate); + bool set_new_mock_solution (Query const& q, typename WrapReturn::Wrapper& candidate); private: @@ -141,8 +152,8 @@ namespace session { /** - * building block defining how to do - * the mock implementation for \em one type. + * building block providing the + * mock implementation for a \em single type. * We simply access a table holding pre-created objects. */ template @@ -171,19 +182,17 @@ namespace session { bool try_special_case (Ret& solution, Query const& q) { - if (true)//solution && isFakeBypass(q)) // backdoor for tests////////////////////////////////////////////////////////////////////////////////////////////TODO + if (solution && isFakeBypass(q)) // backdoor for tests return solution; - string querySpec ;//(q);////////////////////////////////////////////////////////////////////////////////////////////TODO - if (treat_as_defaults_query (querySpec)) + if (is_defaults_query (q)) { - Query defaultsQuery = Query::build().fromText(querySpec); + Query defaultsQuery = q.rebuild().removeTerm("default"); return solution = Session::current->defaults (defaultsQuery); - } // may cause recursion + } // may lead to recursion - Query newQuery = q; - if (this->detect_case (solution, newQuery)) - return resolve (solution, newQuery); + if (this->detect_case (solution, q)) + return resolve (solution, q); return solution = Ret(); // fail: return default-constructed empty smart ptr } @@ -193,21 +202,20 @@ namespace session { /** Hook for treating very special cases for individual types only */ template inline bool - MockTable::detect_case (typename WrapReturn::Wrapper&, Query& q) + MockTable::detect_case (typename WrapReturn::Wrapper&, Query const&) { -// q.clear(); // end recursion////////////////////////////////////////////////////////////////////////////////////////////TODO return false; } template<> inline bool - MockTable::detect_case (WrapReturn::Wrapper& candidate, Query& q) + MockTable::detect_case (WrapReturn::Wrapper& candidate, Query const& q) { - if (true)//!isnil (extractID("make", q)))////////////////////////////////////////////////////////////////////////////////////////////TODO + if (q.usesPredicate ("make")) // used by tests to force fabrication of a new "solution" return fabricate_just_new_Pipe (q); - const string pipeID = "TODO";//extractID("pipe", q);////////////////////////////////////////////////////////////////////////////////////////////TODO - const string streamID = "TODO";//extractID("stream", q);////////////////////////////////////////////////////////////////////////////////////////////TODO + const string pipeID = q.extractID("pipe"); + const string streamID = q.extractID("stream"); if (candidate && pipeID == candidate->getPipeID()) return set_new_mock_solution (q, candidate); // "learn" this solution to be "valid" @@ -218,39 +226,35 @@ namespace session { if (!candidate && (!isnil(streamID) || !isnil(pipeID))) return fabricate_just_new_Pipe (q); -// q.clear();////////////////////////////////////////////////////////////////////////////////////////////TODO return false; } + template<> inline bool - MockTable::detect_case (WrapReturn::Wrapper& candidate, Query& q) + MockTable::detect_case (WrapReturn::Wrapper& candidate, Query const& q) { - const string streamID = "TODO";//extractID("stream", q);////////////////////////////////////////////////////////////////////////////////////////////TODO + const string streamID = q.extractID("stream"); if (!candidate && !isnil(streamID)) return fabricate_ProcPatt_on_demand (q); - -// q.clear();////////////////////////////////////////////////////////////////////////////////////////////TODO return false; } + template<> inline bool - MockTable::detect_case (WrapReturn::Wrapper& candidate, Query& q) + MockTable::detect_case (WrapReturn::Wrapper& candidate, Query const& q) { if (!candidate) return fabricate_Timeline_on_demand (q); - -// q.clear();////////////////////////////////////////////////////////////////////////////////////////////TODO return bool(candidate); } + template<> inline bool - MockTable::detect_case (WrapReturn::Wrapper& candidate, Query& q) + MockTable::detect_case (WrapReturn::Wrapper& candidate, Query const& q) { if (!candidate) return fabricate_Sequence_on_demand (q); - -// q.clear();////////////////////////////////////////////////////////////////////////////////////////////TODO return bool(candidate); } diff --git a/tests/components/proc/mobject/session/defs-manager-impl-test.cpp b/tests/components/proc/mobject/session/defs-manager-impl-test.cpp index e69fd72d7..0caed09ad 100644 --- a/tests/components/proc/mobject/session/defs-manager-impl-test.cpp +++ b/tests/components/proc/mobject/session/defs-manager-impl-test.cpp @@ -117,11 +117,13 @@ namespace test { CHECK (!find (pipe2->getPipeID()), "accidental clash of random test-IDs"); // now declare that these objects should be considered "default" -lumiera::query::setFakeBypass(""); /////////////////////////////////////////////////TODO mock resolution - CHECK (Session::current->defaults.define (pipe1, Query (""))); // unrestricted default + Query justAnyPipe (""); +lumiera::query::setFakeBypass(justAnyPipe); /////////////////////////////////////////////////TODO mock resolution + CHECK (Session::current->defaults.define (pipe1, justAnyPipe)); // unrestricted default -lumiera::query::setFakeBypass("stream("+sID+")"); ///////////////////////////////////TODO mock resolution - CHECK (Session::current->defaults.define (pipe2, Query ("stream("+sID+")"))); + Query pipeWithSpecificStream("stream("+sID+")"); +lumiera::query::setFakeBypass(pipeWithSpecificStream); ///////////////////////////////////TODO mock resolution + CHECK (Session::current->defaults.define (pipe2, pipeWithSpecificStream)); CHECK ( find (pipe1->getPipeID()), "failure declaring object as default"); CHECK ( find (pipe2->getPipeID()), "failure declaring object as default"); diff --git a/tests/components/proc/mobject/session/query-resolver-test.cpp b/tests/components/proc/mobject/session/query-resolver-test.cpp index be943f35f..aec323dd9 100644 --- a/tests/components/proc/mobject/session/query-resolver-test.cpp +++ b/tests/components/proc/mobject/session/query-resolver-test.cpp @@ -158,8 +158,8 @@ namespace test{ DummyTypedSolutionProducer() : QueryResolver() { - Goal::QueryID case1 = {Goal::GENERIC, getResultTypeID()}; - Goal::QueryID case2 = {Goal::GENERIC, getResultTypeID()}; + Goal::QueryID case1(Goal::GENERIC, getResultTypeID()); + Goal::QueryID case2(Goal::GENERIC, getResultTypeID()); installResolutionCase(case1, &resolutionFunction ); installResolutionCase(case2, &resolutionFunction );