I am trying my hand at my first spirit parser and it seems like I have run into an attribute propagation issue. Trying a plantuml grammar. Damned if I hack it, damned if I do not...
The full gist is here: https://coliru.stacked-crooked.com/a/023ec430b509e1be
The essence of it, I think it this:
using ast_node = boost::variant<
ast_null,
ast_transition,
boost::recursive_wrapper<ast_state>,
boost::recursive_wrapper<ast_region>,
ast_machine
>;
using ast_nodes_t = std::vector<ast_node>;
struct ast_transition
{
//std::string _id;
std::string _fromState;
std::string _toState;
std::string _event;
std::string _guard;
std::string _effect;
};
BOOST_FUSION_ADAPT_STRUCT(
ast_transition,
//(std::string, _id)
(std::string, _fromState)
(std::string, _toState)
(std::string, _event)
(std::string, _guard)
(std::string, _effect)
)
struct ast_region
{
ast_nodes_t _subtree;
};
BOOST_FUSION_ADAPT_STRUCT(
ast_region,
(ast_nodes_t, _subtree)
)
// possible "fix" for "error: no type named 'value_type' in 'struct ast_region'" but causes infinite matching loop on region(s)
//region = eps >> *transition
region = *transition
;
bs::qi::rule<ITER, ast_transition(), SKIPPER> transition;
bs::qi::rule<ITER, ast_region(), SKIPPER> region;
bs::qi::rule<ITER, ast_nodes_t(), SKIPPER> regions;
Which gives:
/usr/local/include/boost/spirit/home/support/container.hpp:130:12: error: no type named 'value_type' in 'struct ast_region'
Any ideas how to massage the container attribute would be appreciated, thanks!
What I am trying to model from a plantuml state machine description:
I can only assume you have been leaving out code to account for the location tracking bases and iterator passing (I hope you're using on_sucess
, not just on_error
).
I simplified/reshaped things in order to review. In the end I think the only real change required seems to be the one you already had commented:
regions = qi::eps >> *region;
For rationale, see
Now the infinite loop indicates that region
matches the empty input. Since transition requires at least rstring
which requires at least a single character, you should be able to fix this by requiring at least one transition:
region = qi::eps >> +transition;
I ended up with this example, which also fixes the debug-output in the most energy-efficient way I know how to:
#define BOOST_SPIRIT_DEBUG 1
#include <boost/fusion/include/adapted.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/support_line_pos_iterator.hpp>
#include <fstream>
#include <iomanip>
namespace bs = boost::spirit;
namespace bp = boost::phoenix;
namespace Ast {
/*
- a state machine is a collection of regions
- a region is a collection of states & transitions
- a state is a collection of regions & transitions
*/
struct state;
struct region;
struct machine;
struct location {
size_t _line{0};
size_t _col{0};
std::string _file;
};
using node_base = location;
// struct node_base : location {};
struct null : location {};
struct transition : location {
std::string /*_id,*/ _fromState, _toState, _event, _guard, _effect;
};
using node = boost::variant< //
null, transition, //
boost::recursive_wrapper<state>, //
boost::recursive_wrapper<region>, //
boost::recursive_wrapper<machine>>;
using nodes_t = std::vector<node>;
struct state : location {
nodes_t _subtree;
};
struct region : location {
nodes_t _subtree;
};
struct machine : location {
nodes_t _subtree;
};
using boost::fusion::operator<<;
} // namespace Ast
BOOST_FUSION_ADAPT_STRUCT(Ast::null) // or provide operator<<...
BOOST_FUSION_ADAPT_STRUCT(Ast::machine, _subtree)
BOOST_FUSION_ADAPT_STRUCT(Ast::region, _subtree)
BOOST_FUSION_ADAPT_STRUCT(Ast::state, _subtree)
BOOST_FUSION_ADAPT_STRUCT(Ast::transition, /*_id,*/ _fromState, _toState, _event, _guard, _effect)
BOOST_FUSION_ADAPT_STRUCT(Ast::node_base, _line, _col, _file)
namespace plantuml {
namespace qi = bs::qi;
namespace encoding = qi::ascii;
template <typename It> struct skipper final : qi::grammar<It> {
skipper() : skipper::base_type(rule) {}
qi::rule<It> const rule = encoding::space | ("#" >> *~encoding::char_("\n") >> -qi::eol) |
("//" >> *~encoding::char_("\n") >> -qi::eol) | ("/*" >> *(encoding::char_ - "*/") >> "*/");
};
template <typename ITER, typename SKIPPER>
struct plantuml_grammar final
: qi::grammar<ITER, Ast::machine(), qi::locals<std::string>, SKIPPER /*bs::ascii::space_type*/> {
plantuml_grammar(ITER first) : plantuml_grammar::base_type(start) {
using qi::fail;
using qi::on_error;
using namespace qi::labels; // _a, _1, _val, _pass ...
qstring %= qi::lexeme['"' >> +(qi::char_ - '"') >> '"'];
rstring %= qi::raw[qi::lexeme[+qi::char_("a-zA-Z0-9_")]];
transition %= rstring >> qi::lit("-->") >> rstring;
region = qi::eps >> +transition;
regions = *region;
start = qi::lit("@startuml") //[ bp::push_back(bp::ref(_val._subtree), Ast::region()) ]
>> regions >> qi::lit("@enduml");
BOOST_SPIRIT_DEBUG_NODES((start)(regions)(region)(transition))
}
qi::rule<ITER, std::string()> qstring;
qi::rule<ITER, std::string()> rstring;
qi::rule<ITER, Ast::transition(), SKIPPER> transition;
qi::rule<ITER, Ast::region(), SKIPPER> region;
qi::rule<ITER, Ast::nodes_t(), SKIPPER> regions;
qi::rule<ITER, Ast::machine(), qi::locals<std::string>, SKIPPER> start;
}; // plantuml_grammar
bool plantuml_parser(std::istream& in) {
using base_iter_t = bs::istream_iterator;
using lp_iter_t = bs::line_pos_iterator<base_iter_t>;
using in_iter_t = lp_iter_t; // base_iter_t; //
using skipper_t = skipper<in_iter_t>; //
using plantuml_grammar_t = plantuml_grammar<in_iter_t, skipper_t>;
in_iter_t crtIt(base_iter_t(in >> std::noskipws));
in_iter_t firstIt(crtIt);
in_iter_t endIt;
plantuml_grammar_t grammar(firstIt);
Ast::node ast;
skipper_t skip = {};
bool match = qi::phrase_parse(crtIt, endIt, grammar,
skip, // bs::ascii::space,
ast);
return match;
}
} // namespace plantuml
int main() {
std::string const test = R"--(
@startuml
#xDeploy -down-> xOperation
Deploy --> Operation
/*
state Operation {
[*] -down-> Operation_Launch
Operation_Launch -down-> Operation_Auto_Monitor
--
[*] -down-> Operation_BizData_Collection
Operation_BizData_Collection -down-> Operation_BizData_Anylasis
--
[*] -down-> Operation_Next_Preparation
}
Operation -down-> [*]
*/
@enduml
)--";
// bool ret = plantuml::plantuml_parser(test);
std::istringstream in(test);
bool ret = plantuml::plantuml_parser(in);
}
Printing
<start>
<try>\n @startuml\n </try>
<regions>
<try>\n \n #x</try>
<region>
<try>\n \n #x</try>
<transition>
<try>Deploy --> Operation</try>
<success>\n \n /*</success>
<attributes>[[[D, e, p, l, o, y], [O, p, e, r, a, t, i, o, n], [], [], []]]</attributes>
</transition>
<transition>
<try>\n \n /*</try>
<fail/>
</transition>
<success>\n \n /*</success>
<attributes>[[[[[D, e, p, l, o, y], [O, p, e, r, a, t, i, o, n], [], [], []]]]]</attributes>
</region>
<region>
<try>\n \n /*</try>
<transition>
<try>@enduml\n </try>
<fail/>
</transition>
<fail/>
</region>
<success>\n \n /*</success>
<attributes>[[[[[[D, e, p, l, o, y], [O, p, e, r, a, t, i, o, n], [], [], []]]]]]</attributes>
</regions>
<success>\n </success>
<attributes>[[[[[[[D, e, p, l, o, y], [O, p, e, r, a, t, i, o, n], [], [], []]]]]]]</attributes><locals>()</locals>
</start>