I am using Boost.Spirit Qi to construct rather complex structure from some text data. The data structure may be recursively defined, so I need two of my grammars to reference each other, and that is where problems emerge.
For example, I have a grammar:
element = line | text | circle | box | composite_element
composite_element = 'C', int, int, '[', +element, ']'
Obviously, I need something like that:
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/adapt_struct.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 <tuple>
#include <boost/fusion/adapted/std_pair.hpp>
#include <boost/fusion/include/std_pair.hpp>
#include <boost/spirit/include/qi_eol.hpp>
#include <boost/phoenix.hpp>
#include <vector>
#include <string>
namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
namespace phoenix = boost::phoenix;
struct line {
int x1;
int y1;
int x2;
int y2;
int color;
int width;
int capstyle;
int dashstyle;
int dashlength;
int dashspace;
};
struct box {
int x;
int y;
int width;
int height;
int color;
int line_width;
int capstyle;
int dashstyle;
int dashlength;
int dashspace;
int filltype;
int fillwidth;
int angle1;
int pitch1;
int angle2;
int pitch2;
};
struct circle {
int x;
int y;
int radius;
int color;
int line_width;
int capstyle;
int dashstyle;
int dashlength;
};
struct text {
int x;
int y;
int color;
int size;
int visibility;
int show_name_value;
int angle;
int alignment;
int num_lines;
std::vector<std::string> lines;
};
struct composite_component;
using element_t = boost::variant<line, box, circle, text, boost::recursive_wrapper<composite_component>>;
struct composite_component {
int x;
int y;
std::string basename;
// only used if component is embedded
// i. e. stores its definition within the schematic file
std::vector<element_t> elements;
};
struct element {
// some other fields
// ...
element_t element;
};
struct document {
std::vector<element> elements;
};
BOOST_FUSION_ADAPT_STRUCT(line, x1, y1, x2, y2, color, width, capstyle, dashstyle, dashlength, dashspace)
BOOST_FUSION_ADAPT_STRUCT(box, x, y, width, height, color, line_width, capstyle, dashstyle, dashlength, dashspace, filltype, fillwidth, angle1, pitch1, angle2, pitch2)
BOOST_FUSION_ADAPT_STRUCT(circle, x, y, radius, color, line_width, capstyle, dashstyle, dashlength)
BOOST_FUSION_ADAPT_STRUCT(text, x, y, color, size, visibility, show_name_value, angle, alignment, num_lines, lines)
BOOST_FUSION_ADAPT_STRUCT(composite_component, x, y, basename, elements)
BOOST_FUSION_ADAPT_STRUCT(element, element)
BOOST_FUSION_ADAPT_STRUCT(document, elements)
template <typename Iterator, typename Attribute>
using rule = qi::rule<Iterator, Attribute, qi::blank_type>;
template <typename Iterator>
class composite_element_parser;
template <typename Iterator>
class element_parser : public qi::grammar<Iterator, element(), qi::blank_type> {
public:
element_parser(): element_parser::base_type{start_rule_}
{
using qi::int_;
using qi::repeat;
using phoenix::val;
using phoenix::construct;
/* other definitions except of the 'line' is omitted in sake of simplicity */
line_ = 'L' >> int_ >> int_ >> int_ >> int_ >> int_ >>
int_ >> int_ >> int_ >> int_ >> int_ >> qi::eol;
// box = ...
// circle = ...
// text = ...
start_rule_ = (line_ /* || embedded_component_ */) >> qi::eoi;
}
private:
rule<Iterator, element()> start_rule_;
rule<Iterator, line()> line_;
// here comes the problem - CIRCULAR REFERENCE to incompletely defined template
// composite_element_parser<Iterator> embedded_component_;
};
template <typename Iterator>
class composite_element_parser : public qi::grammar<Iterator, composite_component(), qi::blank_type> {
public:
composite_element_parser() : composite_element_parser::base_type{start_rule_}
{
using phoenix::at_c;
using qi::int_;
using phoenix::push_back;
start_rule_ = "C" >> int_ >> int_ >> qi::lexeme[(qi::char_)[at_c<2>(qi::_val) += qi::_1]]
>> -(
"[" >>
*(element_) [push_back(at_c<3>(qi::_val), qi::_1)] >>
"]"
);
}
private:
rule<Iterator, composite_component()> start_rule_;
element_parser<Iterator> element_;
};
template <typename Iterator>
class document_parser : public qi::grammar<Iterator, document(), qi::blank_type> {
public:
document_parser() : document_parser::base_type{start_rule_}
{
using phoenix::at_c;
using phoenix::push_back;
using qi::_val;
using qi::_0;
using qi::_1;
start_rule_ = +(element_)[push_back(at_c<0>(_val), _1)] >> qi::eoi;
}
private:
rule<Iterator, document()> start_rule_;
element_parser<Iterator> element_;
};
int main(int , char **) {
document_parser<std::string::const_iterator> parser;
document doc;
const std::string text = "v 20180904 2\n"
"L 1 2 3 4 5 6 7 8 9 10\n"
"C 10 10 FOO\n"
"[ "
"L 1 2 3 4 5 6 7 8 9 10\n"
"]\n";
bool r = qi::phrase_parse(text.cbegin(), text.cend(), parser, qi::blank, doc);
std::cout << (r ? "OK" : "FAIL") << std::endl;
return 0;
}
Definitions of rules for 'text', 'circle' and 'box' are omitted, though. Note the comment in private section of element_parser
definition - compiler will be unable to instantiate an incomplete class template composite_element_parser<Iterator>
. What am I supposed to do with that? Obviously, I can't have element_parser
and composite_element_parser
as members of a top-level grammar (document_parser
in my case) and pass them references/pointers to each other in the constructors initializer list, because they are uninitialized at the moment.
UPDATE: this thread may be likely recognized as a duplicate of Deeply-recursive qi grammars (parsers) with synthesized and inherited attributes, but I really can't grasp the approved answer.