How to move results parsed results into a struct using spirit x3

橙三吉。 提交于 2021-01-29 03:50:37

问题


I want to parse an input buffer containing function prototypes into a vector of function prototypes. I have created a function prototype struct that has 3 members: returnType, name and argument list. I am running into a compiler error that complains that it is unable to move the parsed results into the struct. Am I missing something?

//#define BOOST_SPIRIT_X3_DEBUG
#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/adapted.hpp>
#include <boost/fusion/adapted/struct/adapt_struct.hpp>
#include <boost/config/warning_disable.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <list>
#include <iostream>

namespace x3 = boost::spirit::x3;
namespace ascii = boost::spirit::x3::ascii;
using boost::spirit::x3::ascii::space;

namespace ast {
struct identifier {
    std::string name;
};

struct argument {
    identifier typeName;
    identifier value;
};

struct function_call {
    identifier name;
    std::list<argument> arguments;
};

struct function_prototype {
    identifier returnType;
    identifier name;
    std::list<argument> arguments;
};
} // namespace ast

BOOST_FUSION_ADAPT_STRUCT(ast::identifier, (std::string, name))
BOOST_FUSION_ADAPT_STRUCT(ast::argument, (struct identifier, typeName) (struct identifier, value))
BOOST_FUSION_ADAPT_STRUCT(ast::function_call, (struct identifier, name) (struct identifier, arguments))
BOOST_FUSION_ADAPT_STRUCT(ast::function_prototype, (struct identifier, returnType) (struct identifier, name) (std::list<struct argument>, arguments))

namespace parser
{
    struct identifier_class;
    typedef x3::rule<identifier_class, ast::identifier> identifier_type;
    identifier_type const identifier = "identifier";
    auto const identifier_def = x3::raw[x3::lexeme[(x3::alpha | '_') >> *(x3::alnum | '_')]];
    BOOST_SPIRIT_DEFINE(identifier)

    struct argument_class;
    typedef x3::rule<argument_class, ast::argument> argument_type;
    argument_type const argument = "argument";
    auto const argument_def = x3::raw[identifier >> identifier];
    BOOST_SPIRIT_DEFINE(argument)

    struct function_call_class;
    typedef x3::rule<function_call_class, ast::function_call> function_call_type;
    function_call_type const function_call = "function_call";
    auto const function_call_def = x3::raw[identifier >> '(' > -(argument % ',') > ')'];
    BOOST_SPIRIT_DEFINE(function_call)

    struct function_prototype_class;
    typedef x3::rule<function_prototype_class, ast::function_prototype> function_prototype_class_type;
    function_prototype_class_type const function_prototype = "function_prototype";
    auto const function_prototype_def =
    x3::raw[identifier >> identifier >> '(' > -(argument % ',') > ')'];
    BOOST_SPIRIT_DEFINE(function_prototype)

    auto const functionProtos = function_prototype >> *(function_prototype);
}

namespace Application {
class Parser {

  public:
    void functionParser(const std::string& input) {
        std::vector<ast::function_call> output;
        x3::phrase_parse(input.begin(), input.end(), parser::functionProtos, x3::space, output);
        std::cout << "success\n";
    }
};
} // namespace parser

回答1:


Fusion Adapt

One issue is the adaptations: you're supplying types but spelling them as forward declarations of types in the global namespace:

BOOST_FUSION_ADAPT_STRUCT(ast::argument, (struct identifier, typeName) (struct identifier, value))
BOOST_FUSION_ADAPT_STRUCT(ast::function_call, (struct identifier, name) (struct identifier, arguments))
BOOST_FUSION_ADAPT_STRUCT(ast::function_prototype, (struct identifier, returnType) (struct identifier, name) (std::list<struct argument>, arguments))

It might be okay to fix by replacing with e.g. ast::identifier, but why bother? You can definitely use the c++11 variation and let the compiler figure out the types:

BOOST_FUSION_ADAPT_STRUCT(ast::identifier, name)
BOOST_FUSION_ADAPT_STRUCT(ast::argument, typeName, value)
BOOST_FUSION_ADAPT_STRUCT(ast::function_call, name, arguments)
BOOST_FUSION_ADAPT_STRUCT(ast::function_prototype, returnType, name, arguments)

RAW

The second cinch is x3::raw[]. The raw directive exposes an iterator range as the attribute, and that's not compatible with the bound attribute type (e.g. ast::identifier).

In this particular case, you ARE parsing (compare with my remark from the previous answer, where you weren't parsing, and only matching). So you need to match up the synthesized attribute with the target attribute type:

auto const identifier_def = x3::lexeme[(x3::alpha | x3::char_('_')) >> *(x3::alnum | x3::char_('_'))];

Single-Element Sequences

There's a lingering issue with Qi/X3 that causes attribute propagation of single-element sequences to get confused. In this case it helps if you simply make identifier parse into std::string (and fusion will correctly assign that to ast::identifier from there):

auto const identifier_def = x3::lexeme[(x3::alpha | x3::char_('_')) >> *(x3::alnum | x3::char_('_'))];

Dropping the rest of the rawp[] directives makes it compile:

Live On Coliru

#define BOOST_SPIRIT_X3_DEBUG
#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/adapted.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <list>
#include <iostream>

namespace x3 = boost::spirit::x3;
namespace ascii = boost::spirit::x3::ascii;
using boost::spirit::x3::ascii::space;

namespace ast {
struct identifier {
    std::string name;
};

struct argument {
    identifier typeName;
    identifier value;
};

struct function_call {
    identifier name;
    std::list<argument> arguments;
};

struct function_prototype {
    identifier returnType;
    identifier name;
    std::list<argument> arguments;
};
} // namespace ast

BOOST_FUSION_ADAPT_STRUCT(ast::identifier, name)
BOOST_FUSION_ADAPT_STRUCT(ast::argument, typeName, value)
BOOST_FUSION_ADAPT_STRUCT(ast::function_call, name, arguments)
BOOST_FUSION_ADAPT_STRUCT(ast::function_prototype, returnType, name, arguments)

namespace parser
{
    struct identifier_class;
    typedef x3::rule<identifier_class, std::string> identifier_type;
    identifier_type const identifier = "identifier";
    auto const identifier_def = x3::lexeme[(x3::alpha | x3::char_('_')) >> *(x3::alnum | x3::char_('_'))];
    BOOST_SPIRIT_DEFINE(identifier)

    struct argument_class;
    typedef x3::rule<argument_class, ast::argument> argument_type;
    argument_type const argument = "argument";
    auto const argument_def = identifier >> identifier;
    BOOST_SPIRIT_DEFINE(argument)

    struct function_call_class;
    typedef x3::rule<function_call_class, ast::function_call> function_call_type;
    function_call_type const function_call = "function_call";
    auto const function_call_def = identifier >> '(' > -(argument % ',') > ')';
    BOOST_SPIRIT_DEFINE(function_call)

    struct function_prototype_class;
    typedef x3::rule<function_prototype_class, ast::function_prototype> function_prototype_class_type;
    function_prototype_class_type const function_prototype = "function_prototype";
    auto const function_prototype_def =
        identifier >> identifier >> '(' > -(argument % ',') >> x3::expect[')'] >> ';';
    BOOST_SPIRIT_DEFINE(function_prototype)

    auto const functionProtos = +function_prototype;
}

namespace Application {
class Parser {

  public:
    void functionParser(const std::string& input) {
        if (0) {
            ast::identifier output;
            x3::phrase_parse(input.begin(), input.end(), parser::identifier, x3::space, output);
        }
        if (0) {
            ast::argument output;
            x3::phrase_parse(input.begin(), input.end(), parser::argument, x3::space, output);
        }
        if (0) {
            ast::function_call output;
            x3::phrase_parse(input.begin(), input.end(), parser::function_call, x3::space, output);
        }
        if (0) {
            ast::function_prototype output;
            x3::phrase_parse(input.begin(), input.end(), parser::function_prototype, x3::space, output);
        }
        {
            std::vector<ast::function_prototype> output;
            x3::phrase_parse(input.begin(), input.end(), parser::functionProtos, x3::space, output);
            std::cout << "success: " << output.size() << " prototypes parsed\n";
        }
    }
};
} // namespace parser

int main()
{
    Application::Parser p;
    p.functionParser("void foo(int a); float bar(double b, char c);");
}

Prints

success: 2 prototypes parsed

SIMPLIFY

As long as you don't share rules across translation units OR require recursive rules, there is no need for the define/macros. Instead simplify:

auto const identifier         = as<std::string>("identifier", x3::lexeme[(x3::alpha | x3::char_('_')) >> *(x3::alnum | x3::char_('_'))]);
auto const argument           = as<ast::argument>("argument", identifier >> identifier);
auto const function_call      = as<ast::function_call>("function_call", identifier >> '(' > -(argument % ',') > ')');
auto const function_prototype = as<ast::function_prototype>("function_prototype",
    identifier >> identifier >> '(' > -(argument % ',') >> x3::expect[')'] >> ';');

That's using a very simple shorthand to type the rule attirbutes:

template <typename T> auto as = [](auto name, auto p) { return x3::rule<struct _, T> {name} = p; };

See it Live On Coliru

//#define BOOST_SPIRIT_X3_DEBUG
#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/adapted.hpp>
#include <list>
#include <iostream>

namespace x3 = boost::spirit::x3;
using boost::spirit::x3::ascii::space;

namespace ast {
    struct identifier {
        std::string name;
    };

    struct argument {
        identifier typeName;
        identifier value;
    };

    struct function_call {
        identifier name;
        std::list<argument> arguments;
    };

    struct function_prototype {
        identifier returnType;
        identifier name;
        std::list<argument> arguments;
    };
} // namespace ast

BOOST_FUSION_ADAPT_STRUCT(ast::identifier, name)
BOOST_FUSION_ADAPT_STRUCT(ast::argument, typeName, value)
BOOST_FUSION_ADAPT_STRUCT(ast::function_call, name, arguments)
BOOST_FUSION_ADAPT_STRUCT(ast::function_prototype, returnType, name, arguments)

namespace parser
{
    template <typename T> auto as = [](auto name, auto p) { return x3::rule<struct _, T> {name} = p; };

    auto const identifier         = as<std::string>("identifier", x3::lexeme[(x3::alpha | x3::char_('_')) >> *(x3::alnum | x3::char_('_'))]);
    auto const argument           = as<ast::argument>("argument", identifier >> identifier);
    auto const function_call      = as<ast::function_call>("function_call", identifier >> '(' > -(argument % ',') > ')');
    auto const function_prototype = as<ast::function_prototype>("function_prototype",
        identifier >> identifier >> '(' > -(argument % ',') >> x3::expect[')'] >> ';');

    auto const functionProtos = +function_prototype;
}

namespace Application {
    class Parser {

        public:
            void functionParser(const std::string& input) {
                std::vector<ast::function_prototype> output;
                x3::phrase_parse(input.begin(), input.end(), parser::functionProtos, x3::space, output);
                std::cout << "success: " << output.size() << " prototypes parsed\n";
            }
    };
} // namespace parser

int main()
{
    Application::Parser p;
    p.functionParser("void foo(int a); float bar(double b, char c);");
}

Which also prints

success: 2 prototypes parsed


来源:https://stackoverflow.com/questions/56157223/how-to-move-results-parsed-results-into-a-struct-using-spirit-x3

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!