如何在Spirit Qi中重复字符串连接'重复'解析器?

时间:2015-07-20 21:50:25

标签: c++ parsing boost-spirit boost-spirit-qi

我想将一个字符串分成几部分:

input = "part1/part2/part3/also3"

并用这些部分填充由三个std :: string组成的结构。

struct strings
{
    std::string a; // <- part1
    std::string b; // <- part2
    std::string c; // <- part3/also3
};

但是我的解析器似乎将各个部分合并在一起并将其存储到第一个std :: string中。

以下是code on coliru

#include <iostream>

#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/adapted.hpp>

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

struct strings
{
    std::string a;
    std::string b;
    std::string c;
};

BOOST_FUSION_ADAPT_STRUCT(strings,
  (std::string, a) (std::string, b) (std::string, c))

template <typename It>
struct split_string_grammar: qi::grammar<It, strings ()>
{
    split_string_grammar (int parts)
        : split_string_grammar::base_type (split_string)
    {
        assert (parts > 0);

        using namespace qi;

        split_string = repeat (parts-1) [part > '/'] > last_part;

        part = +(~char_ ("/"));
        last_part = +char_;

        BOOST_SPIRIT_DEBUG_NODES ((split_string) (part) (last_part))
    }

private:
    qi::rule<It, strings ()> split_string;
    qi::rule<It, std::string ()> part, last_part;
};

int main ()
{
    std::string const input { "one/two/three/four" };

    auto const last  = input.end ();
    auto       first = input.begin ();

    // split into 3 parts.
    split_string_grammar<decltype (first)> split_string (3);
    strings ss;

    bool ok = qi::parse (first, last, split_string, ss);

    std::cout << "Parsed: " << ok << "\n";

    if (ok) {
        std::cout << "a:" << ss.a << "\n";
        std::cout << "b:" << ss.b << "\n";
        std::cout << "c:" << ss.c << "\n";
    }
}

输出结果为:

Parsed: 1
a:onetwo
b:three/four
c:

虽然我期待:

Parsed: 1
a:one
b:two
c:three/four

我不想大量修改语法并留下&#34;重复&#34;声明,因为&#34;真实&#34;语法当然要复杂得多,我需要在那里使用它。只需要找到禁用连接的方法。我试过了

repeat (parts-1) [as_string[part] > '/']

但这不会编译。

2 个答案:

答案 0 :(得分:2)

这里的麻烦特别是qi::repeat is documented to expose元素类型的容器。

现在,由于规则的公开属性类型(strings是容器类型,因此Spirit“知道”如何展平值。

当然,在这种情况下,它不是想要的,但通常这种启发式方法可以非常方便地积累字符串值。

修复1:使用容器属性

您可以通过删除非容器(序列)目标属性来见证反向修复:

<强> Live On Coliru

//#define BOOST_SPIRIT_DEBUG
#include <iostream>

#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/adapted.hpp>

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

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

template <typename It>
struct split_string_grammar: qi::grammar<It, strings ()>
{
    split_string_grammar (int parts)
        : split_string_grammar::base_type (split_string)
    {
        assert (parts > 0);

        using namespace qi;

        split_string = repeat (parts-1) [part > '/'] 
                     > last_part
                     ;

        part         = +(~char_ ("/"))
                     ;

        last_part    = +char_
                     ;

        BOOST_SPIRIT_DEBUG_NODES ((split_string) (part) (last_part))
    }

private:
    qi::rule<It, strings     ()> split_string;
    qi::rule<It, std::string ()> part, last_part;
};

int main ()
{
    std::string const input { "one/two/three/four" };

    auto const last  = input.end ();
    auto       first = input.begin ();

    // split into 3 parts.
    split_string_grammar<decltype (first)> split_string (3);
    strings ss;

    bool ok = qi::parse (first, last, split_string, ss);

    std::cout << "Parsed: " << ok << "\n";

    if (ok) {
        for(auto i = 0ul; i<ss.size(); ++i)
            std::cout << static_cast<char>('a'+i) << ":" << ss[i] << "\n";
    }
}

你真正想要的是什么:

当然你想保持结构/序列适应(?);在这种情况下,这非常棘手,因为只要您使用任何类型的Kleene运算符(*,%)或qi::repeat,您就会拥有如上所述的属性转换规则,从而破坏您的心情。

幸运的是,我记得我有一个基于auto_ parser hacky 解决方案。请注意这个旧答案中的警告:

  

Read empty values with boost::spirit

     

CAVEAT 直接专注于std::string可能不是最好的主意(它可能并不总是合适的,可能与其他解析器交互不当)。

默认情况下,create_parser<std::string>未定义,因此您可能会认为此用法足以满足您的需求:

<强> Live On Coliru

#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>

namespace qi = boost::spirit::qi;

struct strings {
    std::string a;
    std::string b;
    std::string c;
};

namespace boost { namespace spirit { namespace traits {
    template <> struct create_parser<std::string> {
        typedef proto::result_of::deep_copy<
            BOOST_TYPEOF(
                qi::lexeme [+(qi::char_ - '/')] | qi::attr("(unspecified)")
            )
        >::type type;

        static type call() {
            return proto::deep_copy(
                qi::lexeme [+(qi::char_ - '/')] | qi::attr("(unspecified)")
            );
        }
    };
}}}

BOOST_FUSION_ADAPT_STRUCT(strings, (std::string, a)(std::string, b)(std::string, c))

template <typename Iterator>
struct google_parser : qi::grammar<Iterator, strings()> {
    google_parser() : google_parser::base_type(entry, "contacts") {
        using namespace qi;

        entry =
                skip('/') [auto_]
              ;
    }
  private:
    qi::rule<Iterator, strings()> entry;
};

int main() {
    using It = std::string::const_iterator;
    google_parser<It> p;

    std::string const input = "part1/part2/part3/also3";
    It f = input.begin(), l = input.end();

    strings ss;
    bool ok = qi::parse(f, l, p >> *qi::char_, ss, ss.c);

    if (ok)
    {
        std::cout << "a:" << ss.a << "\n";
        std::cout << "b:" << ss.b << "\n";
        std::cout << "c:" << ss.c << "\n";
    }
    else
        std::cout << "Parse failed\n";

    if (f!=l)
        std::cout << "Remaining unparsed: '" << std::string(f,l) << "'\n";
}

打印

a:part1
b:part2
c:part3/also3

更新/加成

在对OP's own answer的回应中,我想挑战自己,更确切地写出来。

主要的是以这样一种方式编写set_field_,使得它不知道/假设超过目的地序列类型的要求。

随着一点Boost Fusion的魔力变成:

struct set_field_
{
    template <typename Seq, typename Value>
    void operator() (Seq& seq, Value const& src, unsigned idx) const {
        fus::fold(seq, 0u, Visit<Value> { idx, src });
    }
private:
    template <typename Value>
    struct Visit {
        unsigned     target_idx;
        Value const& value;

        template <typename B>
        unsigned operator()(unsigned i, B& dest) const {
            if (target_idx == i) {
                boost::spirit::traits::assign_to(value, dest);
            }
            return i + 1;
        }
    };
};

它具有应用Spirit属性兼容性规则¹的额外灵活性。因此,您可以对以下两种类型使用相同的语法:

struct strings {
    std::string a, b, c;
};

struct alternative {
    std::vector<char> first;
    std::string       second;
    std::string       third;
};

为了使点回家,我使第二个结构的改编逆转了字段顺序:

BOOST_FUSION_ADAPT_STRUCT(strings, a, b, c)
BOOST_FUSION_ADAPT_STRUCT(alternative, third, second, first) // REVERSE ORDER :)

不用多说,演示程序:

<强> Live On Coliru

#define BOOST_SPIRIT_USE_PHOENIX_V3
#define BOOST_RESULT_OF_USE_DECLTYPE
#include <boost/fusion/adapted.hpp>
#include <boost/fusion/algorithm/iteration.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>

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

struct strings {
    std::string a, b, c;
};

struct alternative {
    std::vector<char> first;
    std::string       second;
    std::string       third;
};

BOOST_FUSION_ADAPT_STRUCT(strings, a, b, c)
BOOST_FUSION_ADAPT_STRUCT(alternative, third, second, first) // REVERSE ORDER :)

// output helpers for demo:
namespace {
    inline std::ostream& operator<<(std::ostream& os, strings const& data) {
        return os 
            << "a:\"" << data.a << "\" " 
            << "b:\"" << data.b << "\" " 
            << "c:\"" << data.c << "\" ";
    }

    inline std::ostream& operator<<(std::ostream& os, alternative const& data) {
        os << "first: vector<char> { \""; os.write(&data.first[0], data.first.size()); os << "\" } ";
        os << "second: \"" << data.second << "\" ";
        os << "third: \""  << data.third  << "\" ";
        return os;
    }
}

struct set_field_
{
    template <typename Seq, typename Value>
    void operator() (Seq& seq, Value const& src, unsigned idx) const {
        fus::fold(seq, 0u, Visit<Value> { idx, src });
    }
  private:
    template <typename Value>
    struct Visit {
        unsigned     target_idx;
        Value const& value;

        template <typename B>
        unsigned operator()(unsigned i, B& dest) const {
            if (target_idx == i) {
                boost::spirit::traits::assign_to(value, dest);
            }
            return i + 1;
        }
    };
};

boost::phoenix::function<set_field_> const set_field = {};

template <typename It, typename Target>
struct split_string_grammar: qi::grammar<It, Target(), qi::locals<unsigned> >
{
    split_string_grammar (int parts)
        : split_string_grammar::base_type (split_string)
    {
        assert (parts > 0);

        using namespace qi;
        using boost::phoenix::val;

        _a_type _current; // custom placeholder

        split_string = 
              eps       [ _current = 0u ]
            > repeat (parts-1) 
                [part   [ set_field(_val, _1, _current++) ] > '/']
            > last_part [ set_field(_val, _1, _current++) ];

        part = +(~char_ ("/"));
        last_part = +char_;

        BOOST_SPIRIT_DEBUG_NODES ((split_string) (part) (last_part))
    }

private:
    qi::rule<It, Target(), qi::locals<unsigned> > split_string;
    qi::rule<It, std::string()> part, last_part;
};

template <size_t N = 3, typename Target>
void run_test(Target target) {
    using It = std::string::const_iterator;
    std::string const input { "one/two/three/four" };

    It first = input.begin(), last = input.end();

    split_string_grammar<It, Target> split_string(N);

    bool ok = qi::parse (first, last, split_string, target);

    if (ok) {
        std::cout << target << '\n';
    } else {
        std::cout << "Parse failed\n";
    }

    if (first != last)
        std::cout << "Remaining input left unparsed: '" << std::string(first, last) << "'\n";
}

int main ()
{
    run_test(strings {});
    run_test(alternative {});
}

输出:

a:"one" b:"two" c:"three/four" 
first: vector<char> { "three/four" } second: "two" third: "one" 

¹与BOOST_SPIRIT_ACTIONS_ALLOW_ATTR_COMPAT

一样

答案 1 :(得分:2)

除了sehe的suggestions之外,还有一种可能的方法是使用语义动作(coliru):

struct set_field_
{
    void operator() (strings& dst, std::string const& src, unsigned& idx) const
    {
        assert (idx < 3);
        switch (idx++) {
            case 0: dst.a = src; break;
            case 1: dst.b = src; break;
            case 2: dst.c = src; break;
        }
    }
};

boost::phoenix::function<set_field_> const set_field { set_field_ {} };

template <typename It>
struct split_string_grammar: qi::grammar<It, strings (), qi::locals<unsigned> >
{
    split_string_grammar (int parts)
        : split_string_grammar::base_type (split_string)
    {
        assert (parts > 0);

        using namespace qi;
        using boost::phoenix::val;

        split_string = eps [ _a = val (0) ]
            > repeat (parts-1) [part [ set_field (_val, _1, _a) ] > '/']
            > last_part [ set_field (_val, _1, _a) ];

        part = +(~char_ ("/"));
        last_part = +char_;

        BOOST_SPIRIT_DEBUG_NODES ((split_string) (part) (last_part))
    }

private:
    qi::rule<It, strings (), qi::locals<unsigned> > split_string;
    qi::rule<It, std::string ()> part, last_part;
};