【问题标题】:Parsing nested key value pairs in Boost Spirit在 Boost Spirit 中解析嵌套的键值对
【发布时间】:2015-05-23 18:50:52
【问题描述】:

我在编写我认为应该使用 Boost::Spirit 的简单解析器时遇到了麻烦。 (我使用 Spirit 而不是仅仅使用字符串函数,因为这部分是我的学习练习)。

数据

要解析的数据采用键值对的形式,其中一个值本身可以是一个键值对。键是字母数字的(带有下划线且没有数字作为第一个字符);值是字母数字加上.-_ - 值可以是DD-MMM-YYYY 格式的日期,例如01-Jan-2015 和像 3.1415 这样的浮点数以及普通的旧字母数字字符串。键和值用=分隔;对以; 分隔;结构化值以{...} 分隔。目前,我正在将用户输入中的所有空格删除,然后再将其传递给 Spirit。

示例输入:

Key1 = Value1; Key2 = { NestedKey1=Alan; NestedKey2 = 43.1232; }; Key3 = 15-Jul-1974 ;

然后我会去掉所有空格给

Key1=Value1;Key2={NestedKey1=Alan;NestedKey2=43.1232;};Key3=15-Jul-1974;

然后我实际上将它传递给 Spirit。

问题

当价值观只是价值观时,我目前拥有的东西只是花花公子。当我开始在输入中编码结构化值时,Spirit 在第一个结构化值之后停止。如果只有一个结构化值,一种解决方法是将其放在输入的末尾......但有时我需要两个或更多结构化值。

代码

下面在VS2013中编译并说明错误:

#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/pair.hpp>
#include <boost/fusion/adapted.hpp>
#include <map>
#include <string>
#include <iostream>

typedef std::map<std::string, std::string> ARGTYPE;

#define BOOST_SPIRIT_DEBUG

namespace qi = boost::spirit::qi;
namespace fusion = boost::fusion;

template < typename It, typename Skipper>
struct NestedGrammar : qi::grammar < It, ARGTYPE(), Skipper >
{
    NestedGrammar() : NestedGrammar::base_type(Sequence)
    {
        using namespace qi;
        KeyName = qi::char_("a-zA-Z_") >> *qi::char_("a-zA-Z0-9_");
        Value = +qi::char_("-.a-zA-Z_0-9");

        Pair = KeyName >> -(
            '=' >> ('{' >> raw[Sequence] >> '}' | Value)
            );

        Sequence = Pair >> *((qi::lit(';') | '&') >> Pair);

        BOOST_SPIRIT_DEBUG_NODE(KeyName);
        BOOST_SPIRIT_DEBUG_NODE(Value);
        BOOST_SPIRIT_DEBUG_NODE(Pair);
        BOOST_SPIRIT_DEBUG_NODE(Sequence);
    }
private:
    qi::rule<It, ARGTYPE(), Skipper> Sequence;
    qi::rule<It, std::string()> KeyName;
    qi::rule<It, std::string(), Skipper> Value;
    qi::rule<It, std::pair < std::string, std::string>(), Skipper> Pair;
};


template <typename Iterator>
ARGTYPE Parse2(Iterator begin, Iterator end)
{
    NestedGrammar<Iterator, qi::space_type> p;
    ARGTYPE data;
    qi::phrase_parse(begin, end, p, qi::space, data);
    return data;
}


// ARGTYPE is std::map<std::string,std::string>
void NestedParse(std::string Input, ARGTYPE& Output)
{
    Input.erase(std::remove_if(Input.begin(), Input.end(), isspace), Input.end());
    Output = Parse2(Input.begin(), Input.end());
}

int main(int argc, char** argv)
{
    std::string Example1, Example2, Example3;
    ARGTYPE Out;

    Example1 = "Key1=Value1 ; Key2 = 01-Jan-2015; Key3 = 2.7181; Key4 = Johnny";
    Example2 = "Key1 = Value1; Key2 = {InnerK1 = one; IK2 = 11-Nov-2011;};";
    Example3 = "K1 = V1; K2 = {IK1=IV1; IK2=IV2;}; K3=V3; K4 = {JK1=JV1; JK2=JV2;};";

    NestedParse(Example1, Out);
    for (ARGTYPE::iterator i = Out.begin(); i != Out.end(); i++)
        std::cout << i->first << "|" << i->second << std::endl;
    std::cout << "=====" << std::endl;

    /* get the following, as expected:
    Key1|Value1
    Key2|01-Jan-2015
    Key3|2.7181
    Key4|Johnny
    */

    NestedParse(Example2, Out);
    for (ARGTYPE::iterator i = Out.begin(); i != Out.end(); i++)
        std::cout << i->first << "|" << i->second << std::endl;
    std::cout << "=====" << std::endl;

    /* get the following, as expected:
    Key1|Value1
    key2|InnerK1=one;IK2=11-Nov-2011
    */

    NestedParse(Example3, Out);
    for (ARGTYPE::iterator i = Out.begin(); i != Out.end(); i++)
        std::cout << i->first << "|" << i->second << std::endl;

    /* Only get the first two lines of the expected output:
    K1|V1
    K2|IK1=IV1;IK2=IV2
    K3|V3
    K4|JK1=JV1;JK2=JV2
    */

    return 0;

}

我不确定问题是由于我对 BNF 的无知,我对 Spirit 的无知,还是我目前对两者的无知。

任何帮助表示赞赏。我读过例如Spirit Qi sequence parsing issues 和其中的链接,但我仍然无法弄清楚我做错了什么。

【问题讨论】:

    标签: c++ boost boost-spirit


    【解决方案1】:

    这正是Spirit擅长的简单语法。

    此外,绝对没有必要预先跳过空格:Spirit 已为此目的内置了跳过符。

    不过,对于您的明确问题:

    Sequence 规则过于复杂。您可以只使用列表运算符 (%):

    Sequence = Pair % char_(";&");
    

    现在您的问题是您以不期望的; 结束序列,因此SequenceValue 最终都无法解析。这不是很清楚,除非您 #define BOOST_SPIRIT_DEBUG¹ 并检查调试输出。

    所以要修复它使用:

    Sequence = Pair % char_(";&") >> -omit[char_(";&")];
    

    Fix Live On Coliru(或with debug info

    打印:

    Key1|Value1
    Key2|01-Jan-2015
    Key3|2.7181
    Key4|Johnny
    =====
    Key1|Value1
    Key2|InnerK1=one;IK2=11-Nov-2011;
    =====
    K1|V1
    K2|IK1=IV1;IK2=IV2;
    K3|V3
    K4|JK1=JV1;JK2=JV2;
    

    奖金清理

    其实,这很简单。只需删除删除空格的多余行。船长已经是qi::space

    (请注意,尽管跳过器不适用于您的 Value 规则,因此值不能包含空格,但解析器也不会静默跳过它;我想这可能是您想要的。请注意它) .

    递归 AST

    您实际上希望有一个递归 AST,而不是解析为平面地图。

    Boost recursive variants 让这一切变得轻而易举:

    namespace ast {
        typedef boost::make_recursive_variant<std::string, std::map<std::string, boost::recursive_variant_> >::type Value;
        typedef std::map<std::string, Value> Sequence;
    }
    

    要完成这项工作,您只需更改规则的声明属性类型:

    qi::rule<It, ast::Sequence(),                      Skipper> Sequence;
    qi::rule<It, std::pair<std::string, ast::Value>(), Skipper> Pair;
    qi::rule<It, std::string(),                        Skipper> String;
    qi::rule<It, std::string()>                                 KeyName;
    

    规则本身甚至不必改变。您需要编写一个小访问者来流式传输 AST:

    static inline std::ostream& operator<<(std::ostream& os, ast::Value const& value) {
        struct vis : boost::static_visitor<> {
            vis(std::ostream& os, std::string indent = "") : _os(os), _indent(indent) {}
    
            void operator()(std::map<std::string, ast::Value> const& map) const {
                _os << "map {\n";
                for (auto& entry : map) {
                    _os << _indent << "    " << entry.first << '|';
                    boost::apply_visitor(vis(_os, _indent+"    "), entry.second);
                    _os << "\n";
                }
                _os << _indent << "}\n";
            }
            void operator()(std::string const& s) const {
                _os << s;
            }
    
        private:
            std::ostream& _os;
            std::string _indent;
        };
        boost::apply_visitor(vis(os), value);
        return os;
    }
    

    现在打印出来了:

    map {
        Key1|Value1
        Key2|01-Jan-2015
        Key3|2.7181
        Key4|Johnny
    }
    
    =====
    map {
        Key1|Value1
        Key2|InnerK1 = one; IK2 = 11-Nov-2011;
    }
    
    =====
    map {
        K1|V1
        K2|IK1=IV1; IK2=IV2;
        K3|V3
        K4|JK1=JV1; JK2=JV2;
    }
    

    当然,关键是您现在将raw[Sequence] 更改为 Sequence

    map {
        Key1|Value1
        Key2|01-Jan-2015
        Key3|2.7181
        Key4|Johnny
    }
    
    =====
    map {
        Key1|Value1
        Key2|map {
            IK2|11-Nov-2011
            InnerK1|one
        }
    
    }
    
    =====
    map {
        K1|V1
        K2|map {
            IK1|IV1
            IK2|IV2
        }
    
        K3|V3
        K4|map {
            JK1|JV1
            JK2|JV2
        }
    
    }
    

    完整的演示代码

    Live On Coliru

    //#define BOOST_SPIRIT_DEBUG
    #include <boost/variant.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/fusion/adapted/std_pair.hpp>
    #include <iostream>
    #include <string>
    #include <map>
    
    namespace ast {
        typedef boost::make_recursive_variant<std::string, std::map<std::string, boost::recursive_variant_> >::type Value;
        typedef std::map<std::string, Value> Sequence;
    }
    
    namespace qi = boost::spirit::qi;
    
    template <typename It, typename Skipper>
    struct NestedGrammar : qi::grammar <It, ast::Sequence(), Skipper>
    {
        NestedGrammar() : NestedGrammar::base_type(Sequence)
        {
            using namespace qi;
            KeyName = qi::char_("a-zA-Z_") >> *qi::char_("a-zA-Z0-9_");
            String = +qi::char_("-.a-zA-Z_0-9");
    
            Pair = KeyName >> -(
                    '=' >> ('{' >> Sequence >> '}' | String)
                );
    
            Sequence = Pair % char_(";&") >> -omit[char_(";&")];
    
            BOOST_SPIRIT_DEBUG_NODES((KeyName) (String) (Pair) (Sequence))
        }
    private:
        qi::rule<It, ast::Sequence(),                      Skipper> Sequence;
        qi::rule<It, std::pair<std::string, ast::Value>(), Skipper> Pair;
        qi::rule<It, std::string(),                        Skipper> String;
        qi::rule<It, std::string()>                                 KeyName;
    };
    
    
    template <typename Iterator>
    ast::Sequence DoParse(Iterator begin, Iterator end)
    {
        NestedGrammar<Iterator, qi::space_type> p;
        ast::Sequence data;
        qi::phrase_parse(begin, end, p, qi::space, data);
        return data;
    }
    
    static inline std::ostream& operator<<(std::ostream& os, ast::Value const& value) {
        struct vis : boost::static_visitor<> {
            vis(std::ostream& os, std::string indent = "") : _os(os), _indent(indent) {}
    
            void operator()(std::map<std::string, ast::Value> const& map) const {
                _os << "map {\n";
                for (auto& entry : map) {
                    _os << _indent << "    " << entry.first << '|';
                    boost::apply_visitor(vis(_os, _indent+"    "), entry.second);
                    _os << "\n";
                }
                _os << _indent << "}\n";
            }
            void operator()(std::string const& s) const {
                _os << s;
            }
    
          private:
            std::ostream& _os;
            std::string _indent;
        };
        boost::apply_visitor(vis(os), value);
        return os;
    }
    
    int main()
    {
        std::string const Example1 = "Key1=Value1 ; Key2 = 01-Jan-2015; Key3 = 2.7181; Key4 = Johnny";
        std::string const Example2 = "Key1 = Value1; Key2 = {InnerK1 = one; IK2 = 11-Nov-2011;};";
        std::string const Example3 = "K1 = V1; K2 = {IK1=IV1; IK2=IV2;}; K3=V3; K4 = {JK1=JV1; JK2=JV2;};";
    
        std::cout << DoParse(Example1.begin(), Example1.end()) << "\n";
        std::cout << DoParse(Example2.begin(), Example2.end()) << "\n";
        std::cout << DoParse(Example3.begin(), Example3.end()) << "\n";
    }
    

    ¹您“拥有”它,但不是在正确的位置!它应该在任何 Boost 包含之前进行。

    【讨论】:

    • 添加了一个带有递归 AST 的完整工作示例,以真正模仿输入格式:Live On Coliru
    • Sequence 的修复工作完美无缺,关于“正确”进行修复的部分非常出色。编译需要很长时间,但我只需要这样做一次(加上这是模板库的通常权衡)。谢谢!
    猜你喜欢
    • 1970-01-01
    • 2021-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多