c++boost-spirit

boost::spirit: : error: no type named 'value_type' in 'struct xxx'


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:


Solution

  • 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:

    Live On Compiler Explorer

    #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>