【问题标题】: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 ;

}

【问题讨论】:

    标签: c++ boost boost-spirit


    【解决方案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 除非调用者提供不同的船长真的有意义。
      猜你喜欢
      • 2018-08-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-13
      • 2019-12-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多