【问题标题】:Boost spirit X3 : how to process in case of an optional that can be nullopt in an alternative case?Boost spirit X3:如果在另一种情况下可以为nullopt的可选情况如何处理?
【发布时间】:2021-11-14 02:16:42
【问题描述】:

如果是另一种情况,在一个路径中没有任何东西可以匹配可选,如何处理?

考虑这个 mvce。这不是我的真实示例,而是我可以想象的最小示例来表达我打算做什么:

解析为具有 3 个字段的 foo AST。第二个是可选的,可能是 nullopt。第三个字段 int 有一个 解析验证 规则,该规则取决于第二个字段的存在与否。

在这个例子中,如果有一个 double,那么 int 必须是偶数,否则它必须是奇数。

Valid cases 

foobar:3.14;4 
foobar;4 
foobar|5 

Invalid cases
foobar:3.14;5 
foobar;5 
foobar|4 
foobar:3.14|4 

#include <iostream>
#include <string>
#include <optional>

#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/home/x3.hpp>

namespace x3 = boost::spirit::x3;

namespace ast{
    struct foo {
        std::string string_value;
        std::optional<double> optional_double_value;
        int int_value;
    };
    
}

template <typename T>
std::ostream& operator<<(std::ostream& os, const std::optional<T> & opt)
{
    return opt ? os << opt.value() : os << "nullopt";
};

std::ostream& operator<<(std::ostream& os, const ast::foo & foo)
{
    return os << "string_value :"<<  foo.string_value << " optional_double : " << foo.optional_double_value << " int : " << foo.int_value;
};


BOOST_FUSION_ADAPT_STRUCT(ast::foo, string_value, optional_double_value,int_value)

namespace parser {

 
    const auto even_int = x3::rule<struct even_int, int> {"even int"}
    = x3::int_ [ ([](auto& ctx) {
        auto& attr = x3::_attr(ctx);
        auto& val  = x3::_val(ctx);
        val = attr;
        x3::_pass(ctx) = x3::_val(ctx) %2 == 0;
    }) ];
    

    const auto odd_int = x3::rule<struct even_int, int> {"odd int"}
    = x3::int_ [ ([](auto& ctx) {
        auto& attr = x3::_attr(ctx);
        auto& val  = x3::_val(ctx);
        val = attr;
        x3::_pass(ctx) = x3::_val(ctx) %2 == 1;
    }) ];
    
    const auto foo =  ( *x3::alpha  >> -(':' >> x3::double_) >> ';' >> even_int )
                       ;//|  (  *x3::alpha >>  '|' >> odd_int ) ;
                 
}


template <typename Parser, typename Attr>
static inline bool parse(std::string_view in, Parser const& p, Attr& result)
{
    return x3::parse(in.begin(), in.end(), p, result);
}

int main()
{
    for (auto& input : { "foobar:3.14;4", "foobar;4","foobar|5"}) {
        ast::foo result;
        if (!parse(input, parser::foo, result))
            std::cout << "parsing " << input << " failed" << std::endl;
        else
            std::cout << "parsing " << input << " success : " << result <<  std::endl;
    }
}

取消注释奇数 int raise 的第二种选择

/usr/local/include/boost/spirit/home/x3/operator/detail/sequence.hpp:144:25: error: static assertion failed: Size of the passed attribute is bigger than expected.

  144 |             actual_size <= expected_size

我理解是因为嗯,应该有 3 个的地方有两个“令牌”。如何处理?

额外问题:

为什么

 auto even_int = x3::rule<struct even_int, int> {"even int"}
    = ...

不能简单地定义为

 auto even_int = ...;

(本例编译失败)

【问题讨论】:

    标签: c++ boost boost-spirit-x3


    【解决方案1】:

    所有症状(包括奖励问题)都是不完善的属性传播机制的症状。

    自动属性传播非常好,但仍然会有你必须帮助系统的情况。

    查看您想要的规则和结果:

    const auto foo
        = *x3::alpha >> -(':' >> x3::double_) >> ';' >> even_int
        | *x3::alpha >> '|' >> odd_int
        ;
    

    我得出的结论是,您需要相同的规则,只是没有可选的双数用于偶数序数,并且对偶数和奇数使用不同的分隔符。

    我会尝试更接近解析器表达式的声明性质,并尝试使判断更加高级。例如

    Live On Coliru

    #include <boost/fusion/include/adapt_struct.hpp>
    #include <boost/spirit/home/x3.hpp>
    #include <iomanip>
    #include <optional>
    
    namespace ast {
        enum class discriminator { even, odd };
        struct foo {
            std::string           s;
            std::optional<double> od;
            discriminator         ind;
            int                   id;
    
            bool is_valid() const {
                bool is_even = 0 == (id % 2);
                switch (ind) {
                  case discriminator::even: return is_even;
                  case discriminator::odd: return not(is_even or od.has_value());
                  default: return false;
                }
            }
        };
    
        std::ostream& operator<<(std::ostream& os, const foo& foo)
        {
            os << std::quoted(foo.s); //
            if (foo.od.has_value())
                os << "(" << *foo.od << ")";
            return os << " " << foo.id //
                      << " (" << (foo.is_valid() ? "valid" : "INVALID") << ")";
        }
    } // namespace ast
    
    BOOST_FUSION_ADAPT_STRUCT(ast::foo, s, od, ind, id)
    
    namespace parser {
        namespace x3 = boost::spirit::x3;
    
        static const auto indicator_ = [] {
            x3::symbols<ast::discriminator> sym;
            sym.add                        //
                (";", ast::discriminator::even) //
                ("|", ast::discriminator::odd);
            return sym;
        }();
    
        static const auto foo //
            = +x3::alpha >> -(':' >> x3::double_) >> indicator_ >> x3::int_;
    }
    
    int main()
    {
        for (std::string const input : {
                 "foobar:3.14;4",
                 "foobar;4",
                 "foobar|5",
    
                 // Invalid cases
                 "foobar:3.14;5",
                 "foobar;5",
                 "foobar|4",
                 "foobar:3.14|4",
             }) //
        {
            ast::foo result;
            if (parse(input.begin(), input.end(), parser::foo, result))
                std::cout << std::quoted(input) << " -> " << result << std::endl;
            else
                std::cout << std::quoted(input) << " Syntax error" << std::endl;
        }
    }
    

    打印

    "foobar:3.14;4" -> "foobar"(3.14) 4 (valid)
    "foobar;4" -> "foobar" 4 (valid)
    "foobar|5" -> "foobar" 5 (valid)
    "foobar:3.14;5" -> "foobar"(3.14) 5 (INVALID)
    "foobar;5" -> "foobar" 5 (INVALID)
    "foobar|4" -> "foobar" 4 (INVALID)
    "foobar:3.14|4" -> "foobar"(3.14) 4 (INVALID)
    

    请注意,您可以将此方法视为语法和语义的分离。

    替代方案/从这里改进

    当然你现在可以把解析写成

    return parse(input.begin(), input.end(), parser::foo, result)
        && result.is_valid();
    

    或者,如果您坚持可以像以前一样将该检查封装在语义操作中:

    auto is_valid_ = [](auto& ctx) {
        _pass(ctx) = _val(ctx).is_valid();
    };
    
    static const auto foo                              //
        = x3::rule<struct foo_, ast::foo, true>{"foo"} //
        = (+x3::alpha >> -(':' >> x3::double_) >> indicator_ >>
           x3::int_)[is_valid_];
    

    现在输出变成:

    Live On Coliru

    "foobar:3.14;4" -> "foobar"(3.14) 4 (valid)
    "foobar;4" -> "foobar" 4 (valid)
    "foobar|5" -> "foobar" 5 (valid)
    "foobar:3.14;5" Syntax error
    "foobar;5" Syntax error
    "foobar|4" Syntax error
    "foobar:3.14|4" Syntax error
    

    没有融合

    现在,上面明确地仍然使用具有自动属性传播的融合序列自适应。但是,由于您无论如何都深入研究语义操作¹,您当然可以在那里完成其余的工作:

    Live On Coliru

    #include <boost/spirit/home/x3.hpp>
    #include <iomanip>
    #include <optional>
    
    namespace ast {
        struct foo {
            std::string           s;
            std::optional<double> od;
            int                   id;
        };
    
        std::ostream& operator<<(std::ostream& os, const foo& foo)
        {
            os << std::quoted(foo.s); //
            if (foo.od.has_value())
                os << "(" << *foo.od << ")";
            return os << " " << foo.id;
        }
    } // namespace ast
    
    namespace parser {
        namespace x3 = boost::spirit::x3;
        enum class discriminator { even, odd };
    
        static const auto indicator_ = [] {
            x3::symbols<discriminator> sym;
            sym.add                        //
                (";", discriminator::even) //
                ("|", discriminator::odd);
            return sym;
        }();
    
        auto make_foo = [](auto& ctx) {
            using boost::fusion::at_c;
            auto& attr = _attr(ctx);
            auto& s    = at_c<0>(attr); // where are
            auto& od   = at_c<1>(attr); // structured bindings
            auto& ind  = at_c<2>(attr); // when you
            auto& id   = at_c<3>(attr); // need them? :|
    
            bool  is_even = 0 == (id % 2);
    
            if (ind == discriminator::even)
                _pass(ctx) = is_even;
            else
                _pass(ctx) = not(is_even or od.has_value());
    
            _val(ctx) = ast::foo{
                std::move(s),
                od.has_value() ? std::make_optional(*od) : std::nullopt, id};
        };
    
        static const auto foo = x3::rule<struct foo_, ast::foo> {}
            = (+x3::alpha >> -(':' >> x3::double_) >> indicator_ >>
               x3::int_)[make_foo];
    } // namespace parser
    
    int main()
    {
        for (std::string const input : {
                 "foobar:3.14;4",
                 "foobar;4",
                 "foobar|5",
    
                 // Invalid cases
                 "foobar:3.14;5",
                 "foobar;5",
                 "foobar|4",
                 "foobar:3.14|4",
             }) //
        {
            ast::foo result;
    
            if (parse(input.begin(), input.end(), parser::foo, result))
                std::cout << std::quoted(input) << " -> " << result << std::endl;
            else
                std::cout << std::quoted(input) << " Syntax error" << std::endl;
        }
    }
    

    这有利有弊。优点是

    • 缩短编译时间
    • discriminator 现在是解析器私有的

    缺点:

    • 你在做手动传播(比如boost::optional->std::optional,这很笨拙)
    • 语义操作¹

    混合

    正如您可能知道的那样,我不喜欢手写属性传播屈膝。如果您必须从 ast 中隐藏 ind 字段,或许可以这样做:

    Live On Coliru

    #include <boost/fusion/adapted/struct.hpp>
    #include <boost/spirit/home/x3.hpp>
    #include <iomanip>
    #include <optional>
    
    namespace ast {
        struct foo {
            std::string           s;
            std::optional<double> od;
            int                   id;
        };
    
        std::ostream& operator<<(std::ostream& os, const foo& foo)
        {
            os << std::quoted(foo.s); //
            if (foo.od.has_value())
                os << "(" << *foo.od << ")";
            return os << " " << foo.id;
        }
    } // namespace ast
    
    namespace parser {
        namespace x3 = boost::spirit::x3;
        enum class discriminator { even, odd };
    
        struct p_foo : ast::foo {
            discriminator ind;
    
            struct semantic_error : std::runtime_error {
                using std::runtime_error::runtime_error;
            };
    
            void check_semantics() const {
                bool is_even = 0 == (id % 2);
                switch (ind) {
                  case discriminator::even:
                      if (!is_even)
                          throw semantic_error("id should be even");
                      break;
                  case discriminator::odd:
                      if (is_even)
                          throw semantic_error("id should be odd");
                      if (od.has_value())
                          throw semantic_error("illegal double at odd foo");
                      break;
                  }
            }
        };
    }
    
    BOOST_FUSION_ADAPT_STRUCT(parser::p_foo, s, od, ind, id)
    
    namespace parser {
        static const auto indicator_ = [] {
            x3::symbols<discriminator> sym;
            sym.add                        //
                (";", discriminator::even) //
                ("|", discriminator::odd);
            return sym;
        }();
    
        static const auto raw_foo      //
            = x3::rule<p_foo, p_foo>{} //
            = +x3::alpha >> -(':' >> x3::double_) >> indicator_ >> x3::int_;
    
        auto checked_ = [](auto& ctx) {
            auto& _pf = _attr(ctx);
            _pf.check_semantics();
            _val(ctx) = std::move(_pf);
        };
        static const auto foo                   //
            = x3::rule<struct foo_, ast::foo>{} //
            = raw_foo[checked_];
    } // namespace parser
    
    int main()
    {
        for (std::string const input : {
                 "foobar:3.14;4",
                 "foobar;4",
                 "foobar|5",
    
                 // Invalid cases
                 "foobar:3.14;5",
                 "foobar;5",
                 "foobar|4",
                 "foobar:3.14|4",
                 "foobar:3.14|5",
             }) //
        {
            ast::foo result;
    
            try {
            if (parse(input.begin(), input.end(), parser::foo, result))
                std::cout << std::quoted(input) << " -> " << result << std::endl;
            else
                std::cout << std::quoted(input) << " Syntax error" << std::endl;
            } catch(std::exception const& e) {
                std::cout << std::quoted(input) << " Semantic error: " << e.what() << std::endl;
            }
        }
    }
    

    打印

    "foobar:3.14;4" -> "foobar"(3.14) 4
    "foobar;4" -> "foobar" 4
    "foobar|5" -> "foobar" 5
    "foobar:3.14;5" Semantic error: id should be even
    "foobar;5" Semantic error: id should be even
    "foobar|4" Semantic error: id should be odd
    "foobar:3.14|4" Semantic error: id should be odd
    "foobar:3.14|5" Semantic error: illegal double at odd foo
    

    注意更丰富的诊断信息。


    后脚本:最小的变化

    后来,重新阅读您的问题,我突然意识到有一个较小的变化可以帮助您的语法。我用下面的话介绍了我的答案:

    自动属性传播非常好,但仍会出现需要帮助系统的情况

    在这里,您可以通过使两个分支具有相同的结构来帮助它。所以不是

    const auto foo
        = *x3::alpha >> -(':' >> x3::double_) >> ';' >> even_int
        | *x3::alpha >> '|' >> odd_int
        ;
    

    您可以在奇数分支的中间手动插入一个空的可选双精度:

    const auto foo                                               //
        = +x3::alpha >> -(':' >> x3::double_) >> ';' >> even_int //
        | +x3::alpha >> x3::attr(ast::optdbl{}) >> '|' >> odd_int;
    

    (其中optdbl 是std::optional&lt;double&gt; 风格的别名)。

    现在,如果你稍微重构一下 odd_int/even_int 规则,我会说这个方法比上面的其他选项更重要:

    Live On Coliru

    #include <boost/fusion/include/adapt_struct.hpp>
    #include <boost/spirit/home/x3.hpp>
    #include <iomanip>
    #include <optional>
    
    namespace ast{
        using optdbl = std::optional<double>;
    
        struct foo {
            std::string s;
            optdbl      od;
            int         id;
        };
    
        std::ostream& operator<<(std::ostream& os, const foo& foo)
        {
            os << std::quoted(foo.s); //
            if (foo.od.has_value())
                os << "(" << *foo.od << ")";
            return os << " " << foo.id;
        }
    }
    
    BOOST_FUSION_ADAPT_STRUCT(ast::foo, s, od,id)
    
    namespace parser {
        namespace x3 = boost::spirit::x3;
    
        static auto mod2check(int remainder) {
            return [=](auto& ctx) { //
                _pass(ctx) = _val(ctx) % 2 == remainder;
            };
        }
    
        static auto mod2int(int remainder) {
            return x3::rule<struct _, int, true>{} = x3::int_[mod2check(remainder)];
        }
    
        const auto foo                                           //
            = +x3::alpha >>                                      //
            (-(':' >> x3::double_) | x3::attr(ast::optdbl{})) >> //
            (';' >> mod2int(0) | '|' >> mod2int(1))              //
            ;
    } // namespace parser
    
    int main()
    {
        for (std::string const input : {
                 "foobar:3.14;4",
                 "foobar;4",
                 "foobar|5",
    
                 // Invalid cases
                 "foobar:3.14;5",
                 "foobar;5",
                 "foobar|4",
                 "foobar:3.14|4",
             }) //
        {
            ast::foo result;
            if (parse(input.begin(), input.end(), parser::foo, result))
                std::cout << std::quoted(input) << " -> " << result << std::endl;
            else
                std::cout << std::quoted(input) << " Syntax error" << std::endl;
        }
    }
    

    ¹Boost Spirit: "Semantic actions are evil"?

    【讨论】:

    • 我想到了一个较小的修复程序,它可能会给您带来最大的启发。在“Post Scriptum”的末尾添加
    • 哦,笨蛋。这会遇到字符串回滚问题。("foobarfoobar") 毕竟选择一种没有替代分支的方法可能更好。
    • 当分支失败时,在替代解析器上遇到臭名昭著的非自动回滚问题......我肯定更喜欢脚本后的最后一个解决方案,因为我的真实例子是:我有一个令牌 A ,然后可能是 B,然后是 C 或 A 和 C',其中大写字母是 C++ 类型,' 只是不同的解析规则(在 C 和 C' 之间)。有没有办法“分解”初始的 A 解析,这很常见并且可以避免回滚问题?
    • 不。 x3::raw[] just synthesizes the same value, the propagation still appends. 好的。有什么反对总是解析成“超集序列”并在上面进行一些验证?在某些时候,你必须务实。我不认为 Spirit 是指导您的解析器的框架。您可能应该瞄准最佳位置,或者使用其他东西(例如,后处理 AST 是一个可行的选择,有点像我的“混合”选项,实际上仍然在规则之内)
    • 太棒了。戴上你的护目镜:coliru.stacked-crooked.com/a/2fe4139f77bff23e 展示了我目前可以用clumsy_manual_propagate 和分解分支做的最好的事情。请注意,我通过将std::optional 更改为boost::optional 仍然有点作弊。我认为上面的第二个版本(“没有融合”)严格来说是这样,但更好。 (它确实使用std::optional)
    【解决方案2】:

    如果是另一种情况,在一个路径中没有任何东西可以匹配可选,如何处理?

    对于这种情况,有attr(x) 解析器。每次“解析”它都会生成x 的副本,而不消耗任何输入。

    所以答案

    如果一个可选的情况在另一种情况下可以为nullopt,如何处理?

    就是用attr(std::nullopt),像这样:

        const auto foo =  ( *x3::alpha  >> -(':' >> x3::double_) >> ';' >> even_int )
                           |  (  *x3::alpha >> x3::attr(std::nullopt) >>  '|' >> odd_int ) ;
    

    https://godbolt.org/z/E5jM6s6vW

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-12-30
      • 2014-05-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多