Here is an example of using Spirit X3 to create a grammar to actually analyze this. I would like to parse a map (key-> value) of pairs, which makes a lot more sense than just blindly, assuming the names are always the same:
using Config = std::map<std::string, std::string>; using Entry = std::pair<std::string, std::string>;
Now we set some grammar rules using X3:
namespace parser { using namespace boost::spirit::x3; auto value = quoted("'") | quoted('"'); auto key = lexeme[+alpha]; auto pair = key >> '(' >> value >> ')'; auto config = skip(space) [ *as<Entry>(pair) ]; }
The helpers as<> and quoted are simple lambdas:
template <typename T> auto as = [](auto p) { return rule<struct _, T> {} = p; }; auto quoted = [](auto q) { return lexeme[q >> *('\\' >> char_ | char_ - q) >> q]; };
Now we can directly parse the string to the map:
Config parse_config(std::string const& cfg) { Config parsed; auto f = cfg.begin(), l = cfg.end(); if (!parse(f, l, parser::config, parsed)) throw std::invalid_argument("Parse failed at " + std::string(f,l)); return parsed; }
And a demo program
int main() { Config cfg = parse_config("server ('m1.labs.teradata.com') username ('use\\')r_*5') password('u\" er 5') dbname ('default')"); for (auto& setting : cfg) std::cout << "Key " << setting.first << " has value " << setting.second << "\n"; }
Print
Key dbname has value default Key password has value u" er 5 Key server has value m1.labs.teradata.com Key username has value use')r_*5
Live demo
Live on coliru
#include <iostream> #include <boost/spirit/home/x3.hpp> #include <boost/fusion/adapted/std_pair.hpp> #include <map> using Config = std::map<std::string, std::string>; using Entry = std::pair<std::string, std::string>; namespace parser { using namespace boost::spirit::x3; template <typename T> auto as = [](auto p) { return rule<struct _, T> {} = p; }; auto quoted = [](auto q) { return lexeme[q >> *(('\\' >> char_) | (char_ - q)) >> q]; }; auto value = quoted("'") | quoted('"'); auto key = lexeme[+alpha]; auto pair = key >> '(' >> value >> ')'; auto config = skip(space) [ *as<Entry>(pair) ]; } Config parse_config(std::string const& cfg) { Config parsed; auto f = cfg.begin(), l = cfg.end(); if (!parse(f, l, parser::config, parsed)) throw std::invalid_argument("Parse failed at " + std::string(f,l)); return parsed; } int main() { Config cfg = parse_config("server ('m1.labs.teradata.com') username ('use\\')r_*5') password('u\" er 5') dbname ('default')"); for (auto& setting : cfg) std::cout << "Key " << setting.first << " has value " << setting.second << "\n"; }
Bonus
If you want to learn how to extract the original input: just try
auto source = skip(space) [ *raw [ pair ] ];
as in this:
using RawSettings = std::vector<std::string>; RawSettings parse_raw_config(std::string const& cfg) { RawSettings parsed; auto f = cfg.begin(), l = cfg.end(); if (!parse(f, l, parser::source, parsed)) throw std::invalid_argument("Parse failed at " + std::string(f,l)); return parsed; } int main() { for (auto& setting : parse_raw_config(text)) std::cout << "Raw: " << setting << "\n"; }
What prints: Live On Coliru
Raw: server ('m1.labs.teradata.com') Raw: username ('use\')r_*5') Raw: password('u" er 5') Raw: dbname ('default')