问题
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