Deal with duplicate section names in INI files - boost

I need to load these values from INI file and print them in the application using C++ Boost Library. The sections have duplicate names. I have been restricted to using C++ Boost Library only.
numColors = 4
boardSize = 11
numSnails = 2
[initialization]
id = 0
row = 3
col = 4
orientation = 0
[initialization]
id = 1
row = 5
col = 0
orientation = 1
[color]
id = 0
nextColor = 1
deltaOrientation = +2
[color]
id = 1
nextColor = 2
deltaOrientation = +1
[color]
id = 2
nextColor = 3
deltaOrientation = -2
[color]
id = 3
nextColor = 0
deltaOrientation = -1

I figured that for a random passer-by the Boost Spirit answer might seem complex/overkill.
I wanted to try again, since it's 2021, whether with C++17, we can do a reasonable job with just the standard library.
Turns out it's a lot more work. The Qi implementation takes 86 lines of code, but the standard library implementation takes 136 lines. Also, it took me a lot longer (several hours) to debug/write. In particular it was hard to get '=', '[', ']' as token boundaries with std::istream&. I used the ctype facet approach from this answer: How do I iterate over cin line by line in C++?
I did leave in the DebugPeeker (20 lines) so you can perhaps understand it yourself.
Short Expo
The top level parse function looks sane and shows what I wanted to achieve: natural std::istream extraction:
static Ast::File std_parse_game(std::string_view input) {
std::istringstream iss{std::string(input)};
using namespace Helpers;
if (Ast::File parsed; iss >> parsed)
return parsed;
throw std::runtime_error("Unable to parse game");
}
All the rest lives in namespace Helpers:
static inline std::istream& operator>>(std::istream& is, Ast::File& v) {
for (section s; is >> s;) {
if (s.name == "parameters")
is >> v.parameters;
else if (s.name == "initialization")
is >> v.initializations.emplace_back();
else if (s.name == "color")
is >> v.colors.emplace_back();
else
is.setstate(std::ios::failbit);
}
if (is.eof())
is.clear();
return is;
}
So far, this is paying out well. The different section types are similar:
static inline std::istream& operator>>(std::istream& is, Ast::Parameters& v) {
return is
>> entry{"numColors", v.numColors}
>> entry{"boardSize", v.boardSize}
>> entry{"numSnails", v.numSnails};
}
static inline std::istream& operator>>(std::istream& is, Ast::Initialization& v) {
return is
>> entry{"id", v.id}
>> entry{"row", v.row}
>> entry{"col", v.col}
>> entry{"orientation", v.orientation};
}
static inline std::istream& operator>>(std::istream& is, Ast::Color& v) {
return is
>> entry{"id", v.id}
>> entry{"nextColor", v.nextColor}
>> entry{"deltaOrientation", v.deltaOrientation};
}
Now, if all was plain sailing like this, I would not be recommending Spirit. Now we get into conditional parsings.
The entry{"name", value} formulations use a "manipulator type":
template <typename T> struct entry {
entry(std::string name, T& into) : _name(name), _into(into) {}
std::string _name;
T& _into;
friend std::istream& operator>>(std::istream& is, entry e) {
return is >> expect{e._name} >> expect{'='} >> e._into;
}
};
Similarly, sections are using expect and token:
struct section {
std::string name;
friend std::istream& operator>>(std::istream& is, section& s) {
if (is >> expect('['))
return is >> token{s.name} >> expect{']'};
return is;
}
};
The conditional is important to be able to detect EOF without putting the stream into a hard failed mode (is.bad() != is.fail()).
expect is built on top of token:
template <typename T> struct expect {
expect(T expected) : _expected(expected) {}
T _expected;
friend std::istream& operator>>(std::istream& is, expect const& e) {
if (T actual; is >> token{actual})
if (actual != e._expected)
is.setstate(std::ios::failbit);
return is;
}
};
You will notice that there's much less error information. We just make the
stream fail() in case an expected token is not found.
Here's where the real complexity comes. I didn't want to parse character by
character. But reading std::string using operator>> would only stop on
whitespace, meaning that a section name would "eat" the bracket: parameters]
instead of parameters, and keys might eat the = character if there was no
separating space.
In the answer linked above we learn how to build our own character
classification locale facet:
// make sure =,[,] break tokens
struct mytoken_ctype : std::ctype<char> {
static auto const* get_table() {
static std::vector rc(table_size, std::ctype_base::mask());
rc[' '] = rc['\f'] = rc['\v'] = rc['\t'] = rc['\r'] = rc['\n'] =
std::ctype_base::space;
// crucial for us:
rc['='] = rc['['] = rc[']'] = std::ctype_base::space;
return rc.data();
}
mytoken_ctype() : std::ctype<char>(get_table()) {}
};
And then we need to use it but only if we parse a std::string token. That
way, if we expect('=') it will not skip over '=' because our facet calls it whitespace...
template <typename T> struct token {
token(T& into) : _into(into) {}
T& _into;
friend std::istream& operator>>(std::istream& is, token const& t) {
std::locale loc = is.getloc();
if constexpr (std::is_same_v<std::decay_t<T>, std::string>) {
loc = is.imbue(std::locale(std::locale(), new mytoken_ctype()));
}
try { is >> t._into; is.imbue(loc); }
catch (...) { is.imbue(loc); throw; }
return is;
}
};
I tried to keep it pretty condensed. Had I used proper formatting, we would
have had more lines of code still :)
DEMO AND TEST
I used the same Ast types, so it made sense to test both implementations and
compare the results for equality.
NOTES:
On Compiler Explorer so we can enjoy libfmt for easy output
For comparison I used one C++20 feature to get compiler generated operator==
Live On Compiler Explorer
#include <boost/spirit/home/qi.hpp>
#include <boost/fusion/include/io.hpp>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <fmt/ranges.h>
#include <fmt/ostream.h>
namespace qi = boost::spirit::qi;
namespace Ast {
using Id = unsigned;
using Size = uint16_t; // avoiding char types for easy debug/output
using Coord = Size;
using ColorNumber = Size;
using Orientation = Size;
using Delta = signed;
struct Parameters {
Size numColors{}, boardSize{}, numSnails{};
bool operator==(Parameters const&) const = default;
};
struct Initialization {
Id id;
Coord row;
Coord col;
Orientation orientation;
bool operator==(Initialization const&) const = default;
};
struct Color {
Id id;
ColorNumber nextColor;
Delta deltaOrientation;
bool operator==(Color const&) const = default;
};
struct File {
Parameters parameters;
std::vector<Initialization> initializations;
std::vector<Color> colors;
bool operator==(File const&) const = default;
};
using boost::fusion::operator<<;
template <typename T>
static inline std::ostream& operator<<(std::ostream& os, std::vector<T> const& v) {
return os << fmt::format("vector<{}>{}",
boost::core::demangle(typeid(T).name()), v);
}
} // namespace Ast
BOOST_FUSION_ADAPT_STRUCT(Ast::Parameters, numColors, boardSize, numSnails)
BOOST_FUSION_ADAPT_STRUCT(Ast::Initialization, id, row, col, orientation)
BOOST_FUSION_ADAPT_STRUCT(Ast::Color, id, nextColor, deltaOrientation)
BOOST_FUSION_ADAPT_STRUCT(Ast::File, parameters, initializations, colors)
template <typename It>
struct GameParser : qi::grammar<It, Ast::File()> {
GameParser() : GameParser::base_type(start) {
using namespace qi;
start = skip(blank)[file];
auto section = [](const std::string& name) {
return copy('[' >> lexeme[lit(name)] >> ']' >> (+eol | eoi));
};
auto required = [](const std::string& name, auto value) {
return copy(lexeme[eps > lit(name)] > '=' > value >
(+eol | eoi));
};
file = parameters >
*initialization >
*color >
eoi; // must reach end of input
parameters = section("parameters") >
required("numColors", _size) >
required("boardSize", _size) >
required("numSnails", _size);
initialization = section("initialization") >
required("id", _id) >
required("row", _coord) >
required("col", _coord) >
required("orientation", _orientation);
color = section("color") >
required("id", _id) >
required("nextColor", _colorNumber) >
required("deltaOrientation", _delta);
BOOST_SPIRIT_DEBUG_NODES((file)(parameters)(initialization)(color))
}
private:
using Skipper = qi::blank_type;
qi::rule<It, Ast::File()> start;
qi::rule<It, Ast::File(), Skipper> file;
// sections
qi::rule<It, Ast::Parameters(), Skipper> parameters;
qi::rule<It, Ast::Initialization(), Skipper> initialization;
qi::rule<It, Ast::Color(), Skipper> color;
// value types
qi::uint_parser<Ast::Id> _id;
qi::uint_parser<Ast::Size> _size;
qi::uint_parser<Ast::Coord> _coord;
qi::uint_parser<Ast::ColorNumber> _colorNumber;
qi::uint_parser<Ast::Orientation> _orientation;
qi::int_parser<Ast::Delta> _delta;
};
static Ast::File qi_parse_game(std::string_view input) {
using SVI = std::string_view::const_iterator;
static const GameParser<SVI> parser{};
try {
Ast::File parsed;
if (qi::parse(input.begin(), input.end(), parser, parsed)) {
return parsed;
}
throw std::runtime_error("Unable to parse game");
} catch (qi::expectation_failure<SVI> const& ef) {
std::ostringstream oss;
auto where = ef.first - input.begin();
auto sol = 1 + input.find_last_of("\r\n", where);
auto lineno = 1 + std::count(input.begin(), input.begin() + sol, '\n');
auto col = 1 + where - sol;
auto llen = input.substr(sol).find_first_of("\r\n");
oss << "input.txt:" << lineno << ":" << col << " Expected: " << ef.what_ << "\n"
<< " note: " << input.substr(sol, llen) << "\n"
<< " note:" << std::setw(col) << "" << "^--- here";
throw std::runtime_error(oss.str());
}
}
namespace Helpers {
struct DebugPeeker {
DebugPeeker(std::istream& is, int line) : is(is), line(line) { dopeek(); }
~DebugPeeker() { dopeek(); }
private:
std::istream& is;
int line;
void dopeek() const {
std::char_traits<char> t;
auto ch = is.peek();
std::cerr << "DEBUG " << line << " Peek: ";
if (std::isgraph(ch))
std::cerr << "'" << t.to_char_type(ch) << "'";
else
std::cerr << "<" << ch << ">";
std::cerr << " " << std::boolalpha << is.good() << "\n";
}
};
#define DEBUG_PEEK(is) // Peeker _peek##__LINE__(is, __LINE__);
// make sure =,[,] break tokens
struct mytoken_ctype : std::ctype<char> {
static auto const* get_table() {
static std::vector rc(table_size, std::ctype_base::mask());
rc[' '] = rc['\f'] = rc['\v'] = rc['\t'] = rc['\r'] = rc['\n'] =
std::ctype_base::space;
// crucial for us:
rc['='] = rc['['] = rc[']'] = std::ctype_base::space;
return rc.data();
}
mytoken_ctype() : std::ctype<char>(get_table()) {}
};
template <typename T> struct token {
token(T& into) : _into(into) {}
T& _into;
friend std::istream& operator>>(std::istream& is, token const& t) {
DEBUG_PEEK(is);
std::locale loc = is.getloc();
if constexpr (std::is_same_v<std::decay_t<T>, std::string>) {
loc = is.imbue(std::locale(std::locale(), new mytoken_ctype()));
}
try { is >> t._into; is.imbue(loc); }
catch (...) { is.imbue(loc); throw; }
return is;
}
};
template <typename T> struct expect {
expect(T expected) : _expected(expected) {}
T _expected;
friend std::istream& operator>>(std::istream& is, expect const& e) {
DEBUG_PEEK(is);
if (T actual; is >> token{actual})
if (actual != e._expected)
is.setstate(std::ios::failbit);
return is;
}
};
template <typename T> struct entry {
entry(std::string name, T& into) : _name(name), _into(into) {}
std::string _name;
T& _into;
friend std::istream& operator>>(std::istream& is, entry e) {
DEBUG_PEEK(is);
return is >> expect{e._name} >> expect{'='} >> e._into;
}
};
struct section {
std::string name;
friend std::istream& operator>>(std::istream& is, section& s) {
DEBUG_PEEK(is);
if (is >> expect('['))
return is >> token{s.name} >> expect{']'};
return is;
}
};
static inline std::istream& operator>>(std::istream& is, Ast::Parameters& v) {
DEBUG_PEEK(is);
return is
>> entry{"numColors", v.numColors}
>> entry{"boardSize", v.boardSize}
>> entry{"numSnails", v.numSnails};
}
static inline std::istream& operator>>(std::istream& is, Ast::Initialization& v) {
DEBUG_PEEK(is);
return is
>> entry{"id", v.id}
>> entry{"row", v.row}
>> entry{"col", v.col}
>> entry{"orientation", v.orientation};
}
static inline std::istream& operator>>(std::istream& is, Ast::Color& v) {
DEBUG_PEEK(is);
return is
>> entry{"id", v.id}
>> entry{"nextColor", v.nextColor}
>> entry{"deltaOrientation", v.deltaOrientation};
}
static inline std::istream& operator>>(std::istream& is, Ast::File& v) {
DEBUG_PEEK(is);
for (section s; is >> s;) {
if (s.name == "parameters")
is >> v.parameters;
else if (s.name == "initialization")
is >> v.initializations.emplace_back();
else if (s.name == "color")
is >> v.colors.emplace_back();
else
is.setstate(std::ios::failbit);
}
if (is.eof())
is.clear();
return is;
}
}
static Ast::File std_parse_game(std::string_view input) {
std::istringstream iss{std::string(input)};
using namespace Helpers;
if (Ast::File parsed; iss >> parsed)
return parsed;
throw std::runtime_error("Unable to parse game");
}
std::string read_file(const std::string& name) {
std::ifstream ifs(name);
return std::string(std::istreambuf_iterator<char>(ifs), {});
}
int main() {
std::string const game_save = read_file("input.txt");
Ast::File g1, g2;
try {
std::cout << "Qi: " << (g1 = qi_parse_game(game_save)) << "\n";
} catch (std::exception const& e) { std::cerr << e.what() << "\n"; }
try {
std::cout << "std: " << (g2 = std_parse_game(game_save)) << "\n";
} catch (std::exception const& e) { std::cerr << e.what() << "\n"; }
std::cout << "Equal: " << std::boolalpha << (g1 == g2) << "\n";
}
To my great relief, the parsers agree on the data:
Qi: ((4 11 2)
vector<Ast::Initialization>{(0 3 4 0), (1 5 0 1)}
vector<Ast::Color>{(0 1 2), (1 2 1), (2 3 -2), (3 0 -1)})
std: ((4 11 2)
vector<Ast::Initialization>{(0 3 4 0), (1 5 0 1)}
vector<Ast::Color>{(0 1 2), (1 2 1), (2 3 -2), (3 0 -1)})
Equal: true
Summary/Conclusion
Although this answer is "standard" and "portable" it has some drawbacks.
For example it is certainly not easier to get right, it has virtually no debug options or error reporting, it doesn't validate the input format as much. E.g. it will still read this unholy mess and accept it:
[parameters] numColors=999 boardSize=999 numSnails=999
[color] id=0 nextColor=1 deltaOrientation=+2 [color] id=1 nextColor=2
deltaOrientation=+1 [
initialization] id=1 row=5 col=0 orientation=1
[color] id=2 nextColor=3 deltaOrientation=-2
[parameters] numColors=4 boardSize=11 numSnails=2
[color] id=3 nextColor=0 deltaOrientation=-1
[initialization] id=0 row=3 col=4 orientation=0
If your input format is not stable and computer-written, I would highly recommend against the standard-library approach because it will lead to hard-to-diagnose problems and just horrible UX (don't make your users want to throw their computer out of the window because of unhelpful error messages like "Unable to parse game data").
Otherwise, you might. For one thing, it'll be faster to compile.

What It Isn't
In short, this is not INI format at all. It just very loosely resembles it. Which is nice.
What Is It Instead?
You don't specify a lot, so I'm going to make assumptions.
I'm going to, for simplicity, assume that
initialization sections precede color sections
keys in like sections have the same order always
all keys shown are mandatory in like sections
the deltas are signed integral values (positive sign being optional)
all other values are non-negative integral numbers
whitespace is not significant
case is significant
all numbers are in decimal form (regardless of leading zeros)
Non-essential deductions (could be used to add more validation):
the number of of initializations = numSnails
the board size dictates row and col are in [0, boardSize)
Data Structures
To represent the file, I'd make:
namespace Ast {
struct Initialization {
unsigned id, row, col, orientation;
};
struct Color {
unsigned id, nextColor;
int deltaOrientation;
};
struct File {
unsigned numColors, boardSize, numSnails;
std::vector<Initialization> initializations;
std::vector<Color> colors;
};
}
That's the simplest I can think of.
Parsing It
Is a nice job for Boost Spirit. If we adapt the data structures as Fusion Sequences:
BOOST_FUSION_ADAPT_STRUCT(Ast::Initialization, id, row, col, orientation)
BOOST_FUSION_ADAPT_STRUCT(Ast::Color, id, nextColor, deltaOrientation)
BOOST_FUSION_ADAPT_STRUCT(Ast::File, numColors, boardSize, numSnails,
initializations, colors)
We can basically let the parser "write itself":
template <typename It>
struct GameParser : qi::grammar<It, Ast::File()> {
GameParser() : GameParser::base_type(start) {
using namespace qi;
start = skip(blank)[file];
auto section = [](std::string name) {
return copy('[' >> lexeme[lit(name)] >> ']' >> (+eol | eoi));
};
auto required = [](std::string name) {
return copy(lexeme[eps > lit(name)] > '=' > auto_ >
(+eol | eoi));
};
file =
required("numColors") >
required("boardSize") >
required("numSnails") >
*initialization >
*color >
eoi; // must reach end of input
initialization = section("initialization") >
required("id") >
required("row") >
required("col") >
required("orientation");
color = section("color") >
required("id") >
required("nextColor") >
required("deltaOrientation");
BOOST_SPIRIT_DEBUG_NODES((file)(initialization)(color))
}
private:
using Skipper = qi::blank_type;
qi::rule<It, Ast::File()> start;
qi::rule<It, Ast::File(), Skipper> file;
qi::rule<It, Ast::Initialization(), Skipper> initialization;
qi::rule<It, Ast::Color(), Skipper> color;
};
Because of the many assumptions we've made we littered the place with expectation points (operator> sequences, instead of operator>>). This means we get "helpful" error messages on invalid input, like
Expected: nextColor
Expected: =
Expected: <eoi>
See also BONUS section below that improves this a lot
Testing/Live Demo
Testing it, we will read the file first and then parse it using that parser:
std::string read_file(std::string name) {
std::ifstream ifs(name);
return std::string(std::istreambuf_iterator<char>(ifs), {});
}
static Ast::File parse_game(std::string_view input) {
using SVI = std::string_view::const_iterator;
static const GameParser<SVI> parser{};
try {
Ast::File parsed;
if (qi::parse(input.begin(), input.end(), parser, parsed)) {
return parsed;
}
throw std::runtime_error("Unable to parse game");
} catch (qi::expectation_failure<SVI> const& ef) {
std::ostringstream oss;
oss << "Expected: " << ef.what_;
throw std::runtime_error(oss.str());
}
}
A lot could be improved, but for now it works and parses your input:
Live On Coliru
int main() {
std::string game_save = read_file("input.txt");
Ast::File data = parse_game(game_save);
}
The absense of output means success.
BONUS
Some improvements, instead of using auto_ to generate the right parser for the type, we can make that explicit:
namespace Ast {
using Id = unsigned;
using Size = uint8_t;
using Coord = Size;
using ColorNumber = Size;
using Orientation = Size;
using Delta = signed;
struct Initialization {
Id id;
Coord row;
Coord col;
Orientation orientation;
};
struct Color {
Id id;
ColorNumber nextColor;
Delta deltaOrientation;
};
struct File {
Size numColors{}, boardSize{}, numSnails{};
std::vector<Initialization> initializations;
std::vector<Color> colors;
};
} // namespace Ast
And then in the parser define the analogous:
qi::uint_parser<Ast::Id> _id;
qi::uint_parser<Ast::Size> _size;
qi::uint_parser<Ast::Coord> _coord;
qi::uint_parser<Ast::ColorNumber> _colorNumber;
qi::uint_parser<Ast::Orientation> _orientation;
qi::int_parser<Ast::Delta> _delta;
Which we then use e.g.:
initialization = section("initialization") >
required("id", _id) >
required("row", _coord) >
required("col", _coord) >
required("orientation", _orientation);
Now we can improve the error messages to be e.g.:
input.txt:2:13 Expected: <unsigned-integer>
note: boardSize = (11)
note: ^--- here
Or
input.txt:16:19 Expected: <alternative><eol><eoi>
note: nextColor = 1 deltaOrientation = +2
note: ^--- here
Full Code, Live On Coliru
//#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/home/qi.hpp>
#include <fstream>
#include <sstream>
#include <iomanip>
namespace qi = boost::spirit::qi;
namespace Ast {
using Id = unsigned;
using Size = uint8_t;
using Coord = Size;
using ColorNumber = Size;
using Orientation = Size;
using Delta = signed;
struct Initialization {
Id id;
Coord row;
Coord col;
Orientation orientation;
};
struct Color {
Id id;
ColorNumber nextColor;
Delta deltaOrientation;
};
struct File {
Size numColors{}, boardSize{}, numSnails{};
std::vector<Initialization> initializations;
std::vector<Color> colors;
};
} // namespace Ast
BOOST_FUSION_ADAPT_STRUCT(Ast::Initialization, id, row, col, orientation)
BOOST_FUSION_ADAPT_STRUCT(Ast::Color, id, nextColor, deltaOrientation)
BOOST_FUSION_ADAPT_STRUCT(Ast::File, numColors, boardSize, numSnails,
initializations, colors)
template <typename It>
struct GameParser : qi::grammar<It, Ast::File()> {
GameParser() : GameParser::base_type(start) {
using namespace qi;
start = skip(blank)[file];
auto section = [](const std::string& name) {
return copy('[' >> lexeme[lit(name)] >> ']' >> (+eol | eoi));
};
auto required = [](const std::string& name, auto value) {
return copy(lexeme[eps > lit(name)] > '=' > value >
(+eol | eoi));
};
file =
required("numColors", _size) >
required("boardSize", _size) >
required("numSnails", _size) >
*initialization >
*color >
eoi; // must reach end of input
initialization = section("initialization") >
required("id", _id) >
required("row", _coord) >
required("col", _coord) >
required("orientation", _orientation);
color = section("color") >
required("id", _id) >
required("nextColor", _colorNumber) >
required("deltaOrientation", _delta);
BOOST_SPIRIT_DEBUG_NODES((file)(initialization)(color))
}
private:
using Skipper = qi::blank_type;
qi::rule<It, Ast::File()> start;
qi::rule<It, Ast::File(), Skipper> file;
qi::rule<It, Ast::Initialization(), Skipper> initialization;
qi::rule<It, Ast::Color(), Skipper> color;
qi::uint_parser<Ast::Id> _id;
qi::uint_parser<Ast::Size> _size;
qi::uint_parser<Ast::Coord> _coord;
qi::uint_parser<Ast::ColorNumber> _colorNumber;
qi::uint_parser<Ast::Orientation> _orientation;
qi::int_parser<Ast::Delta> _delta;
};
std::string read_file(const std::string& name) {
std::ifstream ifs(name);
return std::string(std::istreambuf_iterator<char>(ifs), {});
}
static Ast::File parse_game(std::string_view input) {
using SVI = std::string_view::const_iterator;
static const GameParser<SVI> parser{};
try {
Ast::File parsed;
if (qi::parse(input.begin(), input.end(), parser, parsed)) {
return parsed;
}
throw std::runtime_error("Unable to parse game");
} catch (qi::expectation_failure<SVI> const& ef) {
std::ostringstream oss;
auto where = ef.first - input.begin();
auto sol = 1 + input.find_last_of("\r\n", where);
auto lineno = 1 + std::count(input.begin(), input.begin() + sol, '\n');
auto col = 1 + where - sol;
auto llen = input.substr(sol).find_first_of("\r\n");
oss << "input.txt:" << lineno << ":" << col << " Expected: " << ef.what_ << "\n"
<< " note: " << input.substr(sol, llen) << "\n"
<< " note:" << std::setw(col) << "" << "^--- here";
throw std::runtime_error(oss.str());
}
}
int main() {
std::string game_save = read_file("input.txt");
try {
Ast::File data = parse_game(game_save);
} catch (std::exception const& e) {
std::cerr << e.what() << "\n";
}
}
Look here for various failure modes and BOOST_SPIRIT_DEBUG ouput:

Related

Composing boost::variant visitors for recursive variants

I have an application with several boost::variants which share many of the fields. I would like to be able to compose these visitors into visitors for "larger" variants without copying and pasting a bunch of code. It seems straightforward to do this for non-recursive variants, but once you have a recursive one, the self-references within the visitor (of course) point to the wrong class. To make this concrete (and cribbing from the boost::variant docs):
#include "boost/variant.hpp"
#include <iostream>
struct add;
struct sub;
template <typename OpTag> struct binop;
typedef boost::variant<
int
, boost::recursive_wrapper< binop<add> >
, boost::recursive_wrapper< binop<sub> >
> expression;
template <typename OpTag>
struct binop
{
expression left;
expression right;
binop( const expression & lhs, const expression & rhs )
: left(lhs), right(rhs)
{
}
};
// Add multiplication
struct mult;
typedef boost::variant<
int
, boost::recursive_wrapper< binop<add> >
, boost::recursive_wrapper< binop<sub> >
, boost::recursive_wrapper< binop<mult> >
> mult_expression;
class calculator : public boost::static_visitor<int>
{
public:
int operator()(int value) const
{
return value;
}
int operator()(const binop<add> & binary) const
{
return boost::apply_visitor( *this, binary.left )
+ boost::apply_visitor( *this, binary.right );
}
int operator()(const binop<sub> & binary) const
{
return boost::apply_visitor( *this, binary.left )
- boost::apply_visitor( *this, binary.right );
}
};
class mult_calculator : public boost::static_visitor<int>
{
public:
int operator()(int value) const
{
return value;
}
int operator()(const binop<add> & binary) const
{
return boost::apply_visitor( *this, binary.left )
+ boost::apply_visitor( *this, binary.right );
}
int operator()(const binop<sub> & binary) const
{
return boost::apply_visitor( *this, binary.left )
- boost::apply_visitor( *this, binary.right );
}
int operator()(const binop<mult> & binary) const
{
return boost::apply_visitor( *this, binary.left )
* boost::apply_visitor( *this, binary.right );
}
};
// I'd like something like this to compile
// class better_mult_calculator : public calculator
// {
// public:
// int operator()(const binop<mult> & binary) const
// {
// return boost::apply_visitor( *this, binary.left )
// * boost::apply_visitor( *this, binary.right );
// }
// };
int main(int argc, char **argv)
{
// result = ((7-3)+8) = 12
expression result(binop<add>(binop<sub>(7,3), 8));
assert( boost::apply_visitor(calculator(),result) == 12 );
std::cout << "Success add" << std::endl;
// result2 = ((7-3)+8)*2 = 12
mult_expression result2(binop<mult>(binop<add>(binop<sub>(7,3), 8),2));
assert( boost::apply_visitor(mult_calculator(),result2) == 24 );
std::cout << "Success mult" << std::endl;
}
I would really like something like that commented out better_mult_expression to compile (and work) but it doesn't -- because the this pointers within the base calculator visitor don't reference mult_expression, but expression.
Does anyone have suggestions for overcoming this or am I just barking down the wrong tree?
Firstly, I'd suggest the variant to include all possible node types, not distinguishing between mult and expression. This distinction makes no sense at the AST level, only at a parser stage (if you implement operator precedence in recursive/PEG fashion).
Other than that, here's a few observations:
if you encapsulate the apply_visitor dispatch into your evaluation functor you can reduce the code duplication by a big factor
your real question seems not to be about composing variants, but composing visitors, more specifically, by inheritance.
You can use using to pull inherited overloads into scope for overload resolution, so this might be the most direct answer:
Live On Coliru
struct better_mult_calculator : calculator {
using calculator::operator();
auto operator()(const binop<mult>& binary) const
{
return boost::apply_visitor(*this, binary.left) *
boost::apply_visitor(*this, binary.right);
}
};
IMPROVING!
Starting from that listing let's shave off some noise!
remove unncessary AST distinction (-40 lines, down to 55 lines of code)
generalize the operations; the <functional> header comes standard with these:
namespace AST {
template <typename> struct binop;
using add = binop<std::plus<>>;
using sub = binop<std::minus<>>;
using mult = binop<std::multiplies<>>;
using expr = boost::variant<int,
recursive_wrapper<add>,
recursive_wrapper<sub>,
recursive_wrapper<mult>>;
template <typename> struct binop { expr left, right; };
} // namespace AST
Now the entire calculator can be:
struct calculator : boost::static_visitor<int> {
int operator()(int value) const { return value; }
template <typename Op>
int operator()(AST::binop<Op> const& binary) const {
return Op{}(boost::apply_visitor(*this, binary.left),
boost::apply_visitor(*this, binary.right));
}
};
Here your variant can add arbitrary operations without even needing to touch the calculator.
Live Demo, 43 Lines Of Code
Like I mentioned starting off, encapsulate visitation!
struct Calculator {
template <typename... T> int operator()(boost::variant<T...> const& v) const {
return boost::apply_visitor(*this, v);
}
template <typename T>
int operator()(T const& lit) const { return lit; }
template <typename Op>
int operator()(AST::binop<Op> const& bin) const {
return Op{}(operator()(bin.left), operator()(bin.right));
}
};
Now you can just call your calculator, like intended:
Calculator calc;
auto result1 = calc(e1);
It will work when you extend the variant with operatios or even other literal types (like e.g. double). It will even work, regardless of whether you pass it an incompatible variant type that holds a subset of the node types.
To finish that off for maintainability/readability, I'd suggest making operator() only a dispatch function:
Full Demo
Live On Coliru
#include <boost/variant.hpp>
#include <iostream>
namespace AST {
using boost::recursive_wrapper;
template <typename> struct binop;
using add = binop<std::plus<>>;
using sub = binop<std::minus<>>;
using mult = binop<std::multiplies<>>;
using expr = boost::variant<int,
recursive_wrapper<add>,
recursive_wrapper<sub>,
recursive_wrapper<mult>>;
template <typename> struct binop { expr left, right; };
} // namespace AST
struct Calculator {
auto operator()(auto const& v) const { return call(v); }
private:
template <typename... T> int call(boost::variant<T...> const& v) const {
return boost::apply_visitor(*this, v);
}
template <typename T>
int call(T const& lit) const { return lit; }
template <typename Op>
int call(AST::binop<Op> const& bin) const {
return Op{}(call(bin.left), call(bin.right));
}
};
int main()
{
using namespace AST;
std::cout << std::boolalpha;
auto sub_expr = add{sub{7, 3}, 8};
expr e1 = sub_expr;
expr e2 = mult{sub_expr, 2};
Calculator calc;
auto result1 = calc(e1);
std::cout << "result1: " << result1 << " Success? " << (12 == result1) << "\n";
// result2 = ((7-3)+8)*2 = 12
auto result2 = calc(e2);
std::cout << "result2: " << result2 << " Success? " << (24 == result2) << "\n";
}
Still prints
result1: 12 Success? true
result2: 24 Success? true

How to make recursive Spirit X3 parser with a separate visitor class

A parser application where I’m working on calls for recursive rules. Besides looking into the Recursive AST tutorial examples of Boost Spirit X3 which can be found here:
https://www.boost.org/doc/libs/develop/libs/spirit/doc/x3/html/index.html, I was looking for a solution with a std::variant of some types as well as a std::vector of that same
variant type.
In the StackOverflow post titled: Recursive rule in Spirit.X3, I found the code from the answer from sehe a decent starting point for my parser.
I have repeated the code here but I have limited the input strings to be tested. Because the full list from the original is not relevant for this question here.
//#define BOOST_SPIRIT_X3_DEBUG
#include <iostream>
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/home/x3.hpp>
#include <string>
#include <vector>
#include <variant>
struct value: std::variant<int,float,std::vector<value>>
{
using base_type = std::variant<int,float,std::vector<value>>;
using base_type::variant;
friend std::ostream& operator<<(std::ostream& os, base_type const& v) {
struct {
std::ostream& operator()(float const& f) const { return _os << "float:" << f; }
std::ostream& operator()(int const& i) const { return _os << "int:" << i; }
std::ostream& operator()(std::vector<value> const& v) const {
_os << "tuple: [";
for (auto& el : v) _os << el << ",";
return _os << ']';
}
std::ostream& _os;
} vis { os };
return std::visit(vis, v);
}
};
namespace parser {
namespace x3 = boost::spirit::x3;
x3::rule<struct value_class, value> const value_ = "value";
x3::rule<struct o_tuple_class, std::vector<value> > o_tuple_ = "tuple";
x3::real_parser<float, x3::strict_real_policies<float> > float_;
const auto o_tuple__def = "tuple" >> x3::lit(':') >> ("[" >> value_ % "," >> "]");
const auto value__def
= "float" >> (':' >> float_)
| "int" >> (':' >> x3::int_)
| o_tuple_
;
BOOST_SPIRIT_DEFINE(value_, o_tuple_)
const auto entry_point = x3::skip(x3::space) [ value_ ];
}
int main()
{
for (std::string const str : {
"float: 3.14",
"int: 3",
"tuple: [float: 3.14,int: 3]",
"tuple: [float: 3.14,int: 3,tuple: [float: 4.14,int: 4]]"
}) {
std::cout << "============ '" << str << "'\n";
//using boost::spirit::x3::parse;
auto first = str.begin(), last = str.end();
value val;
if (parse(first, last, parser::entry_point, val))
std::cout << "Parsed '" << val << "'\n";
else
std::cout << "Parse failed\n";
if (first != last)
std::cout << "Remaining input: '" << std::string(first, last) << "'\n";
}
}
However I would like to use a traditional visitor class rather than making ostream a friend in the variant class. You know just a struct/class with a bunch of function objects for each type you encounter in the variant and a "for loop" for the vector that calls std::visit for each
element.
My goal for the traditional visitor class is to be able to maintain a state machine during printing.
My own attempts to write this visitor class did fail because I ran into an issue with my GCC 8.1 compiler. With GCC during compilation std::variant happens to be std::variant_size somehow and I got the following error:
error: incomplete type 'std::variant_size' used in nested name specifier
More about this here:
Using std::visit on a class inheriting from std::variant - libstdc++ vs libc++
Is it possible giving this constraint on GCC to write a visitor class for the code example I included, so that the ostream stuff can be removed?
Is it possible giving this constraint on GCC to write a visitor class for the code example I included, so that the ostream stuff can be removed?
Sure. Basically, I see three approaches:
1. Add the template machinery
You can specialize the implementation details accidentally required by GCC:
struct value: std::variant<int,float,std::vector<value>> {
using base_type = std::variant<int,float,std::vector<value>>;
using base_type::variant;
};
namespace std {
template <> struct variant_size<value> :
std::variant_size<value::base_type> {};
template <size_t I> struct variant_alternative<I, value> :
std::variant_alternative<I, value::base_type> {};
}
See it live on Wandbox (GCC 8.1)
2. Don't (again live)
Extending the std namespace is fraught (though I think it's legal for
user-defined types). So, you can employ my favorite pattern and hide th
estd::visit dispatch in the function object itself:
template <typename... El>
void operator()(std::variant<El...> const& v) const { std::visit(*this, v); }
Now you can simply call the functor and it will automatically dispatch
on your own variant-derived type because that operator() overload does
NOT have the problems that GCC stdlib has:
if (parse(first, last, parser::entry_point, val))
{
display_visitor display { std::cout };
std::cout << "Parsed '";
display(val);
std::cout << "'\n";
}
3. Make things explicit
I like this the least, but it does have merit: there's no magic and no
tricks:
struct value: std::variant<int,float,std::vector<value>> {
using base_type = std::variant<int,float,std::vector<value>>;
using base_type::variant;
base_type const& as_variant() const { return *this; }
base_type& as_variant() { return *this; }
};
struct display_visitor {
void operator()(value const& v) const { std::visit(*this, v.as_variant()); }
// ...
Again, live
SUMMARY
After thinking a bit more, I'd recommend the last approach, due to the relative simplicity. Clever is often a code-smell :)
Full listing for future visitors:
//#define BOOST_SPIRIT_X3_DEBUG
#include <iostream>
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/home/x3.hpp>
#include <string>
#include <vector>
#include <variant>
struct value: std::variant<int,float,std::vector<value>> {
using base_type = std::variant<int,float,std::vector<value>>;
using base_type::variant;
base_type const& as_variant() const { return *this; }
base_type& as_variant() { return *this; }
};
struct display_visitor {
std::ostream& _os;
void operator()(value const& v) const { std::visit(*this, v.as_variant()); }
void operator()(float const& f) const { _os << "float:" << f; }
void operator()(int const& i) const { _os << "int:" << i; }
void operator()(std::vector<value> const& v) const {
_os << "tuple: [";
for (auto& el : v) {
operator()(el);
_os << ",";
}
_os << ']';
}
};
namespace parser {
namespace x3 = boost::spirit::x3;
x3::rule<struct value_class, value> const value_ = "value";
x3::rule<struct o_tuple_class, std::vector<value> > o_tuple_ = "tuple";
x3::real_parser<float, x3::strict_real_policies<float> > float_;
const auto o_tuple__def = "tuple" >> x3::lit(':') >> ("[" >> value_ % "," >> "]");
const auto value__def
= "float" >> (':' >> float_)
| "int" >> (':' >> x3::int_)
| o_tuple_
;
BOOST_SPIRIT_DEFINE(value_, o_tuple_)
const auto entry_point = x3::skip(x3::space) [ value_ ];
}
int main()
{
for (std::string const str : {
"float: 3.14",
"int: 3",
"tuple: [float: 3.14,int: 3]",
"tuple: [float: 3.14,int: 3,tuple: [float: 4.14,int: 4]]"
}) {
std::cout << "============ '" << str << "'\n";
//using boost::spirit::x3::parse;
auto first = str.begin(), last = str.end();
value val;
if (parse(first, last, parser::entry_point, val))
{
display_visitor display { std::cout };
std::cout << "Parsed '";
display(val);
std::cout << "'\n";
}
else
std::cout << "Parse failed\n";
if (first != last)
std::cout << "Remaining input: '" << std::string(first, last) << "'\n";
}
}

Spirit.X3 : using push_back_container traits with list parser

I have a class having public ctor and some add() method:
class object
{
object() {}
template <typename>
void add(T&& val) { // some adding here}
}
The main question I'm faced is how can I adopt spirit.x3 list parser to use object::add() method instead of std::vector<>::push_back ?
I was easily able to achieve what I need with simple
x3::int_ % ','
parser (live demo) using the following code :
#include <boost/spirit/home/x3.hpp>
#include <iostream>
#include <vector>
namespace x3 = boost::spirit::x3;
namespace parse_to_object
{
struct object
{
using value_type = int;
object() { std::cout << "object::object() - invoked" << std::endl; }
void add(value_type val) { _data.push_back(val); }
std::vector<value_type> _data;
};
const x3::rule<struct Test, object> r_ints("r_ints");
const auto r_ints_def = x3::int_ % ',';
BOOST_SPIRIT_DEFINE(r_ints);
}
namespace boost { namespace spirit { namespace x3 { namespace traits {
template<>
struct push_back_container<parse_to_object::object>
{
template<typename T>
static bool call(parse_to_object::object& obj, T&& val)
{
obj.add(std::move(val));
return true;
}
};
}}}}
int main()
{
const std::string text("1,2,3,4");
auto begin = std::begin(text);
const auto end = std::end(text);
parse_to_object::object result;
const auto ok = x3::phrase_parse(begin, end, parse_to_object::r_ints, x3::space, result);
std::cout << "ok = " << std::boolalpha << (ok && begin == end) << std::endl;
std::copy(result._data.begin(), result._data.end(), std::ostream_iterator<int>(std::cout, " "));
return 0;
}
But unfortunately, when I tried more compilcated example like
'{' >> x3::int_ >> ':' >> x3::int_ >> '}') % ','
I'm getting the compilation error (live demo) :
/opt/wandbox/boost-1.67.0/clang-head/include/boost/spirit/home/x3/support/traits/container_traits.hpp:102:45: error: no type named 'iterator' in 'parse_to_object::object'
: mpl::identity {};
Could somebody assist with spirit.x3 traits and give some example how to abopt custom class to be used instead of std::vector<> for list parser ?
In the end it's down to a missing include:
#include <boost/fusion/adapted/std_pair.hpp>
std::pair isn't adapted by default.
Side note: std::move should be std::forward<T> with "universal references" (or perfect forwarding)
Live On Coliru
#define BOOST_SPIRIT_X3_DEBUG
#include <boost/fusion/adapted/std_pair.hpp>
#include <boost/spirit/home/x3.hpp>
#include <iostream>
#include <vector>
namespace x3 = boost::spirit::x3;
namespace parse_to_object
{
struct object
{
using value_type = std::pair<int,int>;
object() { std::cout << "object::object() - invoked" << std::endl; }
void add(value_type val) { _data.push_back(std::move(val)); }
std::vector<std::pair<int,int>> _data;
};
const x3::rule<struct Test, object> r_ints("r_ints");
const auto r_ints_def = ('{' >> x3::int_ >> ':' >> x3::int_ >> '}') % ',';
BOOST_SPIRIT_DEFINE(r_ints)
}
namespace boost { namespace spirit { namespace x3 { namespace traits {
template<> struct push_back_container<parse_to_object::object>
{
template<typename T>
static bool call(parse_to_object::object& obj, T&& val)
{
obj.add(std::forward<T>(val));
return true;
}
};
}}}}
int main()
{
const std::string text("{1:2},{3:4}");
auto begin = std::begin(text), end = std::end(text);
parse_to_object::object result;
auto ok = phrase_parse(begin, end, parse_to_object::r_ints >> x3::eoi, x3::space, result);
std::cout << "ok = " << std::boolalpha << ok << "\n";
for (auto& p : result._data)
std::cout << "{" << p.first << ", " << p.second << "} ";
std::cout << "\n";
}
Prints
object::object() - invoked
<r_ints>
<try>{1:2},{3:4}</try>
<success></success>
<attributes></attributes>
</r_ints>
ok = true
{1, 2} {3, 4}

Boost graph Library using Bundled properties

I am new to BGL and trying to setup a simple shortest path finding program using BGL where undirected graph is defined as a adjacency List with custom defined EdgeProperty and VertexProperty. I am getting compile time error which I attribute to my insufficient skills in templates and Boost.
The code is as follows:
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/directed_graph.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include<iostream>
#include <map>
using namespace std;
using namespace boost;
enum Node_type
{
STAIR,
LEVEL,
LIFT,
OTHER,
UNKNOWN
};
class VertexProperty
{
public:
VertexProperty(){ id=-1; type=Node_type::UNKNOWN, level_id=254, stair_id=254;}
std::string toString()
{
std::string str;
str = "id "+to_string(id)+" type "+to_string(type)+" level "+to_string(level_id)+" stair_id "+to_string(stair_id);
return str;
}
int getVertexID() {return id;}
int id;
Node_type type;
int level_id;
int stair_id;
};
class EdgeProperty
{
public:
EdgeProperty(){id=-1, weight=0;}
EdgeProperty(int i, double wt){ id=i; weight=wt; }
double getWeigth(){ return weight;}
int getID() {return id;}
std::string toString()
{
std::string str;
str = "id "+to_string(id)+" weight="+to_string(weight);
return str;
}
int id;
double weight;
};
typedef boost::property<boost::edge_weight_t, double> EdgeWeigthProperty;
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS,VertexProperty, EdgeProperty> UndirectedGraph;
typedef boost::graph_traits<UndirectedGraph>::edge_iterator edge_iterator;
typedef boost::graph_traits<UndirectedGraph>::vertex_iterator vertex_iterator;
typedef boost::directed_graph <boost::no_property, EdgeWeigthProperty> DirectedGraph;
typedef boost::graph_traits<UndirectedGraph>::vertex_descriptor vertex_descriptor;
typedef boost::graph_traits<UndirectedGraph>::edge_descriptor edge_descriptor;
class A
{
public:
A();
void undirected_graph_creation();
void directed_graph_creation();
void showEdges();
void showVertices();
void run_dijkastra();
UndirectedGraph g;
DirectedGraph dg;
map<int, vertex_descriptor> map_id_vertex_desc;
map<int, edge_descriptor> map_id_edge_desc;
map<int, Node_type> map_node_id_type;
};
A::A()
{
}
void A::undirected_graph_creation()
{
UndirectedGraph::vertex_descriptor v0= boost::add_vertex(g);
map_id_vertex_desc.emplace(0,v0);
g[v0].id=0;
g[v0].type=Node_type::LEVEL;
UndirectedGraph::vertex_descriptor v1= boost::add_vertex(g);
g[v1].id=1;
g[v1].type=Node_type::STAIR;
map_id_vertex_desc.emplace(1,v1);
UndirectedGraph::vertex_descriptor v2= boost::add_vertex(g);
map_id_vertex_desc.emplace(2,v2);
g[v2].id=2;
g[v2].type=Node_type::STAIR;
UndirectedGraph::vertex_descriptor v3= boost::add_vertex(g);
map_id_vertex_desc.emplace(3,v3);
g[v3].id=3;
g[v3].type=Node_type::STAIR;
/*EdgeWeigthProperty w8(8);
EdgeWeigthProperty w18(18);
EdgeWeigthProperty w20(20);
EdgeWeigthProperty w2(2);
EdgeWeigthProperty w7(7);*/
EdgeProperty w8(1,8);
EdgeProperty w18(2,18);
EdgeProperty w20(3,20);
EdgeProperty w2(4,2);
EdgeProperty w7(5,7);
boost::add_edge(v0,v1,w8,g);
boost::add_edge(v0,v3,w18,g);
boost::add_edge(v1,v2,w20,g);
boost::add_edge(v2,v3,w2,g);
boost::add_edge(v1,v3,w7,g);
}
void A::showEdges()
{
std::pair<edge_iterator,edge_iterator> ei=edges(g);
std::cout<<" number of edges "<<num_edges(g)<<endl;
std::cout<<" Edge list ";
for(edge_iterator it= ei.first; it!=ei.second; ++it)
cout<<*it<<" "<<g[*it].toString()<<endl;
}
void A::showVertices()
{
std::pair<vertex_iterator, vertex_iterator> vi= boost::vertices(g);
std::cout<<" Number of vertices are "<<endl;
for( vertex_iterator i = vi.first; i!=vi.second; ++i)
{
cout<<*i<<" id="<<g[*i].toString()<<" type="<<g[*i].type<<endl;
}
}
void A::run_dijkastra()
{
std::vector<vertex_descriptor> predecessors(boost::num_vertices(g));
std::vector<EdgeProperty> distances(boost::num_vertices(g));
std::pair<vertex_iterator,vertex_iterator> vi=boost::vertices(g);
vertex_descriptor first_vertex_descriptor = *(vi.first);
vertex_descriptor start_vertex = boost::vertex(0,g);
// boost::dijkstra_shortest_paths(g, first_vertex_descriptor,
// boost::weight_map(get(&EdgeProperty::weight,g))
// .distance_map(boost::make_iterator_property_map(distances.begin(), get(boost::vertex_index, g))) );
dijkstra_shortest_paths(g, first_vertex_descriptor,
predecessor_map(make_iterator_property_map(predecessors.begin(), get(vertex_index,g))).distance_map(make_iterator_property_map(distances.begin(), get(boost::vertex_index,g))).weight_map(get(&EdgeProperty::weight,g));
/*
std::cout << "distances and parents:" << std::endl;
graph_traits < UndirectedGraph >::vertex_iterator vi1, vend1;
for (boost::tie(vi1, vend1) = vertices(g); vi1 != vend1; ++vi1) {
std::cout << "distance(" << g[*vi1].id << ") = " << distances[*vi1].toString() << ", ";
std::cout << "parent(" << g[*vi1].id << ") = " << g[predecessors[*vi1]].id << std::
endl;
}*/
}
void A::directed_graph_creation()
{
//todo
}
int main()
{
A a_graph;
a_graph.undirected_graph_creation();
a_graph.showEdges();
a_graph.showVertices();;
a_graph.run_dijkastra();
return 0;
}
Error is unknown conversion from double to EdgeProperty. It appear that I have mistake in syntax of call to dijkstra_shortest_paths functions.
I will also like to replace data member of EdgeProperty with a function.
Other query I have is about maintaining an index to nodes via vertex descriptor. At present, I am using VertexProperty::id do maintain a dictionary to object of VertexProperty type. Do Boost maintains internally any dictionary which I can use of.
I am using gcc5.4 version compiler on Ubuntu 16.04
Thank you
Nitin
#llonesmiz was right on the mark. Here's a general cleanup of the code and a live demo.
I also used make_transform_value_property_map to use getWeight() and made all data members private.
NOTE I suspect that the std::map instances aren't really useful anymore now that you use bundled properties (?). In any case, you could drop some code if you don't need them any more: Shorter Demo
NOTE You might not know about print_graph. Even Shorter, slightly abbreviated output
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/property_map/transform_value_property_map.hpp>
#include <iostream>
#include <map>
enum class Node_type { STAIR, LEVEL, LIFT, OTHER, UNKNOWN };
static std::ostream& operator<<(std::ostream& os, Node_type type) {
switch(type) {
case Node_type::STAIR: return os << "STAIR";
case Node_type::LEVEL: return os << "LEVEL";
case Node_type::LIFT: return os << "LIFT";
case Node_type::OTHER: return os << "OTHER";
default:
case Node_type::UNKNOWN: return os << "UNKNOWN";
}
}
class VertexProperty {
public:
VertexProperty(int id = -1, Node_type type = Node_type::UNKNOWN, int level_id=254, int stair_id=254)
: id(id), type(type), level_id(level_id), stair_id(stair_id) { }
std::string toString() const {
std::ostringstream oss;
oss << *this;
return oss.str();
}
int getVertexID() { return id; }
private:
friend std::ostream& operator<<(std::ostream& os, VertexProperty const& v) {
return os << "id " << v.id << " type " << v.type << " level " << v.level_id << " stair_id " << v.stair_id;
}
int id;
Node_type type;
int level_id;
int stair_id;
};
class EdgeProperty {
public:
EdgeProperty(int i = -1, double wt = 0) : id(i), weight(wt) {
id = i;
weight = wt;
}
double getWeight() { return weight; }
int getID() { return id; }
std::string toString() const {
std::ostringstream oss;
oss << *this;
return oss.str();
}
private:
friend std::ostream& operator<<(std::ostream& os, EdgeProperty const& e) {
return os << "id " << e.id << " weight=" << std::fixed << e.weight;
}
int id;
double weight;
};
class A {
public:
void undirected_graph_creation();
void showEdges();
void showVertices();
void runDijstra();
private:
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, VertexProperty, EdgeProperty> UndirectedGraph;
UndirectedGraph g;
using edge_iterator = UndirectedGraph::edge_iterator;
using vertex_iterator = UndirectedGraph::vertex_iterator;
using vertex_descriptor = UndirectedGraph::vertex_descriptor;
using edge_descriptor = UndirectedGraph::edge_descriptor;
std::map<int, vertex_descriptor> map_id_vertex_desc;
std::map<int, edge_descriptor> map_id_edge_desc;
std::map<int, Node_type> map_node_id_type;
};
void A::undirected_graph_creation() {
auto add_vertex = [this](int id, Node_type type) {
// TODO: these maps are probably not required anymore
map_node_id_type[id] = type;
vertex_descriptor vd = boost::add_vertex(VertexProperty{id, type}, g);
return map_id_vertex_desc[id] = vd;
};
auto v0 = add_vertex(0, Node_type::LEVEL);
auto v1 = add_vertex(1, Node_type::STAIR);
auto v2 = add_vertex(2, Node_type::STAIR);
auto v3 = add_vertex(3, Node_type::STAIR);
auto add_edge = [this](vertex_descriptor u, vertex_descriptor v, EdgeProperty prop) {
auto ins = boost::add_edge(u, v, prop, g);
if (ins.second)
map_id_edge_desc[prop.getID()] = ins.first;
return ins.first;
};
add_edge(v0, v1, {1, 8});
add_edge(v0, v3, {2, 18});
add_edge(v1, v2, {3, 20});
add_edge(v2, v3, {4, 2});
add_edge(v1, v3, {5, 7});
}
void A::showEdges() {
for (auto e : boost::make_iterator_range(boost::edges(g)))
std::cout << "Edge " << e << " " << g[e] << "\n";
}
void A::showVertices() {
for (auto v : boost::make_iterator_range(boost::vertices(g)))
std::cout << "Vertex " << v << " " << g[v] << "\n";
}
void A::runDijstra() {
std::vector<vertex_descriptor> predecessors(num_vertices(g));
std::vector<double> distances(num_vertices(g));
vertex_descriptor start = *(vertices(g).first);
auto v_index = get(boost::vertex_index, g);
auto weight = boost::make_transform_value_property_map(std::mem_fn(&EdgeProperty::getWeight), get(boost::edge_bundle, g));
dijkstra_shortest_paths(
g, start,
predecessor_map(make_iterator_property_map(predecessors.begin(), v_index))
.distance_map(make_iterator_property_map(distances.begin(), v_index))
.weight_map(weight));
std::cout << "distances and parents:\n";
for (auto v : boost::make_iterator_range(boost::vertices(g))) {
auto id = g[v].getVertexID();
std::cout << "distance(" << id << ") = " << distances[v] << ", ";
std::cout << "parent(" << id << ") = " << g[predecessors[v]] << "\n";
}
}
int main() {
A a;
a.undirected_graph_creation();
a.showEdges();
a.showVertices();
a.runDijstra();
}
Prints:
Edge (0,1) id 1 weight=8.000000
Edge (0,3) id 2 weight=18.000000
Edge (1,2) id 3 weight=20.000000
Edge (2,3) id 4 weight=2.000000
Edge (1,3) id 5 weight=7.000000
Vertex 0 id 0 type LEVEL level 254 stair_id 254
Vertex 1 id 1 type STAIR level 254 stair_id 254
Vertex 2 id 2 type STAIR level 254 stair_id 254
Vertex 3 id 3 type STAIR level 254 stair_id 254
distances and parents:
distance(0) = 0.000000, parent(0) = id 0 type LEVEL level 254 stair_id 254
distance(1) = 8.000000, parent(1) = id 0 type LEVEL level 254 stair_id 254
distance(2) = 17.000000, parent(2) = id 3 type STAIR level 254 stair_id 254
distance(3) = 15.000000, parent(3) = id 1 type STAIR level 254 stair_id 254

boost phoenix bind a semantic action with multiple parameter and a return value

I'm new to both C++ and Boost spirit.
I'm stuck on this for a day now.
I want to parse two strings separated by a dot.
basically, I need following strings to be parsed to an integer.
eg: [field] --> integer // working
eg2: [instance.field] --> integer // not working
For the 2nd one, I need to take two strings as parameters and evaluate them and return the relevant integer value.
I must have missed a basic thing, but I can't figure it out.
Please let me know the error in my code or a better way to do it.
calling a method and getting the value is needed. I can't change that.
this is the code peace.
#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/support_utree.hpp>
#include <iostream>
#include <string>
namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
namespace spirit = boost::spirit;
namespace phx = boost::phoenix;
int fieldVal(std::vector<char> fieldName) {
std::string fieldValue(fieldName.begin(), fieldName.end());
std::cout << "Field Name Recieved.::: " << fieldValue << std::endl;
int i = 50; //just for test
return i;
}
int instanceFieldVal(std::string instance, std::string fieldName) {
std::cout << "Recieved ::: " << instance <<" : "<< fieldName << std::endl;
int i = 60; //just for test
return i;
}
namespace client
{
template <typename Iterator>
struct calculator : qi::grammar<Iterator, ascii::space_type, int()>
{
calculator() : calculator::base_type(instanceFieldValue)
/*change base type to "field" and comment everything relevant to
"instanceFieldValue", then it's working */
{
using qi::int_;
using qi::_val;
using qi::_1;
using qi::char_;
using qi::lexeme;
field = lexeme[
'['
>> +(~char_(".]["))
>> ']'
][qi::_val = phx::bind(&fieldVal, qi::_1)]; // this is working
instanceField = '['
>> +(~char_(".]["))
>> '.'
>> +(~char_(".]["))
>> ']';
instanceFieldValue
= instanceField[qi::_val = phx::bind(&instanceFieldVal, qi::_1)];
// how ^this line should be changed??
}
qi::rule<Iterator, ascii::space_type, int()> field, instanceFieldValue;
qi::rule<Iterator, ascii::space_type, std::string(), std::string()>instanceField;
};
}
int main()
{
std::cout << "Type an expression...or [q or Q] to quit\n\n";
using boost::spirit::ascii::space;
using boost::spirit::utree;
typedef std::string::const_iterator iterator_type;
typedef client::calculator<iterator_type> calculator;
calculator calc; // Our grammar
std::string str;
while (std::getline(std::cin, str))
{
if (str.empty() || str[0] == 'q' || str[0] == 'Q')
break;
std::string::const_iterator iter = str.begin();
std::string::const_iterator end = str.end();
int val;
bool r = phrase_parse(iter, end, calc, space, val);
if (r && iter == end)
{
std::cout << "-------------------------\n";
std::cout << "Parsing succeeded: " << val << "\n";
std::cout << "-------------------------\n";
}
else
{
std::string rest(iter, end);
std::cout << "-------------------------\n";
std::cout << "Parsing failed\n";
std::cout << "stopped at: \": " << rest << "\"\n";
std::cout << "-------------------------\n";
}
}
std::cout << "Bye... :-) \n\n";
return 0;
}
Well, you say it's a function with multiple arguments, but then you only pass one arg: _1.
How can that work? It can't. It's also completely what you even want to pass, because the second parameter is likely from the instanceField expression, but the first is ... magical context.
Like always, I'd try to minimize state and semantic actions. In fact, I'd suggest separation of concerns:
parse to std:vector<std::string> first
on successful parse, evaluate it
That would lead to a grammar like
template <typename Iterator>
struct path : qi::grammar<Iterator, std::deque<std::string>()> {
path() : path::base_type(start) {
using namespace qi;
name = +(graph - char_(".][")); // not matching spaces please
qualifiedName = name % '.';
start = skip(ascii::space) ['[' >> qualifiedName >> ']'];
BOOST_SPIRIT_DEBUG_NODES((start)(qualifiedName)(name))
}
private:
qi::rule<Iterator, std::deque<std::string>(), qi::ascii::space_type> qualifiedName;
qi::rule<Iterator, std::string()> name;
qi::rule<Iterator, std::deque<std::string>()> start;
};
You can of course combine it using a semantic action if you insist:
template <typename Iterator>
struct property : qi::grammar<Iterator, int()> {
property() : property::base_type(start) {
using namespace qi;
name = +(graph - char_(".][")); // not matching spaces please
qualifiedName = name % '.';
start = skip(ascii::space) ['[' >> qualifiedName >> ']']
[_val = phx::bind(lookup, phx::cref(sample), _1) ]
;
BOOST_SPIRIT_DEBUG_NODES((start)(qualifiedName)(name))
}
private:
qi::rule<Iterator, std::deque<std::string>(), qi::ascii::space_type> qualifiedName;
qi::rule<Iterator, std::string()> name;
qi::rule<Iterator, int()> start;
};
Now, just implement a function
int lookup(Node const& context, std::deque<std::string> path);
As an example, let's imagine a sample context:
using Leaf = int;
using Node = boost::make_recursive_variant< Leaf, std::map<std::string, boost::recursive_variant_> >::type;
using SubTree = std::map<std::string, Node>;
static Node sample = SubTree {
{ "simple", 100 },
{ "compound", SubTree {
{ "first", 200 },
{ "second", 300 },
}, },
{ "deep", SubTree {
{ "nested", SubTree {
{ "compound", SubTree {
{ "buried", 400 },
{ "secrets", 500 },
}, }, }, }, }, }
};
Now, an implementation of lookup could be as simple as:
int lookup(Node const& context, std::deque<std::string> path) {
if (path.empty())
return boost::get<Leaf>(context);
auto& sub = boost::get<SubTree>(context);
auto element = path.front();
path.pop_front();
try {
return lookup(sub.at(element), std::move(path));
} catch(std::out_of_range const& e) {
throw std::runtime_error("'" + element + "' not found");
}
}
Full Demo
Live On Coliru
//#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <iostream>
#include <map>
namespace qi = boost::spirit::qi;
namespace phx = boost::phoenix;
using Leaf = int;
using Node = boost::make_recursive_variant< Leaf, std::map<std::string, boost::recursive_variant_> >::type;
using SubTree = std::map<std::string, Node>;
static Node sample = SubTree {
{ "simple", 100 },
{ "compound", SubTree {
{ "first", 200 },
{ "second", 300 },
}, },
{ "deep", SubTree {
{ "nested", SubTree {
{ "compound", SubTree {
{ "buried", 400 },
{ "secrets", 500 },
},
},
},
},
},
}
};
int lookup(Node const& context, std::deque<std::string> path) {
if (path.empty())
return boost::get<Leaf>(context);
auto& sub = boost::get<SubTree>(context);
auto element = path.front();
path.pop_front();
try {
return lookup(sub.at(element), std::move(path));
} catch(std::out_of_range const& e) {
throw std::runtime_error("'" + element + "' not found");
}
}
namespace parser {
template <typename Iterator>
struct property : qi::grammar<Iterator, int()> {
property() : property::base_type(start) {
using namespace qi;
name = +(graph - char_(".][")); // not matching spaces please
qualifiedName = name % '.';
start = skip(ascii::space) ['[' >> qualifiedName >> ']']
[_val = phx::bind(lookup, phx::cref(sample), _1) ]
;
BOOST_SPIRIT_DEBUG_NODES((start)(qualifiedName)(name))
}
private:
qi::rule<Iterator, std::deque<std::string>(), qi::ascii::space_type> qualifiedName;
qi::rule<Iterator, std::string()> name;
qi::rule<Iterator, int()> start;
};
}
int main() {
using It = std::string::const_iterator;
parser::property<It> calc;
for (std::string const str : {
"[simple]",
"[compound.first]",
"[compound.second]",
"[deep.nested.compound.buried]",
"[deep.nested.compound.secrets]",
// whitespace is ok
" [ compound.\tfirst ]",
// failing:
"[]",
"[missing]",
"[deep.missing.compound.buried]",
// whitespace not ok inside names
" [ compound.\tfi rst ]",
})
try {
std::cout << " ===== Input: '" << str << "'\n";
It iter = str.begin(), end = str.end();
int val;
bool r = parse(iter, end, calc, val);
if (r) {
std::cout << "Parsing succeeded: " << val << "\n";
} else {
std::cout << "Parsing failed\n";
}
if (iter != end) {
std::cout << " - Remaining unparsed input: '" << std::string(iter, end) << "'\n";
}
} catch(std::exception const& e) {
std::cout << "Exception: " << e.what() << "\n";
}
}
Prints:
===== Input: '[simple]'
Parsing succeeded: 100
===== Input: '[compound.first]'
Parsing succeeded: 200
===== Input: '[compound.second]'
Parsing succeeded: 300
===== Input: '[deep.nested.compound.buried]'
Parsing succeeded: 400
===== Input: '[deep.nested.compound.secrets]'
Parsing succeeded: 500
===== Input: ' [ compound. first ]'
Parsing succeeded: 200
===== Input: '[]'
Parsing failed
- Remaining unparsed input: '[]'
===== Input: '[missing]'
Exception: 'missing' not found
===== Input: '[deep.missing.compound.buried]'
Exception: 'missing' not found
===== Input: ' [ compound. fi rst ]'
Parsing failed
- Remaining unparsed input: ' [ compound. fi rst ]'

Resources