提振精神:将结果复制到字符串向量中

boost spirit: copy the result in a vector of strings

我想以这种形式解析一个函数(具有任意名称和任意数字的参数):

function(bye, 1, 3, 4, foo)

参数可以是逗号分隔的通用字符串。 我想复制函数的名称和字符串向量中的参数。 像这样

   std::vector<std::string> F;
   std::string fun = "function(bye, 1, 3, 4, foo)";

// The parser must produce this vector from the example
    F[0] == "function"
    F[1] == "1"
    F[2] == "3"
    F[3] == "4"
    F[4] == "foo"

我在阅读一些教程后编写了以下代码,但它不起作用(从某种意义上说它无法编译)。

#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_object.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/io.hpp>

#include <iostream>
#include <string>



namespace client
{

    namespace qi = boost::spirit::qi;
    namespace ascii = boost::spirit::ascii;

    ///////////////////////////////////////////////////////////////////////////////
    template <typename Iterator>
    struct command_parser : qi::grammar<Iterator, std::vector<std::string>(), ascii::space_type>
    {
        command_parser() : command_parser::base_type(start)
        {
            using qi::int_;
            using qi::lit;
            using qi::double_;
            using qi::lexeme;
            using ascii::char_;

            fn_name = +qi::char_("a-zA-Z");
            string =  +qi::char_("a-zA-Z_0-9");
            rec = *( lit(",") >> string );

            start %= fn_name >> lit("(") >> string >> rec >> lit(")") ;
        }

        qi::rule<Iterator, std::string(), ascii::space_type> fn_name;
        qi::rule<Iterator, std::string(), ascii::space_type> string;
        qi::rule<Iterator, std::string(), ascii::space_type> rec;

        qi::rule<Iterator, std::vector<std::string>, ascii::space_type> start;
    };
}


////////////////////////////////////////////////////////////////////////////
//  Main program
////////////////////////////////////////////////////////////////////////////
int
 main()
{

    namespace qi = boost::spirit::qi;

    std::cout << "/////////////////////////////////////////////////////////\n\n";

    client::command_parser<std::string::iterator> CP;
    std::string cmd("fun(1,2,3,4  , 5, foo) ");

    std::vector<std::string> VV;

    bool result = qi::parse(cmd.begin(), cmd.end(), CP, VV); 

    if (result) {
        for ( auto sss : VV ){
            std::cout << sss << std::endl;
        }
    } else {
        std::cout << "Fail" << std::endl;
    }

    return 0 ;

}

我正在根据@sehe 提出的建议更正我的答案。这些更正的所有功劳都归功于他。我在下面引用您的行号。所以第一个错误来自精神,它说:

incompatible_start_rule: // If you see the assertion below failing then the start rule // passed to the constructor of the grammar is not compatible with // the grammar (i.e. it uses different template parameters).

start 解析器的签名与解析器减速的签名不匹配。

22. struct command_parser : qi::grammar<Iterator, std::vector<std::string>(), ascii::space_type>
43. qi::rule<Iterator, std::vector<std::string>, ascii::space_type> start;

我用谷歌搜索了这个,但找不到解释,但最好使用对象而不是类型。我在第一个答案中以另一种方式做到了。正确的修复在第 43 行:

43. qi::rule<Iterator, std::vector<std::string>(), ascii::space_type> start;

下一个精神错误是:

The rule was instantiated with a skipper type but you have not pass any. Did you use parse instead of phrase_parse?");

所以船长需要 phrase_parse。请注意,我们需要一个船长来传递。

64. using qi::ascii::space;
65. bool result = qi::phrase_parse(cmd.begin(), cmd.end(), CP, space, VV);

现在编译,输出为:

fun
1
2345foo

我知道这不行,您希望用每个传递的参数填充向量。所以你需要一个符合你的属性和意图的规则。使用 std::string 的 kleene 运算符会将所有数据放入一个字符串中。所以使用你的属性:

41. qi::rule<Iterator, std::vector<std::string>(), ascii::space_type> rec;``

现在正如@sehe 指出的那样,带有 fn_namestring 的船长只会将名称与空格和换行符连接起来。所以不要在那里使用船长。

39. qi::rule<Iterator, std::string()> fn_name;
40. qi::rule<Iterator, std::string()> string;

我犯的另一个错误是看到 %= 并将其称为列表运算符。从here开始,它是一个定义运算符。我不确定为什么有两个但是玩弄,似乎你需要使用 %= 和语义动作。这是更正后的代码:

#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_object.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/io.hpp>

#include <iostream>
#include <string>

namespace client
{
    namespace qi = boost::spirit::qi;
    namespace ascii = boost::spirit::ascii;

    template <typename Iterator>
    struct command_parser : qi::grammar<Iterator, std::vector<std::string>(), ascii::space_type>
    {
        command_parser() : command_parser::base_type(start)
        {
            using qi::int_;
            using qi::lit;
            using qi::double_;
            using qi::lexeme;
            using ascii::char_;

            fn_name = +qi::char_("a-zA-Z");
            string = +qi::char_("a-zA-Z_0-9");
            rec = *(lit(",") >> string);

            start %= fn_name >> lit("(") >> string >> rec >> lit(")");
        }
        qi::rule<Iterator, std::string()> fn_name;
        qi::rule<Iterator, std::string()> string;
        qi::rule<Iterator, std::vector<std::string>(), ascii::space_type> rec;
        qi::rule<Iterator, std::vector<std::string>(), ascii::space_type> start;
    };
}

int main()
{
    namespace qi = boost::spirit::qi;

    client::command_parser<std::string::iterator> CP;
    std::string cmd("function(1,2,3,4  , 5, foo) ");

    std::vector<std::string> VV;

    bool result = qi::phrase_parse(cmd.begin(), cmd.end(), CP, qi::ascii::space, VV);

    if (result) {
        for (auto sss : VV) {
            std::cout << sss << std::endl;
        }
    }
    else {
        std::cout << "Fail" << std::endl;
    }
    return 0;
}

这里是一个使用 X3 的例子:

#include <boost/spirit/home/x3.hpp>
#include <iostream>
#include <vector>

//your attribute, could be more complex, might use namespace
using attr = std::vector<std::string>;

namespace parser {
    namespace x3 = boost::spirit::x3;

    const auto fn_name = +x3::char_("a-zA-Z");
    const auto string = +x3::char_("a-zA-Z_0-9");
    const auto start = x3::rule<struct _, attr>() = fn_name >> "(" >> string % ',' >> ")";
}

int main()
{
    namespace x3 = boost::spirit::x3;
    std::string cmd("fun(1,.2,3,4  , 5, foo) ");
    attr VV;
    auto it = cmd.begin();
    bool result = phrase_parse(it, cmd.end(), parser::start, x3::space, VV);

    if (result) {
        for (auto sss : VV) {
            std::cout << "-> " << sss << std::endl;
        }
    }
    else 
        std::cout << "Fail at" << std::endl;

    return 0;
}

只是为了好玩,这是我对这个语法的极简主义看法:

using CallList = std::vector<std::string>;

struct ParseError : std::runtime_error {
    ParseError() : std::runtime_error("ParseError") {}
};

// The parse implementation
CallList parse_function_call(std::string const& fun) {
    CallList elements;
    using namespace boost::spirit::qi;
    using It = decltype(begin(fun));
    static const rule<It, std::string()> identifier = alpha >> +(alnum | char_('_'));

    if (!phrase_parse(begin(fun), end(fun),
                identifier >> '(' >> -(lexeme[+~char_(",)")] % ",") >> ')' >> eoi,
                space, elements))
        throw ParseError{};
    return elements;
}

有一点管道

// just for test output
using TestResult = std::variant<CallList, ParseError>;

// exceptions are equivalent
static constexpr bool operator==(ParseError const&, ParseError const&)
    { return true; }

static inline std::ostream& operator<<(std::ostream& os, TestResult const& tr) {
    using namespace std;
    if (holds_alternative<ParseError>(tr)) {
        return os << "ParseError";
    } else {
        auto& list = get<CallList>(tr);
        copy(begin(list), end(list), std::experimental::make_ostream_joiner(os << "{", ","));
        return os << "}";
    }
}

TestResult try_parse(std::string const& fun) {
    try { return parse_function_call(fun); }
    catch(ParseError const& e) { return e; }
}

这是一个测试运行程序:

for (auto const& [input, expected]: {
        Case("function(bye, 1, 3, 4, foo)", CallList{"function", "1", "3", "4", "foo"}),
        {"liar(pants on fire)", CallList{"liar", "pants on fire"}},
        {"liar('pants on fire')", CallList{"liar", "'pants on fire'"}},
        {"nullary()", CallList{"nullary"}},
        {"nullary(    )", CallList{"nullary"}},
        {"zerolength(a,,b)", ParseError{}},
        {"zerolength(a, ,b)", ParseError{}},
        {"noarglust", ParseError{}},
        {"", ParseError{}},
        {"()", ParseError{}},
        {"1(invalidfunctionname)", ParseError{}},
        {"foo(bar) BOGUS", ParseError{}},
    })
{
    auto const actual = try_parse(input);
    bool const ok = (actual == expected);

    cout << std::quoted(input) << ": " << (ok? "PASS":"FAIL") << "\n";
    if (!ok) {
        std::cout << " -- expected: " << expected << "\n";
        std::cout << " -- actual:   " << actual << "\n";
    }
}

打印 Live On Coliru

"function(bye, 1, 3, 4, foo)": FAIL
 -- expected: {function,1,3,4,foo}
 -- actual:   {function,bye,1,3,4,foo}
"liar(pants on fire)": PASS
"liar('pants on fire')": PASS
"nullary()": PASS
"nullary(    )": PASS
"zerolength(a,,b)": PASS
"zerolength(a, ,b)": PASS
"noarglust": PASS
"": PASS
"()": PASS
"1(invalidfunctionname)": PASS
"foo(bar) BOGUS": PASS

请注意,您的示例测试用例没有通过,但我认为这是测试用例中的一个错误。

完整列表

Live On Coliru

//#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>
#include <experimental/iterator>
#include <variant>
#include <iomanip>

using CallList = std::vector<std::string>;

struct ParseError : std::runtime_error {
    ParseError() : std::runtime_error("ParseError") {}
};

// The parse implementation
CallList parse_function_call(std::string const& fun) {
    CallList elements;
    using namespace boost::spirit::qi;
    using It = decltype(begin(fun));
    static const rule<It, std::string()> identifier = alpha >> +(alnum | char_('_'));

    if (!phrase_parse(begin(fun), end(fun),
                identifier >> '(' >> -(lexeme[+~char_(",)")] % ",") >> ')' >> eoi,
                space, elements))
        throw ParseError{};
    return elements;
}

// just for test output
using TestResult = std::variant<CallList, ParseError>;

// exceptions are equivalent
static constexpr bool operator==(ParseError const&, ParseError const&)
    { return true; }

static inline std::ostream& operator<<(std::ostream& os, TestResult const& tr) {
    using namespace std;
    if (holds_alternative<ParseError>(tr)) {
        return os << "ParseError";
    } else {
        auto& list = get<CallList>(tr);
        copy(begin(list), end(list), std::experimental::make_ostream_joiner(os << "{", ","));
        return os << "}";
    }
}

TestResult try_parse(std::string const& fun) {
    try { return parse_function_call(fun); }
    catch(ParseError const& e) { return e; }
}

int main() {
    using namespace std;

    using Case = pair<std::string, TestResult>;

    for (auto const& [input, expected]: {
            Case("function(bye, 1, 3, 4, foo)", CallList{"function", "1", "3", "4", "foo"}),
            {"liar(pants on fire)", CallList{"liar", "pants on fire"}},
            {"liar('pants on fire')", CallList{"liar", "'pants on fire'"}},
            {"nullary()", CallList{"nullary"}},
            {"nullary(    )", CallList{"nullary"}},
            {"zerolength(a,,b)", ParseError{}},
            {"zerolength(a, ,b)", ParseError{}},
            {"noarglust", ParseError{}},
            {"", ParseError{}},
            {"()", ParseError{}},
            {"1(invalidfunctionname)", ParseError{}},
            {"foo(bar) BOGUS", ParseError{}},
        })
    {
        auto const actual = try_parse(input);
        bool const ok = (actual == expected);

        cout << std::quoted(input) << ": " << (ok? "PASS":"FAIL") << "\n";
        if (!ok) {
            std::cout << " -- expected: " << expected << "\n";
            std::cout << " -- actual:   " << actual << "\n";
        }
    }
}