diff --git a/include/core/utils/converter.h b/include/core/utils/converter.h index 36d59e759018377bb81266932a57d7a5d827b318..85b47613e435facc904fa086550512297e40d79b 100644 --- a/include/core/utils/converter.h +++ b/include/core/utils/converter.h @@ -8,6 +8,8 @@ #include <iostream> #include <array> +#include "core/common/wolf.h" + /** @file */ @@ -35,7 +37,7 @@ namespace utils{ /** @Brief Returns all the substrings of @val that match @exp * @param val String to be matched * @param exp Regular expression - * @return <b>{std::vector<std::string>}</b> Collection of matching subtrings + * @return <b>{std::vector<std::string>}</b> Collection of matching substrings */ static inline std::vector<std::string> getMatches(std::string val, std::regex exp){ std::smatch res; @@ -91,6 +93,55 @@ namespace utils{ } return result; } + static inline std::vector<std::string> parseList(std::string val){ + std::stack<char> limiters; + std::stack<std::string> word_stack; + std::string current_word; + std::vector<std::string> words; + std::vector<char> chars(val.begin(), val.end()); + for(const char ¤t : chars){ + if(current == '['){ + limiters.push(current); + word_stack.push(current_word); + current_word = ""; + }else if(current == ']'){ + if(limiters.empty()) throw std::runtime_error("Unmatched delimiter"); + if(limiters.top() == '[') { + if(limiters.size() > 1) { + if(word_stack.empty()) word_stack.push(""); + current_word = word_stack.top() + "[" + current_word + "]"; + word_stack.pop(); + }else if(limiters.size() == 1 and current_word != "") words.push_back(current_word); + else current_word += current; + limiters.pop(); + }else throw std::runtime_error("Unmatched delimiter"); + }else if(current == '{'){ + limiters.push(current); + word_stack.push(current_word); + current_word = ""; + }else if(current == '}'){ + if(limiters.top() == '{') { + if(limiters.size() > 1) { + if(word_stack.empty()) word_stack.push(""); + current_word = word_stack.top() + "{" + current_word + "}"; + word_stack.pop(); + }else if(limiters.size() == 1) words.push_back(current_word); + else current_word += current; + limiters.pop(); + }else throw std::runtime_error("Unmatched delimiter"); + }else if(current == ',') { + if(limiters.size() == 1 and current_word != "") { + words.push_back(current_word); + current_word = ""; + }else if(limiters.size() > 1) current_word += current; + }else { + if(limiters.empty()) throw std::runtime_error("Found non-delimited text"); + current_word += current; + } + } + if(not limiters.empty()) throw std::runtime_error("Unclosed delimiter [] or {}"); + return words; + } } namespace wolf{ @@ -102,14 +153,16 @@ struct converter{ }; template<typename A> struct converter<utils::list<A>>{ - static utils::list<A> convert(std::string val){ + static utils::list<A> convert(std::string val){ std::regex rgxP("\\[([^,]+)?(,[^,]+)*\\]"); utils::list<A> result = utils::list<A>(); if(std::regex_match(val, rgxP)) { - std::string aux = val.substr(1,val.size()-2); - auto l = utils::getMatches(aux, std::regex("([^,]+)")); + // std::string aux = val.substr(1,val.size()-2); + // auto l = utils::getMatches(aux, std::regex("([^,]+)")); + auto l = utils::parseList(val); for(auto it : l){ - result.push_back(converter<A>::convert(it)); + // WOLF_DEBUG("Asking to convert in list ", it); + result.push_back(converter<A>::convert(it)); } } else throw std::runtime_error("Invalid string format representing a list-like structure. Correct format is [(value)?(,value)*]. String provided: " + val); return result; @@ -118,11 +171,11 @@ struct converter<utils::list<A>>{ std::string aux = ""; bool first = true; for(auto it : val){ - if(not first) aux += "," + it; - else{ - first = false; - aux = it; - } + if(not first) aux += "," + converter<A>::convert(it); + else{ + first = false; + aux = converter<A>::convert(it); + } } return "[" + aux + "]"; } @@ -264,8 +317,11 @@ struct converter<Eigen::Matrix<_Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCo template<typename A> struct converter<std::map<std::string,A>>{ static std::map<std::string,A> convert(std::string val){ - auto str_map = utils::splitMapStringRepresentation(val); - auto v = utils::pairSplitter(str_map); + std::regex rgxM("\\[((?:(?:\\{[^\\{:]+:[^:\\}]+\\}),?)*)\\]"); + if(not std::regex_match(val, rgxM)) + throw std::runtime_error("Invalid string representation of a Map. Correct format is [({id:value})?(,{id:value})*]. String provided: " + val); + + auto v = utils::parseList(val); auto map = std::map<std::string, A>(); for(auto it : v){ auto p = converter<std::pair<std::string, A>>::convert(it); diff --git a/include/core/yaml/parser_yaml.hpp b/include/core/yaml/parser_yaml.hpp index 3af5c1630da3e1bbeba9ec0ec4c6cc8ab5c63cfb..1ec675756ec1b3d59946a21f1be5c01eef16979a 100644 --- a/include/core/yaml/parser_yaml.hpp +++ b/include/core/yaml/parser_yaml.hpp @@ -14,52 +14,67 @@ #include <numeric> namespace { - /** @Brief Generates a std::string [v1,v2,v3,...] representing the YAML sequence node - * @param n a vector of YAML::Node where each node should be of type YAML::Node::Scalar - * @return <b>{std::string}</b> [v1,v2,v3,...] - */ - std::string fromSequenceToString(std::vector<YAML::Node> n){ - std::string aux = "["; - bool first = true; - for(auto it : n){ - assert(it.Type() == YAML::NodeType::Scalar && "fromSequenceToString requires that the sequence be a sequence of Scalars"); - if(first) { - aux = aux + it.Scalar(); - first = false; - }else{ - aux = aux + "," + it.Scalar(); - } - } - aux = aux + "]"; - return aux; - } - /** @Brief Generates a std::string representing a YAML sequence. The sequence is assumed to be scalar or at most be a sequence of sequences of scalars. - * @param n a vector of YAML::Node that represents a YAML::Sequence - * @return <b>{std::string}</b> representing the YAML sequence - */ - std::string parseScalarSequence(std::vector<YAML::Node> n){ - std::string aux = "["; - bool first = true; - std::string separator = ""; - for(auto it : n){ - if(it.Type() == YAML::NodeType::Scalar) aux = aux + separator + it.Scalar(); - else { - auto seq = std::vector<YAML::Node>(); - for(auto itt : it){ - //I'm going to assume that the node is a sequence of scalars - assert(itt.Type() == YAML::NodeType::Scalar && "The sequence should be a sequence of Scalars"); - seq.push_back(itt); - } - aux = aux + separator + fromSequenceToString(seq); - } - if(first){ - separator = ","; - first = false; - } + //====== START OF FORWARD DECLARATION ======== + std::string parseAtomicNode(YAML::Node); + std::string fetchMapEntry(YAML::Node); + std::string mapToString(std::map<std::string,std::string>); + //====== END OF FORWARD DECLARATION ======== + +/** @Brief Interprets a map as being atomic and thus parses it as a single entity. We assume that the map has as values only scalars and sequences. + * @param n the node representing a map + * @return std::map<std::string, std::string> populated with the key,value pairs in n + */ +std::map<std::string, std::string> fetchAsMap(YAML::Node n){ + assert(n.Type() == YAML::NodeType::Map && "trying to fetch as Map a non-Map node"); + auto m = std::map<std::string, std::string>(); + for(const auto& kv : n){ + std::string key = kv.first.as<std::string>(); + switch (kv.second.Type()) { + case YAML::NodeType::Scalar : { + std::string value = kv.second.Scalar(); + m.insert(std::pair<std::string,std::string>(key, value)); + break; + } + case YAML::NodeType::Sequence : { + std::string aux = parseAtomicNode(kv.second); + m.insert(std::pair<std::string,std::string>(key, aux)); + break; + } + case YAML::NodeType::Map : { + std::string value = fetchMapEntry(kv.second); + std::regex r("^\\$.*"); + if (std::regex_match(key, r)) key = key.substr(1,key.size()-1); + m.insert(std::pair<std::string,std::string>(key, value)); + break; } - aux = aux + "]"; - return aux; + default: + assert(1 == 0 && "Unsupported node Type at fetchAsMap"); + break; + } + } + return m; +} + std::string fetchMapEntry(YAML::Node n){ + switch (n.Type()) { + case YAML::NodeType::Scalar: { + return n.Scalar(); + break; + } + case YAML::NodeType::Sequence: { + return parseAtomicNode(n); + break; + } + case YAML::NodeType::Map: { + return mapToString(fetchAsMap(n)); + break; + } + default: { + assert(1 == 0 && "Unsupported node Type at fetchMapEntry"); + return ""; + break; + } } + } /** @Brief Transforms a std::map<std::string,std::string> to its std::string representation [{k1:v1},{k2:v2},{k3:v3},...] * @param _map just a std::map<std::string,std::string> * @return <b>{std::string}</b> [{k1:v1},{k2:v2},{k3:v3},...] @@ -77,6 +92,76 @@ namespace { else accumulated = ""; return "[" + accumulated + "]"; } + /** @Brief Generates a std::string representing a YAML sequence. The sequence is assumed to be scalar or at most be a sequence of sequences of scalars. + * @param n a vector of YAML::Node that represents a YAML::Sequence + * @return <b>{std::string}</b> representing the YAML sequence + */ + std::string parseAtomicNode(YAML::Node n){ + std::string aux = ""; + bool first = true; + std::string separator = ""; + switch(n.Type()){ + case YAML::NodeType::Scalar: + return n.Scalar(); + break; + case YAML::NodeType::Sequence: + for(auto it : n){ + aux += separator + parseAtomicNode(it); + if(first){ + separator = ","; + first = false; + } + } + return "[" + aux + "]"; + break; + case YAML::NodeType::Map: + return mapToString(fetchAsMap(n)); + break; + default: + return ""; + break; + } + } + + /** @Brief checks if a node of the YAML tree is atomic. Only works if the nodes are of type + * Scalar, Sequence or Map. + * @param key is the key associated to the node n if n.Type() == YAML::NodeType::Map + * @param n node to be test for atomicity + */ + bool isAtomic(std::string key, YAML::Node n){ + assert(n.Type() != YAML::NodeType::Undefined && n.Type() != YAML::NodeType::Null && "Cannot determine atomicity of Undefined/Null node"); + std::regex r("^\\$.*"); + bool is_atomic = true; + switch(n.Type()){ + case YAML::NodeType::Scalar: + return true; + break; + case YAML::NodeType::Sequence: + for(auto it : n) { + switch(it.Type()){ + case YAML::NodeType::Map: + for(const auto& kv : it){ + is_atomic = is_atomic and isAtomic(kv.first.as<std::string>(), it); + } + break; + default: + is_atomic = is_atomic and isAtomic("", it); + break; + } + } + return is_atomic; + break; + case YAML::NodeType::Map: + is_atomic = std::regex_match(key, r); + return is_atomic; + break; + default: + throw std::runtime_error("Cannot determine atomicity of node type " + std::to_string(n.Type())); + return false; + break; + } + return false; + } } class ParserYAML { struct ParamsInitSensor{ @@ -158,7 +243,6 @@ public: std::vector<std::array<std::string, 2>> getProblem(); std::map<std::string,std::string> getParams(); void parse(); - std::map<std::string, std::string> fetchAsMap(YAML::Node); }; std::string ParserYAML::generatePath(std::string path){ std::regex r("^/.*"); @@ -193,7 +277,7 @@ void ParserYAML::walkTree(std::string file, std::vector<std::string>& tags, std: n = YAML::LoadFile(generatePath(file)); walkTreeR(n, tags, hdr); } -/** @Brief Recursively walks the YAML tree while filling a map with the values oarsed from the file +/** @Brief Recursively walks the YAML tree while filling a map with the values parsed from the file * @param YAML node to be parsed * @param tags represents the path from the root of the YAML tree to the current node * @param hdr is the name of the current YAML node @@ -211,67 +295,59 @@ void ParserYAML::walkTreeR(YAML::Node n, std::vector<std::string>& tags, std::st break; } case YAML::NodeType::Sequence : { - std::vector<YAML::Node> scalar_and_seqs; - std::vector<YAML::Node> maps; - for(const auto& kv : n){ - if(kv.Type() == YAML::NodeType::Scalar or kv.Type() == YAML::NodeType::Sequence) scalar_and_seqs.push_back(kv); - else if(kv.Type() == YAML::NodeType::Map) maps.push_back(kv); - } - assert(scalar_and_seqs.size() * maps.size() == 0); - if(scalar_and_seqs.size() > 0 and maps.size() == 0){ - _params.insert(std::pair<std::string,std::string>(hdr, parseScalarSequence(scalar_and_seqs))); - }else if(scalar_and_seqs.size() == 0 and maps.size() > 0){ - for(const auto& kv : maps){ + if(isAtomic("", n)){ + _params.insert(std::pair<std::string,std::string>(hdr, parseAtomicNode(n))); + }else{ + for(const auto& kv : n){ walkTreeR(kv, tags, hdr); } } break; } case YAML::NodeType::Map : { - for(const auto& kv : n){ - //If the key's value starts with a $ (i.e. $key) then its value is parsed as an atomic map, - //otherwise the parser recursively parses the map. - std::regex r("^\\$.*"); - if(not std::regex_match(kv.first.as<std::string>(), r)){ - /* - If key=="follow" then the parser will assume that the value is a path and will parse - the (expected) yaml file at the specified path. Note that this does not increase the header depth. - The following example shows how the header remains unafected: - @my_main_config | @some_path - - cov_det: 1 | - my_value : 23 - - follow: "@some_path" | - - var: 1.2 | - Resulting map: - cov_det -> 1 - my_value-> 23 - var: 1.2 - Instead of: - cov_det -> 1 - follow/my_value-> 23 - var: 1.2 - Which would result from the following yaml files - @my_main_config | @some_path - - cov_det: 1 | - my_value : 23 - - $follow: "@some_path" | - - var: 1.2 | - */ - std::regex rr("follow"); - if(not std::regex_match(kv.first.as<std::string>(), rr)) { - tags.push_back(kv.first.as<std::string>()); - if(tags.size() == 2) this->updateActiveName(kv.first.as<std::string>()); - walkTreeR(kv.second, tags, hdr +"/"+ kv.first.as<std::string>()); - tags.pop_back(); - if(tags.size() == 1) this->updateActiveName(""); - }else{ - walkTree(kv.second.as<std::string>(), tags, hdr); - } - }else{ - std::string key = kv.first.as<std::string>(); - key = key.substr(1,key.size() - 1); - auto fm = fetchAsMap(kv.second); - _params.insert(std::pair<std::string,std::string>(hdr + "/" + key, mapToString(fm))); - } - } + for(const auto& kv : n){ + if(isAtomic(kv.first.as<std::string>(), n)){ + std::string key = kv.first.as<std::string>(); + //WOLF_DEBUG("KEY IN MAP ATOMIC ", hdr + "/" + key); + key = key.substr(1,key.size() - 1); + _params.insert(std::pair<std::string,std::string>(hdr + "/" + key, parseAtomicNode(kv.second))); + }else{ + /* + If key=="follow" then the parser will assume that the value is a path and will parse + the (expected) yaml file at the specified path. Note that this does not increase the header depth. + The following example shows how the header remains unafected: + @my_main_config | @some_path + - cov_det: 1 | - my_value : 23 + - follow: "@some_path" | + - var: 1.2 | + Resulting map: + cov_det -> 1 + my_value-> 23 + var: 1.2 + Instead of: + cov_det -> 1 + follow/my_value-> 23 + var: 1.2 + Which would result from the following yaml files + @my_main_config | @some_path + - cov_det: 1 | - my_value : 23 + - $follow: "@some_path" | + - var: 1.2 | + */ + std::string key = kv.first.as<std::string>(); + //WOLF_DEBUG("KEY IN MAP NON ATOMIC ", key); + std::regex rr("follow"); + if(not std::regex_match(kv.first.as<std::string>(), rr)) { + tags.push_back(kv.first.as<std::string>()); + if(tags.size() == 2) this->updateActiveName(kv.first.as<std::string>()); + walkTreeR(kv.second, tags, hdr +"/"+ kv.first.as<std::string>()); + tags.pop_back(); + if(tags.size() == 1) this->updateActiveName(""); + }else{ + walkTree(kv.second.as<std::string>(), tags, hdr); + } + } + } break; } default: @@ -350,65 +426,4 @@ void ParserYAML::parse(){ this->walkTreeR(it.n , tags , it._name); } } - -std::string fetchMapEntry(YAML::Node n){ - switch (n.Type()) { - case YAML::NodeType::Scalar : { - return n.Scalar(); - break; - } - case YAML::NodeType::Sequence : { - std::vector<YAML::Node> nodes; - for(auto it : n){ - nodes.push_back(it); - } - return parseScalarSequence(nodes); - break; - } - default: { - assert(1 == 0 && "Unsupported node Type at fetchMapEntry"); - return ""; - break; - } - } -} -/** @Brief Interprets a map as being atomic and thus parses it as a single entity. We assume that the map has as values only scalars and sequences. - * @param n the node representing a map - * @return std::map<std::string, std::string> populated with the key,value pairs in n - */ -std::map<std::string, std::string> ParserYAML::fetchAsMap(YAML::Node n){ - assert(n.Type() == YAML::NodeType::Map && "trying to fetch as Map a non-Map node"); - auto m = std::map<std::string, std::string>(); - for(const auto& kv : n){ - std::string key = kv.first.as<std::string>(); - switch (kv.second.Type()) { - case YAML::NodeType::Scalar : { - std::string value = kv.second.Scalar(); - m.insert(std::pair<std::string,std::string>(key, value)); - break; - } - case YAML::NodeType::Sequence : { - std::vector<YAML::Node> scalars; - std::vector<YAML::Node> non_scalars; - for(const auto& kvv : kv.second){ - if(kvv.Type() == YAML::NodeType::Scalar or kvv.Type() == YAML::NodeType::Sequence) scalars.push_back(kvv); - else non_scalars.push_back(kvv); - } - if(non_scalars.size() > 0) WOLF_WARN("Ignoring non-scalar members of sequence in atomic map..."); - std::string aux = parseScalarSequence(scalars); - m.insert(std::pair<std::string,std::string>(key, aux)); - break; - } - case YAML::NodeType::Map : { - std::string value = fetchMapEntry(kv.second); - m.insert(std::pair<std::string,std::string>(key, value)); - break; - } - default: - assert(1 == 0 && "Unsupported node Type at fetchAsMap"); - break; - } - } - return m; -} #endif diff --git a/test/gtest_converter.cpp b/test/gtest_converter.cpp index d36d8665d7c3f9226e945a445ff922c20b8c1e00..d068e024a3f9145f347a8645d7ba52dedd036c38 100644 --- a/test/gtest_converter.cpp +++ b/test/gtest_converter.cpp @@ -57,6 +57,19 @@ TEST(Converter, ParseToMap) map<string, vector<int>> m = {{"x",vector<int>{1,2}}, {"y",vector<int>{}}, {"z",vector<int>{3}}}; ASSERT_EQ(converter<string>::convert(m), "[{x:[1,2]},{y:[]},{z:[3]}]"); } +TEST(Converter, extraTests) +{ + string str = "[{x:1},{y:[[{x:[1,2]},{y:[]},{z:[3]}]]},{z:[1,2,3,4,5]}]"; + auto v = converter<vector<string>>::convert(str); + ASSERT_EQ(v[0], "{x:1}"); + ASSERT_EQ(v[1], "{y:[[{x:[1,2]},{y:[]},{z:[3]}]]}"); + ASSERT_EQ(v[2], "{z:[1,2,3,4,5]}"); + + string str2 = "[]"; + auto v2 = converter<vector<string>>::convert(str2); + // EXPECT_EQ(v2.size(), 1); + EXPECT_TRUE(v2.empty()); +} TEST(Converter, noGeneralConvert) { class DUMMY{}; diff --git a/test/gtest_parser_yaml.cpp b/test/gtest_parser_yaml.cpp index 5102157d05185f93874e9ec0e89e9141feaf230e..985657de83d5bc1381a488d2ed5b6986c12f8f64 100644 --- a/test/gtest_parser_yaml.cpp +++ b/test/gtest_parser_yaml.cpp @@ -28,7 +28,10 @@ TEST(ParserYAML, ParseMap) { auto parser = parse("test/yaml/params2.yaml", wolf_root); auto params = parser.getParams(); + // for(auto it : params) + // cout << it.first << " %% " << it.second << endl; EXPECT_EQ(params["processor1/mymap"], "[{k1:v1},{k2:v2},{k3:[v3,v4,v5]}]"); + // EXPECT_EQ(params["processor1/$mymap/k1"], "v1"); } TEST(ParserYAML, JumpFile) {