Search code examples
boost-spiritboost-spirit-qi

How do I use a class with only one attribute in a AST with Boost Spirit?


I want to parse a file into an AST using Boost Spirit.

The root of my AST is a class with only one attribute :

typedef boost::variant<FunctionDeclaration, GlobalVariableDeclaration> FirstLevelBlock;

struct Program {
    std::vector<FirstLevelBlock> blocks;
};

BOOST_FUSION_ADAPT_STRUCT(
    ::Program,
    (std::vector<eddic::FirstLevelBlock>, blocks)
)

If I parse using a single rule :

program %= *(function | globalDeclaration);

it doesn't compiles, but if I add a single string name to Program, it works well. I could use the vector as the root, but I want to use the class, because I want to add some methods to the Program class.

EDIT :

If I surround my program with braces, it works well :

program %= lexer.left_brace >> *(function | globalDeclaration) >> lexer.right_brace;

compiles and works fine, but :

program %= *(function | globalDeclaration);

does not compile...

Is there something in Boost Spirit that prevent using such simple rules ?


Solution

  • Edited question version 2

    If I surround my program with braces, it works well [...], but program %= *(function | globalDeclaration); does not compile...

    Is there something in Boost Spirit that prevent using such simple rules ?

    Firstly, we can't really tell without the defintion for function and globalDeclaration.

    Secondly I tried, changing my PoC lines to

    static const qi::rule<It, Program(), space_type> program  = *(function | global);
    Program d = test("void test(); int abc; int xyz; void last();" , program); 
    

    Lo and behold, I get your compiler error! Now I would certainly agree that this looks very much like an attribute conversion bug. Also, Here is a preliminary workaround:

    program %= eps >> *(function | global);
    

    As you can see, qi::eps to the rescue


    Answer to original question version 1

    Mmm. I think you need to post a minimal working sample. Here is a proof of concept starting from your question, and it all works rather nicely.

    Note that I compiled with g++ -std=c++0x in order to get the default Attr parameter argument on the test function.

    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/karma.hpp>
    #include <boost/fusion/adapted.hpp>
    #include <boost/strong_typedef.hpp>
    
    // added missing bits
    namespace eddic 
    {
        typedef std::string FunctionDeclaration;
        typedef std::string GlobalVariableDeclaration;
    
        typedef boost::variant<FunctionDeclaration, GlobalVariableDeclaration> FirstLevelBlock;
    }
    
    using namespace eddic;
    // end missing bits
    
    struct Program {
        std::vector<FirstLevelBlock> blocks;
    };
    
    BOOST_FUSION_ADAPT_STRUCT(
        ::Program,
        (std::vector<eddic::FirstLevelBlock>, blocks)
    )
    
    namespace /*anon*/
    {    
        using namespace boost::spirit::karma;
    
        struct dumpvariant : boost::static_visitor<std::ostream&>
        {
            dumpvariant(std::ostream& os) : _os(os) {}
            template <typename T> std::ostream& operator ()(const T& t) const
                { return _os << format(stream, t); }
    
          private: std::ostream& _os;
        };
    
        std::ostream& operator<<(std::ostream& os, const FirstLevelBlock& block)
        { 
            os << "variant[" << block.which() << ", ";
            boost::apply_visitor(dumpvariant(os), block);
            return os << "]";
        }
    
        std::ostream& operator<<(std::ostream& os, const std::vector<FirstLevelBlock>& blocks)
        { return os << format(-(stream % eol), blocks); }
    
        std::ostream& operator<<(std::ostream& os, const Program& program)
        { return os << "BEGIN\n" << program.blocks << "\nEND"; }
    }
    
    namespace qi = boost::spirit::qi;
    
    template <typename Rule, typename Attr = typename Rule::attr_type>
        Attr test(const std::string& input, const Rule& rule)
    {
        typedef std::string::const_iterator It;
        It f(input.begin()), l(input.end());
    
        Attr result;
        try
        {
            bool ok = qi::phrase_parse(f, l, rule, qi::space, result);
            if (!ok)
                std::cerr << " -- ERR: parse failed" << std::endl;
        } catch(qi::expectation_failure<It>& e)
        {
            std::cerr << " -- ERR: expectation failure at '" << std::string(e.first, e.last) << "'" << std::endl;
        }
        if (f!=l)
            std::cerr << " -- WARN: remaing input '" << std::string(f,l) << "'" << std::endl;
        return result;
    }
    
    int main()
    {
        typedef std::string::const_iterator It;
        static const qi::rule<It, FunctionDeclaration(), space_type>        function = "void " > +~qi::char_("()") > "();";
        static const qi::rule<It, GlobalVariableDeclaration(), space_type>  global   = "int "  > +~qi::char_(";")  > ";";
        static const qi::rule<It, FirstLevelBlock(), space_type>            block    = function | global;
        static const qi::rule<It, Program(), space_type>                    program  = '{' >> *(function | global) >> '}';
    
        FunctionDeclaration       a = test("void test();", function); 
        std::cout << "FunctionDeclaration a         : " << a << std::endl;
    
        GlobalVariableDeclaration b = test("int abc;", global); 
        std::cout << "GlobalVariableDeclaration b   : " << b << std::endl;
    
        FirstLevelBlock c = test("void more();", block); 
        std::cout << "FirstLevelBlock c             : " << c << std::endl;
    
        /*FirstLevelBlock*/ c = test("int bcd;", block); 
        std::cout << "FirstLevelBlock c             : " << c << std::endl;
    
        Program d = test("{"
                "void test();"
                "int abc"
                ";"
                "int xyz; void last();"
                "}", program); 
        std::cout << "Program d                     : " << d << std::endl;
    }
    

    Output:

    FunctionDeclaration a         : test
    GlobalVariableDeclaration b   : abc
    FirstLevelBlock c             : variant[1, more]
    FirstLevelBlock c             : variant[1, bcd]
    Program d                     : BEGIN
    test
    abc
    xyz
    last
    END