boost regex to match IF statements - c++

I need to write a boost regex to match the following string and seperate it into three tokens depending on the parameters to the IF block
=IF(ISNUMBER(SEARCH("Windows",GETWORKSPACE(1))),ON.TIME(NOW()+"00:00:02","abcdef"),CLOSE(TRUE))
Ideally these should come to
token1 = "ISNUMBER(SEARCH("Windows",GETWORKSPACE(1)))"
token2 = "ON.TIME(NOW()+"00:00:02","abcdef")"
token3 = "CLOSE(TRUE)"
I had originally written a simple regex as "(?<=\=IF\()(.),(.),(.*)(?=\))" which gives out incorrect tokens because the greedy qualifier takes too much of the first token. I am currently getting
token1 = "ISNUMBER(SEARCH("Windows",GETWORKSPACE(1))),ON.TIME(NOW()+"00:00:02""
token2 = ""abcdef")"
token3 = "CLOSE(TRUE)"
Also tried "(?<=\\=IF\\()([A-Za-z(),:\"]*?),([A-Za-z(),.:\"]*?),([A-Z(),:\"]*?)(?=\\))" with no luck. Can someone please suggest a regex ?

You need a simple parser.
Here's one with my favorite Boost swiss-army knife for quick parsers.
I've created a very flexible "token" grammar that honours (nested) parentheses and double-quoted string literals (potentially with embedded escaped quotes and parentheses):
token = raw [ *(
'(' >> -token_list >> ')'
| '[' >> -token_list >> ']'
| '{' >> -token_list >> '}'
| string_literal
| lexeme[ + ~char_(")]}([{\"',") ]
) ];
Where token_list and string_literal are defined as
string_literal = lexeme [
'"' >> *('\\' >> char_ | ~char_('"')) >> '"'
];
token_list = token % ',';
Now the parser expression for an =IF(condition, true_part, false_part) is simply:
if_expr
= '=' >> no_case["if"]
>> '(' >> token >> ',' >> token >> ',' >> token >> ')';
For fun I made the IF keyword case-insensitive
DEMO
Live On Coliru
//#define BOOST_SPIRIT_X3_DEBUG
#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/adapted/std_tuple.hpp>
#include <iostream>
#include <iomanip>
namespace x3 = boost::spirit::x3;
namespace parser {
using namespace x3;
static rule<struct token_, std::string> const token = "token";
static auto const string_literal = lexeme [
'"' >> *('\\' >> char_ | ~char_('"')) >> '"'
];
static auto const token_list = token % ',';
static auto const token_def = raw [ *(
'(' >> -token_list >> ')'
| '[' >> -token_list >> ']'
| '{' >> -token_list >> '}'
| string_literal
| +~char_(")]}([{\"',") // glue together everything else
) ];
BOOST_SPIRIT_DEFINE(token)
static auto const if_expr
= '=' >> no_case["if"]
>> '(' >> token >> ',' >> token >> ',' >> token >> ')';
}
int main() {
for (std::string const& input : {
R"(=IF(ISNUMBER,ON.TIME,CLOSE))",
R"(=IF(ISNUMBER(SEARCH("Windows")),ON.TIME(NOW()+"00:00:02","abcdef"),CLOSE(TRUE)))",
R"(=IF(ISNUMBER(SEARCH("Windows",GETWORKSPACE(1))),ON.TIME(NOW()+"00:00:02","abcdef"),CLOSE(TRUE)))",
" = if( isnumber, on .time, close ) ",
R"( = if( "foo, bar", if( isnumber, on .time, close ), IF("[ISN(UM}B\"ER")) )",
})
{
auto f = input.begin(), l = input.end();
std::cout << "=== " << std::quoted(input) << ":\n";
std::string condition, true_part, false_part;
auto attr = std::tie(condition, true_part, false_part);
if (phrase_parse(f, l, parser::if_expr, x3::blank, attr)) {
std::cout << "Parsed: \n"
<< " - condition: " << std::quoted(condition) << "\n"
<< " - true_part: " << std::quoted(true_part) << "\n"
<< " - false_part: " << std::quoted(false_part) << "\n";
} else {
std::cout << "Parse failed\n";
}
if (f!=l) {
std::cout << "Remaining unparsed: " << std::quoted(std::string(f,l)) << "\n";
}
}
}
Prints
=== "=IF(ISNUMBER,ON.TIME,CLOSE)":
Parsed:
- condition: "ISNUMBER"
- true_part: "ON.TIME"
- false_part: "CLOSE"
=== "=IF(ISNUMBER(SEARCH(\"Windows\")),ON.TIME(NOW()+\"00:00:02\",\"abcdef\"),CLOSE(TRUE))":
Parsed:
- condition: "ISNUMBER(SEARCH(\"Windows\"))"
- true_part: "ON.TIME(NOW()+\"00:00:02\",\"abcdef\")"
- false_part: "CLOSE(TRUE)"
=== "=IF(ISNUMBER(SEARCH(\"Windows\",GETWORKSPACE(1))),ON.TIME(NOW()+\"00:00:02\",\"abcdef\"),CLOSE(TRUE))":
Parsed:
- condition: "ISNUMBER(SEARCH(\"Windows\",GETWORKSPACE(1)))"
- true_part: "ON.TIME(NOW()+\"00:00:02\",\"abcdef\")"
- false_part: "CLOSE(TRUE)"
=== " = if( isnumber, on .time, close ) ":
Parsed:
- condition: "isnumber"
- true_part: "on .time"
- false_part: "close "
=== " = if( \"foo, bar\", if( isnumber, on .time, close ), IF(\"[ISN(UM}B\\\"ER\")) ":
Parsed:
- condition: "\"foo, bar\""
- true_part: "if( isnumber, on .time, close )"
- false_part: "IF(\"[ISN(UM}B\\\"ER\")"

Related

Parsing selections from a fixed list in Boost.Spirit

Starting from the Employee - Parsing into structs example:
template <typename Iterator>
struct employee_parser : qi::grammar<Iterator, employee(), ascii::space_type>
{
employee_parser() : employee_parser::base_type(start)
{
using qi::int_;
using qi::lit;
using qi::double_;
using qi::lexeme;
using ascii::char_;
quoted_string %= lexeme['"' >> +(char_ - '"') >> '"'];
start %=
lit("employee")
>> '{'
>> int_ >> ','
>> quoted_string >> ','
>> quoted_string >> ','
>> double_
>> '}'
;
}
qi::rule<Iterator, std::string(), ascii::space_type> quoted_string;
qi::rule<Iterator, employee(), ascii::space_type> start;
};
suppose I wanted to replace quoted_string with a rule that matches any string stored in a given container.
For example, if I have a container such as:
std::array<std::string, 4> match_list =
{ "string0", "string1", "string2", "string3" };
and I want the parser to only match the input against one of the values in the array (the container doesn't have to be an array).
I'm sure this is simple, but the Spirit help pages don't seem to address this.
It is simple: https://www.boost.org/doc/libs/1_67_0/libs/spirit/doc/html/spirit/qi/reference/string/symbols.html
Live On Coliru
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>
namespace qi = boost::spirit::qi;
struct employee {
int id;
std::string sym;
std::string name;
double value;
};
BOOST_FUSION_ADAPT_STRUCT(employee, id, sym, name, value)
template <typename Iterator, typename Skipper = qi::space_type>
struct employee_parser : qi::grammar<Iterator, employee(), Skipper>
{
employee_parser() : employee_parser::base_type(start)
{
using namespace qi;
quoted_string = lexeme['"' >> +(char_ - '"') >> '"'];
symbol_.add
("string0")
("string1")
("string2")
("string3")
;
start =
lit("employee")
>> '{'
>> int_ >> ','
>> symbol_ >> ','
>> quoted_string >> ','
>> double_
>> '}'
;
}
qi::rule<Iterator, std::string(), Skipper> quoted_string;
qi::rule<Iterator, employee(), Skipper> start;
qi::symbols<char, std::string> symbol_;
};
int main() {
std::string const input = "employee { 42, string3, \"more names or stuff\", 6.7 }";
using It = std::string::const_iterator;
It f = input.begin(), l = input.end();
employee_parser<It> p;
employee e;
if (phrase_parse(f, l, p, qi::space, e)) {
using boost::fusion::operator<<;
std::cout << boost::fusion::tuple_delimiter(';');
std::cout << "Parsed: " << e << "\n";
} else {
std::cout << "Parse failed\n";
}
if (f!=l)
std::cout << "Remaining input: '" << std::string(f,l) << "'\n";
}
Prints
Parsed: (42;;more names or stuff;6.7)
To actually include values:
symbol_.add
("string0", "STRING0")
("string1", "STRING1")
("string2", "STRING2")
("string3", "STRING3")
;
Prints Live On Coliru
Parsed: (42;STRING3;more names or stuff;6.7)
Or you can use another type altogether:
symbol_.add
("string0", 0)
("string1", 1)
("string2", 2)
("string3", 3)
;
With
symbol_.add
("string0", 0)
("string1", 1)
("string2", 2)
("string3", 3)
;
Which prints Live On Coliru
Parsed: (42;3;more names or stuff;6.7)
Finally, you might use raw[] instead to "transduce" the input sequence, for example combined with qi::no_space[]: Live On Coliru
>> raw[no_case[symbol_]] >> ','
Prints
Parsed: (42;sTrInG3;more names or stuff;6.7)
After Seth encouraged me in the comments to post my answer as well, here it is. As expected, it is very similar with the difference that I dynamically construct the symbols from a std::array passed to the grammar.
#include <iostream>
#include <string>
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>
struct employee {
int age;
std::string surname;
std::string forename;
double salary;
};
BOOST_FUSION_ADAPT_STRUCT(employee, age, surname, forename, salary)
namespace ascii = boost::spirit::ascii;
namespace qi = boost::spirit::qi;
template <typename Iterator, std::size_t N>
struct employee_parser : qi::grammar<Iterator, employee(), ascii::space_type> {
employee_parser(std::array<std::string, N> const &match_list)
: employee_parser::base_type(start) {
using namespace qi;
for (auto match : match_list) {
employees.add(match, match);
}
quoted_string %= lexeme['"' >> +(char_ - '"') >> '"'];
start %=
lit("employee")
>> '{'
>> int_ >> ','
>> quoted_string >> ','
>> employees >> ','
>> double_
>> '}'
;
}
qi::rule<Iterator, std::string(), ascii::space_type> quoted_string;
qi::rule<Iterator, employee(), ascii::space_type> start;
qi::symbols<typename std::iterator_traits<Iterator>::value_type,
std::string> employees;
};
template <typename Iterator, std::size_t N>
employee parse(Iterator first, Iterator last,
std::array<std::string, N> const &match_list) {
employee_parser<Iterator, N> const grammar(match_list);
employee e;
bool r = qi::phrase_parse(first, last, grammar, ascii::space, e);
if (!r || first != last) {
std::cerr << "Parsing failed at " + std::string(first, last) + "\n";
}
return e;
}
int main() {
employee e;
std::array<std::string, 4> match_list = {"Homer", "Marge", "Lisa", "Bart"};
std::string homer = "employee { 38, \"Simpson\", Homer, 3.0 }";
e = parse(homer.begin(), homer.end(), match_list);
std::cout << "employee { " << e.age << ", " << e.surname << ", "
<< e.forename << ", " << e.salary << " }\n";
// Fails parsing because Hans Mole is not in the list
std::string mole = "employee { 100, \"Mole\", Hans, 0.0 }";
e = parse(mole.begin(), mole.end(), match_list);
std::cout << "employee { " << e.age << ", " << e.surname << ", "
<< e.forename << ", " << e.salary << " }\n";
}
$ clang++ -Wall -Wextra -Wpedantic -std=c++11 test.cpp
$ ./a.out
employee { 38, Simpson, Homer, 3 }
Parsing failed at employee { 100, "Mole", Hans, 0.0 }
employee { 100, Mole, , 0 }
Here is also a reference for Homer's salary being 3.0: https://www.youtube.com/watch?v=HIEWgwRrY9s

Semantic actions calling lambda

I'm trying to parse a time string using boost spirit and not sure why this doesn't work.
auto fill_ts_nanos = [&t] (int h, int m, int s, int ms) -> int
{ t.tv_nsec = ( ( h * 3600 + m * 60 + s ) * 1000 + ms ) * 1000000; return t.tv_sec; };
auto fill_suffix = [&suffix] (string &s) { suffix=s; };
auto parse_ok = qi::parse(input.begin(), input.end(),
( qi::int_ >> qi::char_(":") >> qi::int_ >> qi::char_(":") >>
qi::int_ >> qi::char_(".") >> qi::int_ )
[boost::bind(fill_ts_nanos, qi::_1, qi::_3, qi::_5, qi::_7
>> qi::char_(",") >> qi::as_string[*qi::char_][fill_suffix] ;
A sample input is "04:00:00.512,2251812698588658"
After guessing a lot of details (e.g. what the type of t is supposed to be), here's the fixed code with some debug output:
Live On Coliru
Note: I fixed the signed-ness of the numbers as well as I changed the types to prevent overflow.
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <ctime>
#include <chrono>
#include <iomanip>
namespace qi = boost::spirit::qi;
namespace px = boost::phoenix;
using namespace std::chrono_literals;
using namespace qi::labels;
int main() {
timespec t;
std::string suffix;
auto fill_ts_nanos = [&t](int h, unsigned m, unsigned s, unsigned ms) -> long {
t = {};
t.tv_nsec = ((h * 3600 + m * 60 + s) * 1000 + ms) * 1000000l;
return t.tv_sec;
};
auto fill_suffix = [&suffix](std::string &s) { suffix = s; };
std::string const input = "04:00:00.512,2251812698588658";
auto parse_ok = qi::parse(input.begin(), input.end(),
(qi::int_ >> ':' >> qi::uint_ >> ':' >> qi::uint_ >> '.' >> qi::uint_)
[px::bind(fill_ts_nanos, _1, _2, _3, _4) ]
>> ',' >> qi::as_string[*qi::char_]
[fill_suffix] );
std::printf("%lld.%.9ld\n", (long long)t.tv_sec, t.tv_nsec);
auto ns = t.tv_nsec * 1ns;
std::cout << std::fixed << std::setprecision(6);
std::cout << "hours: " << (ns / 1.0h) << "\n";
std::cout << "minutes: " << (ns / 1.0min) << "\n";
std::cout << "seconds: " << (ns / 1.0s) << "\n";
std::cout << "suffix: " << suffix << "\n";
return parse_ok? 0:255;
}
Prints
0.14400512000000
hours: 4.000142
minutes: 240.008533
seconds: 14400.512000
suffix: 2251812698588658
Suggestions
I'd try to simplify this by a lot, e.g., by creating a rule:
qi::rule<It, long()> timespec_ =
(qi::int_ >> ':' >> qi::uint_ >> ':' >> qi::uint_ >> '.' >> qi::uint_)
[ _val = ((_1 * 3600 + _2 * 60 + _3) * 1000 + _4) * 1000000l ];
Which means you can then parse with nothing else but:
timespec t {};
std::string suffix;
It f = input.begin(), l = input.end();
parse(f, l, timespec_ >> ',' >> *qi::char_, t.tv_nsec, suffix);
This has the same output:
Live On Coliru
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <ctime>
#include <chrono>
#include <iomanip>
namespace qi = boost::spirit::qi;
namespace px = boost::phoenix;
using namespace std::chrono_literals;
using namespace qi::labels;
using It = std::string::const_iterator;
qi::rule<It, long()> timespec_ =
(qi::int_ >> ':' >> qi::uint_ >> ':' >> qi::uint_ >> '.' >> qi::uint_)
[ _val = ((_1 * 3600 + _2 * 60 + _3) * 1000 + _4) * 1000000l ];
int main() {
std::string const input = "04:00:00.512,2251812698588658";
timespec t {};
std::string suffix;
It f = input.begin(), l = input.end();
if (parse(f, l, timespec_ >> ',' >> *qi::char_, t.tv_nsec, suffix)) {
std::printf("%lld.%.9ld\n", (long long)t.tv_sec, t.tv_nsec);
auto ns = t.tv_nsec * 1ns;
std::cout << std::fixed << std::setprecision(6);
std::cout << "hours: " << (ns / 1.0h) << "\n";
std::cout << "minutes: " << (ns / 1.0min) << "\n";
std::cout << "seconds: " << (ns / 1.0s) << "\n";
std::cout << "suffix: " << suffix << "\n";
} else {
std::cout << "Parse failed\n";
}
if (f!=l) {
std::cout << "Remaining unparsed: '" << std::string(f,l) << "'\n";
}
}
In my desperation I've also figured out how the container version works. See below,
auto test_fn = [&t](auto c) {
t.tv_nsec = ( ( at_c<0>(c) * 3600 +
at_c<2>(c) * 60 +
at_c<4>(c) ) * 1000 +
at_c<6>(c) ) * 1000000;
auto parse_ok = qi::parse(input.begin(), input.end(),
( qi::int_ >> qi::char_(":") >> qi::int_ >> qi::char_(":") >>
qi::int_ >> qi::char_(".") >> qi::int_ )[ test_fn ]
Admittedly, it's quite ugly.

Parsing list of 'key = value' with unknown keys with Boost::Spirit

I've a string like the following one:
[GENERAL]
FMax Antenna = 3000
FMin Antenna = 2000
Invalid key = Invalid value
EMin Antenna = -50
EMax Antenna = 80
I want to parse it in order to save the value of FMin Antenna, FMax Antenna, EMin Antenna, EMax Antenna in a structure. I've created a Spirit parser, but it works partially.
Since the file can have many key = value rows, I need to parse only what I need (the key values that I must read) ignoring other pairs.
Both key and value can be alphanumeric strings with spaces and tabs.
I've defined inside the parser the keys that I want to read but when I encounter an unknown key, I cannot read keys that follows it (in the example case, I can't read EMin Antenna and EMax Antenna because are defined after an unknown key).
I've tried the code below: If I parse file1, that contains only keys that I want to read, it works, but if I add unknown key = value pairs in the middle of the file, like in file2, it stops to read all subsequent lines.
How can I fix it and continue to parse the file after unknown key-value pairs?
#include <boost/optional/optional_io.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/date_time/posix_time/posix_time_io.hpp>
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
namespace qi = boost::spirit::qi;
const std::string file1 = R"xx(
[GENERAL]
FMax Antenna = 3000
FMin Antenna = 2000
EMin Antenna = -50
EMax Antenna = 80
)xx";
const std::string file2 = R"xx(
[GENERAL]
FMax Antenna = 3000
FMin Antenna = 2000
EMin Antenna = -50
pappa pio = po po
EMax Antenna = 80
Ciao = 55
)xx";
struct Data {
double minFrequency = 0.0;
double maxFrequency = 0.0;
double minElevation = 0.0;
double maxElevation = 0.0;
};
BOOST_FUSION_ADAPT_STRUCT(
Data,
(double, minFrequency)
(double, maxFrequency)
(double, minElevation)
(double, maxElevation)
)
template <typename It, typename Skipper = qi::space_type>
struct grammar : qi::grammar<It, Data(), Skipper> {
grammar() : grammar::base_type(start) {
auto minFrequency = bind(&Data::minFrequency, qi::_val);
auto maxFrequency = bind(&Data::maxFrequency, qi::_val);
auto minElevation = bind(&Data::minElevation, qi::_val);
auto maxElevation = bind(&Data::maxElevation, qi::_val);
start = qi::no_case["[GENERAL]"] >> *(
("FMin Antenna" >> qi::lit('=') >> qi::int_)[minFrequency = qi::_1] |
("FMax Antenna" >> qi::lit('=') >> qi::int_)[maxFrequency = qi::_1] |
("EMin Antenna" >> qi::lit('=') >> qi::int_)[minElevation = qi::_1] |
("EMax Antenna" >> qi::lit('=') >> qi::int_)[maxElevation = qi::_1] |
(+(qi::alnum | qi::blank) >> qi::lit('=') >> +(qi::alnum | qi::blank)) // Issue here?
);
}
private:
qi::rule<It, Data(), Skipper> start;
};
int main() {
using It = std::string::const_iterator;
Data parsed1, parsed2;
bool ok = qi::phrase_parse(file1.begin(), file1.end(), grammar<It>(), qi::space, parsed1);
std::cout << "--- File 1 ---" << std::endl;
std::cout << "parsed = " << std::boolalpha << ok << std::endl;
std::cout << "min freq = " << parsed1.minFrequency << std::endl;
std::cout << "max freq = " << parsed1.maxFrequency << std::endl;
std::cout << "min elev = " << parsed1.minElevation << std::endl;
std::cout << "max elev = " << parsed1.maxElevation << std::endl;
std::cout << "--- File 2 ---" << std::endl;
ok = qi::phrase_parse(file2.begin(), file2.end(), grammar<It>(), qi::space, parsed2);
std::cout << "parsed = " << std::boolalpha << ok << std::endl;
std::cout << "min freq = " << parsed2.minFrequency << std::endl;
std::cout << "max freq = " << parsed2.maxFrequency << std::endl;
std::cout << "min elev = " << parsed2.minElevation << std::endl;
std::cout << "max elev = " << parsed2.maxElevation << std::endl;
return 0;
}
Ouput:
--- File 1 ---
parsed = true
min freq = 2000
max freq = 3000
min elev = -50
max elev = 80
--- File 2 ---
parsed = true
min freq = 2000
max freq = 3000
min elev = -50
max elev = 0 <-- This should be 80 like in the first parsing
You're confused about skippers.
Newlines are significant in your grammar, which is why you need a skipper that doesn't eat them
In your rules, you match +(alnum|blank) which is never gonna work because the skipper eats everything that matches blank anyways
(See Boost spirit skipper issues for background)
Other notes:
You don't need Fusion adaptation unless you want auto-magic attribute propagation. You're not using it right now.
Solving It
I'd make things very explicit:
known =
("FMin Antenna" >> lit('=') >> int_)[minFrequency = _1] |
("FMax Antenna" >> lit('=') >> int_)[maxFrequency = _1] |
("EMin Antenna" >> lit('=') >> int_)[minElevation = _1] |
("EMax Antenna" >> lit('=') >> int_)[maxElevation = _1]
;
unknown = +alnum >> '=' >> +alnum;
setting = (known(_r1) | unknown) >> +eol;
start =
no_case["[GENERAL]"] >> eol
>> *setting(_val);
Splitting up in rules is a little tricky, because *setting would try to synthesize a container attribute, making it impossible to propagate to the actual Data attribute.
I solved it by passing the attribute by reference in an inherited attribute, which disables automatic attribute propagation.
Alternatively, you could add a semantic action of any kind to inhibit automatic attribute propagation
DEMO
Live On Coliru
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iomanip>
namespace qi = boost::spirit::qi;
struct Data {
double minFrequency = 0.0;
double maxFrequency = 0.0;
double minElevation = 0.0;
double maxElevation = 0.0;
};
template <typename It, typename Skipper = qi::blank_type>
struct grammar : qi::grammar<It, Data(), Skipper> {
grammar() : grammar::base_type(start) {
using namespace qi;
auto minFrequency = bind(&Data::minFrequency, _r1);
auto maxFrequency = bind(&Data::maxFrequency, _r1);
auto minElevation = bind(&Data::minElevation, _r1);
auto maxElevation = bind(&Data::maxElevation, _r1);
known =
("FMin Antenna" >> lit('=') >> int_)[minFrequency = _1] |
("FMax Antenna" >> lit('=') >> int_)[maxFrequency = _1] |
("EMin Antenna" >> lit('=') >> int_)[minElevation = _1] |
("EMax Antenna" >> lit('=') >> int_)[maxElevation = _1]
;
unknown = +alnum >> '=' >> +alnum;
setting = (known(_r1) | unknown) >> +eol;
start =
no_case["[GENERAL]"] >> eol
>> *setting(_val);
}
private:
qi::rule<It, Data(), Skipper> start;
qi::rule<It, void(Data&), Skipper> setting, known;
qi::rule<It, Skipper> unknown;
};
int main() {
using It = std::string::const_iterator;
grammar<It> const g;
for (std::string const file : {
"[GENERAL]\nFMax Antenna = 3000\nFMin Antenna = 2000\nEMin Antenna = -50\nEMax Antenna = 80\n",
"[GENERAL]\nFMax Antenna = 3000\nFMin Antenna = 2000\nEMin Antenna = -50\npappa pio = po po\nEMax Antenna = 80\nCiao = 55\n",
})
{
Data parsed;
It f = begin(file), l = end(file);
bool ok = qi::phrase_parse(f, l, g, qi::blank, parsed);
std::cout << "--- File ---" << "\n";
std::cout << "parsed = " << std::boolalpha << ok << "\n";
if (ok) {
std::cout << "min freq = " << parsed.minFrequency << "\n";
std::cout << "max freq = " << parsed.maxFrequency << "\n";
std::cout << "min elev = " << parsed.minElevation << "\n";
std::cout << "max elev = " << parsed.maxElevation << "\n";
}
if (f!=l) {
std::cout << "Remaining unparsed: ";
while (f!=l) {
char c = *f++;
if (isprint(c)) std::cout << c;
else std::cout << "\\x" << std::setw(2) << std::setfill('0') << std::hex << static_cast<int>(c);
}
}
}
}
Prints
--- File ---
parsed = true
min freq = 2000
max freq = 3000
min elev = -50
max elev = 80
--- File ---
parsed = true
min freq = 2000
max freq = 3000
min elev = -50
max elev = 80

Cannot add values to vector using boost spirit

I want to parse using the boost spirit the following string:
"{"DeliverNr":7424,"fruits":[["apple","banana","orange", "raspberry"]]}"
but I want to put into vector only three first fruits.
I have the following output:
it: { , dist: 0
Can anybody tell me what am I doing wrong ?
#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_fusion.hpp>
#include <boost/spirit/include/phoenix_stl.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/variant/recursive_variant.hpp>
#include <boost/foreach.hpp>
#include <iostream>
#include <vector>
struct SomeStructF
{
std::string str1, str2, str3;
};
using namespace boost::spirit;
qi::rule<std::string::iterator> startRule;
qi::rule<std::string::iterator> endRule;
qi::rule<std::string::iterator, std::string()> stringRule;
qi::rule<std::string::iterator, SomeStructF()> valueRule;
qi::rule<std::string::iterator, std::vector<SomeStructF>()> valuesRule;
char const QUOTATION_MARK{ '"' };
char const OPEN_SQUARE_BRACKET{ '[' };
startRule =
+(qi::char_ - qi::char_(OPEN_SQUARE_BRACKET))
>> qi::char_(OPEN_SQUARE_BRACKET);
endRule = +qi::char_;
stringRule =
QUOTATION_MARK
>> *(qi::char_ - QUOTATION_MARK)
>> QUOTATION_MARK;
valueRule =
OPEN_SQUARE_BRACKET
>> stringRule
>> stringRule
>> stringRule;
valuesRule %=
startRule
>> valueRule
>> endRule;
std::vector<SomeStructF> res;
auto it = data.begin();
if (qi::parse(it, data.end(), valuesRule, res))
{
std::cout << "PARSE succeded" << std::endl;
}
std::cout << "it: " << *it << " , dist: " << std::distance(data.begin(), it) << std::endl;
std::cout << "res size " << res.size() << std::endl;
BOOST_FUSION_ADAPT_STRUCT(
parsers::SomeStructF,
(std::string, str1)
(std::string, str2)
(std::string, str3)
)
It's not particularly clear to me what you are asking. It seems to me it's easy enough to take into acocunt the commas, as you noted:
Live On Coliru
#define BOOST_SPIRIT_DEBUG
#include <iostream>
#include <vector>
#include <string>
#include <iterator>
#include <iomanip>
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
namespace qi = boost::spirit::qi;
struct SomeStructF {
std::string str1, str2, str3;
};
BOOST_FUSION_ADAPT_STRUCT(
SomeStructF,
(std::string, str1)
(std::string, str2)
(std::string, str3)
)
int main()
{
qi::rule<std::string::iterator> startRule;
qi::rule<std::string::iterator> endRule;
qi::rule<std::string::iterator, std::string()> stringRule;
qi::rule<std::string::iterator, SomeStructF()> valueRule;
qi::rule<std::string::iterator, SomeStructF()> valuesRule;
char const QUOTATION_MARK{ '"' };
char const OPEN_SQUARE_BRACKET{ '[' };
startRule =
+(qi::char_ - qi::char_(OPEN_SQUARE_BRACKET))
>> qi::char_(OPEN_SQUARE_BRACKET);
endRule = +qi::char_;
stringRule =
QUOTATION_MARK
>> *(qi::char_ - QUOTATION_MARK)
>> QUOTATION_MARK;
valueRule =
OPEN_SQUARE_BRACKET
>> stringRule >> ','
>> stringRule >> ','
>> stringRule;
valuesRule %=
startRule
>> valueRule
>> endRule;
BOOST_SPIRIT_DEBUG_NODES((startRule)(endRule)(stringRule)(valueRule)(valuesRule));
std::string data = R"({"DeliverNr":7424,"fruits":
[["apple","banana","orange","raspberry"]]})";
std::vector<SomeStructF> res;
auto it = data.begin();
if (qi::parse(it, data.end(), valuesRule, res))
{
std::cout << "PARSE succeded" << std::endl;
std::cout << "res size " << res.size() << std::endl;
for (auto& el : res) {
std::cout << "el: {" << std::quoted(el.str1) << ","
<< std::quoted(el.str2) << ","
<< std::quoted(el.str3) << "}\n";
}
}
else
{
std::cout << "Parse did not succeed\n";
}
if (it != data.end())
std::cout << "Remaining unparsed: '" << std::string(it, data.end()) << "'\n";
}
Prints
PARSE succeded
res size 1
el: {"apple","banana","orange"}
Your grammar (nor your question) shows how you'd like to detect multiple SomeStructF so I can't really know what you want. Perhaps you want to simplify:
Live On Coliru
using It = std::string::const_iterator;
qi::rule<It, std::string()> quoted_ = '"' >> *~qi::char_('"') >> '"';
qi::rule<It, SomeStructF()/*, qi::space_type*/> value_ = quoted_ >> ',' >> quoted_ >> ',' >> quoted_;
using boost::spirit::repository::qi::seek;
BOOST_SPIRIT_DEBUG_NODES((quoted_)(value_));
std::string const data = R"({"DeliverNr":7424,"fruits":[["apple","banana","orange","raspberry"]]}
{"DeliverNr":7435,"botanics":[["pine","maple","oak","pernambucco"]]})";
std::vector<SomeStructF> res;
It it = data.begin();
if (qi::phrase_parse(it, data.end(), *seek["[[" >> value_], qi::space, res)) {
for (auto& el : res) {
std::cout << "el: {" << std::quoted(el.str1) << ","
<< std::quoted(el.str2) << ","
<< std::quoted(el.str3) << "}\n";
}
} else {
std::cout << "Parse did not succeed\n";
}
Which prints
el: {"apple","banana","orange"}
el: {"pine","maple","oak"}
Remaining unparsed: ',"pernambucco"]]}'

Boost Spirit Parser optional expression evaluation

I am trying to parse a line from a text file which of the form:
[int_:] [int_/int_] [(int_, string)] string [string:int_]...
Where [] are optional parameter but will contain tags such as (":", "(", ")", "/").
Also the last format is repeat format "key:value" combination. e.g.:
10: 0x1/2 (8, INC) rd API:2 SI:100
I am able to parse the whole line when all the parameter are available.
But if any of the starting optional parameter is missing then the parser fails.
How can I ignore the optional parameters in the Boost Spirit library? (i.e. skip the assignment of optional variables to default values.)
These are the qi grammar rules:
quoted_string = lexeme[+(char_ -(lit(' ') | lit(')')))];
hex_num = ((lit("0x") | lit("0X")) >> hex) | uint_;
start = (hex_num >> lit(":"))
>> (hex_num >> lit("/") >> hex_num )
>> lit("(") >> hex_num >> lit(",") >> quoted_string >> lit(")")
>> quoted_string
>> quoted_string;
qi::rule<Iterator, std::string(), ascii::space_type> quoted_string;
qi::rule<Iterator, uint32_t(), ascii::space_type> hex_num;
qi::rule<Iterator, employee(), ascii::space_type> start;
Model your AST node to reflect the Parser tree:
struct ratio_t { uint32_t a,b; };
struct opcode_t { uint32_t id; std::string name; };
struct Node {
uint32_t label; // prefix:
boost::optional<ratio_t> ratio; // a/b
boost::optional<opcode_t> opcode; // (id, name)
std::string extra;
std::multimap<std::string, uint32_t> params;
};
(Just making stuff up as I go, because I can only guess what the data means. I'm assuming employee, hex_num and quoted_string are somehow remnants from sample code you started with).
Now when you adapt these structures:
BOOST_FUSION_ADAPT_STRUCT(AST::ratio_t, a, b)
BOOST_FUSION_ADAPT_STRUCT(AST::opcode_t, id, name)
BOOST_FUSION_ADAPT_STRUCT(AST::Node, label, ratio, opcode, extra, params)
You can simply parse into it with an analogous parse tree:
// lexemes
unquoted_string = +(graph - ')');
num = (no_case[ "0x" ] >> hex) | uint_;
param = +(graph - ':') >> ':' >> num;
// skipping productions
opcode = '(' >> num >> ',' >> unquoted_string >> ')';
ratio = num >> '/' >> num;
prefix = (num >> ':') | attr(0); // defaults to 0
start = prefix
>> -ratio
>> -opcode
>> unquoted_string
>> *param;
Now when you parse these test cases:
for (std::string const input : {
"10: 0x1/2 (8, INC) rd API:2 SI:100",
"10: 0x1/2 (8, INC) rd API:2",
"10: 0x1/2 (8, INC) rd",
"10: 0x1/2 rd API:2 SI:100",
"10: rd API:2 SI:100",
"0x1/2 rd API:2 SI:100",
"rd API:2 SI:100",
})
{
It f = input.begin(), l = input.end();
AST::Node data;
bool ok = qi::phrase_parse(f, l, p, qi::ascii::space, data);
if (ok) {
std::cout << "Parse success: " << data << "\n";
}
else {
std::cout << "Parse failure ('" << input << "')\n";
}
if (f!=l) {
std::cout << "Remaining unparsed input: '" << std::string(f,l) << "'\n";
}
}
you get:
Parse success: 10: 1/2 (8, 'INC') rd API:2 SI:100
Parse success: 10: 1/2 (8, 'INC') rd API:2
Parse success: 10: 1/2 (8, 'INC') rd
Parse success: 10: 1/2 -- rd API:2 SI:100
Parse success: 10: -- -- rd API:2 SI:100
Parse success: 0: 1/2 -- rd API:2 SI:100
Parse success: 0: -- -- rd API:2 SI:100
FULL DEMO
Live On Coliru
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/adapted.hpp>
#include <boost/optional/optional_io.hpp>
#include <map>
namespace qi = boost::spirit::qi;
namespace AST {
struct ratio_t { uint32_t a,b; };
struct opcode_t { uint32_t id; std::string name; };
struct Node {
uint32_t label; // prefix:
boost::optional<ratio_t> ratio; // a/b
boost::optional<opcode_t> opcode; // (id, name)
std::string extra;
std::multimap<std::string, uint32_t> params;
};
std::ostream& operator<<(std::ostream& os, ratio_t const& v) {
return os << v.a << "/" << v.b;
}
std::ostream& operator<<(std::ostream& os, opcode_t const& v) {
return os << "(" << v.id << ", '" << v.name << "')";
}
std::ostream& operator<<(std::ostream& os, Node const& v) {
os << v.label << ": " << v.ratio << " " << v.opcode << " " << v.extra;
for (auto& p : v.params) os << " " << p.first << ":" << p.second;
return os;
}
}
BOOST_FUSION_ADAPT_STRUCT(AST::ratio_t, a, b)
BOOST_FUSION_ADAPT_STRUCT(AST::opcode_t, id, name)
BOOST_FUSION_ADAPT_STRUCT(AST::Node, label, ratio, opcode, extra, params)
template <typename It, typename Skipper = qi::ascii::space_type>
struct P : qi::grammar<It, AST::Node(), Skipper> {
P() : P::base_type(start)
{
using namespace qi;
// lexemes
unquoted_string = +(graph - ')');
num = (no_case[ "0x" ] >> hex) | uint_;
param = +(graph - ':') >> ':' >> num;
// skipping productions
opcode = '(' >> num >> ',' >> unquoted_string >> ')';
ratio = num >> '/' >> num;
prefix = (num >> ':') | attr(0); // defaults to 0
start = prefix
>> -ratio
>> -opcode
>> unquoted_string
>> *param;
BOOST_SPIRIT_DEBUG_NODES((start)(unquoted_string)(num)(prefix)(ratio)(opcode)(param))
}
private:
qi::rule<It, AST::ratio_t(), Skipper> ratio;
qi::rule<It, AST::opcode_t(), Skipper> opcode;
qi::rule<It, AST::Node(), Skipper> start;
qi::rule<It, uint32_t(), Skipper> prefix;
//lexemes
qi::rule<It, std::string()> unquoted_string;
qi::rule<It, uint32_t()> num;
qi::rule<It, std::pair<std::string, uint32_t>> param;
};
int main() {
using It = std::string::const_iterator;
P<It> const p;
for (std::string const input : {
"10: 0x1/2 (8, INC) rd API:2 SI:100",
"10: 0x1/2 (8, INC) rd API:2",
"10: 0x1/2 (8, INC) rd",
"10: 0x1/2 rd API:2 SI:100",
"10: rd API:2 SI:100",
"0x1/2 rd API:2 SI:100",
"rd API:2 SI:100",
})
{
It f = input.begin(), l = input.end();
AST::Node data;
bool ok = qi::phrase_parse(f, l, p, qi::ascii::space, data);
if (ok) {
std::cout << "Parse success: " << data << "\n";
}
else {
std::cout << "Parse failure ('" << input << "')\n";
}
if (f!=l) {
std::cout << "Remaining unparsed input: '" << std::string(f,l) << "'\n";
}
}
}