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

Posted

技术标签:

【中文标题】提升精神:将结果复制到字符串向量中【英文标题】:boost spirit: copy the result in a vector of strings 【发布时间】:2019-08-23 15:46:25 【问题描述】:

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

函数(再见, 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 ;


【问题讨论】:

【参考方案1】:

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

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";
        
    

【讨论】:

【参考方案2】:

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

incompatible_start_rule: // 如果你看到下面的断言失败,那么开始规则 // 传递给构造函数的语法不兼容 // 语法(即它使用不同的模板参数)。

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;

下一个精神错误是:

该规则已使用船长类型实例化,但您尚未通过 任何。你用parse代替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;

【讨论】:

其实指定属性类型的首选方式是T(),而不是T。参数列表可以指定任何继承的属性。您建议的拼写好像“正确”实际上只是最近版本中添加的后备功能,以减少用户不友好。但根据我的经验,T 样式并非在所有情况下都得到很好的支持。 此外,您将船长保留在标识符规则上,这会导致解析混乱(fun\nction name 将解析为 functionname 就好了)。 嗨@sehe 我想修复我的帖子。我看到了船长的问题。但是我一定对在模板减速中使用对象而不是类型有一个误解。 std::vector&lt;std::string&gt;() 对我不起作用。我找不到参考资料来消除我的困惑,所以它可能对一个新问题有好处?谢谢,丹。 @lakeweb 我猜你忘记了启动规则一致性,所以start 必须具有与语法完全相同的属性声明。 This 显示它工作正常。我通常也 encapsulate the skipper 除非调用者提供不同的船长真的有意义。

以上是关于提升精神:将结果复制到字符串向量中的主要内容,如果未能解决你的问题,请参考以下文章

将轮廓点向量复制到垫子中

如何将基本字符串中的字符复制到向量字符串中?

提升精神提取第一个单词并将其存储在向量中

将项目从一个向量复制到另一个向量

C++ 将对象向量中的元素复制到具有此元素的向量中

C ++ 11分段错误试图将数组(<算法>)动态复制到向量中