Boost.Spirit.Qi:动态创建“差异";解析时的解析器

可以通过二进制-(减号)运算符创建差异"解析器:

rule = qi::char_ - qi::lit("}}")

甚至复合差异:

rule = qi::char_ - qi::lit("}}") - qi::lit("]]")

但是我怎么能在解析时生成差异解析器的整个结果?
我猜它可能是某种形式,如下所示:

phoenix::function差异解析器;规则 = qi::lazy(difference_parser(qi::char_, {"}}", "]]"}));

这里,{..., ..., ...} 部分实际上是一个 stl 容器,但这不是重点;我可以处理那部分.

我找到了模板 qi::difference -- 但我不知道如何使用它.

解决方案

在我看来,您并不是在寻找动态的差异"表达式,而是寻找动态的可变参数替代 (a|b|c...)" 表达式:

expr - a - b - c 等价于 expr - (a|b|c)

然后您可以使用以下任一方法轻松实现差异:

expr - orCombine(alternatives)

!orCombine(alternatives) >>表达式

现在,完成这项工作有很多困难,我将首先解释一下.幸运的是,有一种更简单的方法,使用 qi::symbols,我将在之后演示.

棘手的事情

如果您愿意,您可以按需生成"替代解析器表达式,并具有一定的魔力.我在这个答案中展示了如何做到这一点:

  • 生成精神解析器来自可选解析器表达式的可变参数列表的表达式

但是

  1. 它充满了陷阱(因为 proto 表达式不适合复制)1
  2. 为了避免中间存储,它方便地使用了可变参数(注意 deepcopy_ 以防止未定义行为):

    templatevoid parse_one_of(Expr& ...表达式){自动解析器 = boost::fusion::fold(boost::tie(表达式...),qi::eps(false),deepcopy_(arg2 | arg1));

    看到您如何需要真正替代解析器的动态组合,我不知道如何在不增加复杂性和细微错误机会的情况下适应您的需求(相信我,我已经试过了).

所以,我推荐一个尝试过的 &滥用"现有动态"解析器的真正方法:

使用qi::symbols

简化

这个想法是从著名的Nabilek Trick"中借来的.它使用 qi::symbols,因此具有出色的运行时性能特征2.

不用多说,这是一个如何使用它的示例,从字符串文字向量开始:

template 结构解析器:qi::grammar{parser() : parser::base_type(start){static const std::vector不接受 { "}}", "]]" };使用命名空间qi;排除 = 排除(not_accepted);开始 = *(char_ - 排除);BOOST_SPIRIT_DEBUG_NODE(开始);}私人的:qi::rule<It, std::string(), Skipper>开始;typedef qi::symbols排除;排除排除;模板排除排除(元素 const& 元素){排除结果;for(auto& el : 元素)结果.add(el);返回结果;}};

一个完整的工作示例是no.rerefern>stacked-crooked.com/view?id=ddbb2549674bfed90e3c8df33b048574-7616891f9fd25da6391c2728423de797 并打印

解析成功数据:123尾随未解析:']] 4'

完整代码

供以后参考:

#include 命名空间 qi = boost::spirit::qi;template 结构解析器:qi::grammar{parser() : parser::base_type(start){static const std::vector不接受 { "}}", "]]" };使用命名空间qi;排除 = 排除(not_accepted);开始 = *(char_ - 排除);BOOST_SPIRIT_DEBUG_NODE(开始);}私人的:qi::rule<It, std::string(), Skipper>开始;typedef qi::symbols排除;排除排除;模板排除排除(元素 const& 元素){排除结果;for(auto& el : 元素)结果.add(el);返回结果;}};int main(){const std::string input = "1 2 3]] 4";typedef std::string::const_iterator 它;它 f(begin(input)), l(end(input));解析器p;std::string 数据;bool ok = qi::phrase_parse(f,l,p,qi::space,data);如果(好){std::cout <<"解析成功
";std::cout <<数据:"<<数据<<"
";}否则 std::cerr <<解析失败:'" <<std::string(f,l) <<"'
";如果 (f!=l) std::cerr <<尾随未解析:'"<<std::string(f,l) <<"'
";}

<小时>

1我相信这个问题会在即将推出的Spirit新版本中解决(目前实验版称为Spirit X3")

2 它使用 Tries 来查找匹配项>

A "difference" parser can be created by the binary -(minus) operator:

rule = qi::char_ - qi::lit("}}")

or even compound differences:

rule = qi::char_ - qi::lit("}}") - qi::lit("]]")

But how could I generate the whole result of the difference parser at the parse time?
I'm guessing it might be some kind of form like below:

phoenix::function<difference_parser_impl> difference_parser;
rule = qi::lazy(difference_parser(qi::char_, {"}}", "]]"}));

Here, the {..., ..., ...} part would actually be a stl container, but it is not the point; I can handle that part.

I have found the template qi::difference<Left, Right> -- but I couldn't find out how to use it.

解决方案

It seems to me you're not looking for a dynamic "difference" expression so much, but rather a dynamic "variadic alternative (a|b|c...)" expression:

expr - a - b - c is equivalent to expr - (a|b|c)

You could then easily achieve the difference using either:

expr - orCombine(alternatives)

or

!orCombine(alternatives) >> expr

Now, getting this done has many rough edges, which I'll explain first. Luckily, there is a simpler way, using qi::symbols, which I'll demonstrate right after that.

The tricky stuff

If you want, you can "generate" alternative parser expressions on-demand, with a fair bit of wizardry. I showed how to do this in this answer:

  • Generating Spirit parser expressions from a variadic list of alternative parser expressions

But

  1. it is fraught with pitfalls (as proto expressions don't lend themselves to copying well)1
  2. it conveniently used variadics in order to avoid intermediate storage (note the deepcopy_ to ward of Undefined Behaviour):

    template<typename ...Expr>
    void parse_one_of(Expr& ...expressions)
    {
        auto parser = boost::fusion::fold(
                    boost::tie(expressions...),
                    qi::eps(false),
                    deepcopy_(arg2 | arg1)
                );
    

    Seeing how you have a need for truly dynamic composition of the alternative parser, I don't see how this could be adapted to your needs without an explosion of complexity and opportunity for subtle error (believe me, I already tried).

So, instead I recommend a tried & true approach that "abuses" an existing "dynamic" parser:

Simplify using qi::symbols

This idea borrows losely from the well-famed "Nabialek Trick". It uses qi::symbols, and consequently has excellent runtime performance characteristics2.

With no further ado, this is an example of how you could use it, starting from a vector of string literals:

template <typename It, typename Skipper = qi::space_type>
    struct parser : qi::grammar<It, std::string(), Skipper>
{
    parser() : parser::base_type(start)
    {
        static const std::vector<std::string> not_accepted { "}}", "]]" };

        using namespace qi;
        exclude = exclusions(not_accepted);
        start = *(char_ - exclude);

        BOOST_SPIRIT_DEBUG_NODE(start);
    }

  private:
    qi::rule<It, std::string(), Skipper> start;

    typedef qi::symbols<char, qi::unused_type> Exclude;
    Exclude exclude;

    template<typename Elements>
    Exclude exclusions(Elements const& elements) {
        Exclude result;

        for(auto& el : elements)
            result.add(el);

        return result;
    }
};

A full working sample of this is here: http://coliru.stacked-crooked.com/view?id=ddbb2549674bfed90e3c8df33b048574-7616891f9fd25da6391c2728423de797 and it prints

parse success
data: 123
trailing unparsed: ']] 4'

Full code

For future reference:

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

namespace qi    = boost::spirit::qi;

template <typename It, typename Skipper = qi::space_type>
    struct parser : qi::grammar<It, std::string(), Skipper>
{
    parser() : parser::base_type(start)
    {
        static const std::vector<std::string> not_accepted { "}}", "]]" };

        using namespace qi;
        exclude = exclusions(not_accepted);
        start = *(char_ - exclude);

        BOOST_SPIRIT_DEBUG_NODE(start);
    }

  private:
    qi::rule<It, std::string(), Skipper> start;

    typedef qi::symbols<char, qi::unused_type> Exclude;
    Exclude exclude;

    template<typename Elements>
    Exclude exclusions(Elements const& elements) {
        Exclude result;

        for(auto& el : elements)
            result.add(el);

        return result;
    }
};

int main()
{
    const std::string input = "1 2 3]] 4";
    typedef std::string::const_iterator It;
    It f(begin(input)), l(end(input));

    parser<It> p;
    std::string data;

    bool ok = qi::phrase_parse(f,l,p,qi::space,data);
    if (ok)   
    {
        std::cout << "parse success
";
        std::cout << "data: " << data << "
";
    }
    else std::cerr << "parse failed: '" << std::string(f,l) << "'
";

    if (f!=l) std::cerr << "trailing unparsed: '" << std::string(f,l) << "'
";
}


1 I believe this problem is about to be removed in the upcoming new version of Spirit (currently dubbed "Spirit X3" for the experimental version)

2 It uses Tries to lookup the matches

相关文章