Boost::Spirit - 通过语义操作创建 class(也许通过 C++ lambda?)

Boost::Spirit - Create class by semantic action (maybe by a C++ lambda?)

我的目标是通过 boost::spirit 解析某些内容,使用语义操作创建 class(并可能将其放入容器中)。

我设法使用 boost::spirit 使用 BOOST_FUSION_ADAPT_STRUCT 宏创建了一个 class。

//The struct
using stringvec = std::string;
struct CVar
{
public:
    stringvec sVariable;
    CVar(stringvec sVariable) : sVariable(sVariable) {}
    CVar() {}
};
//The macro gluing everything
BOOST_FUSION_ADAPT_STRUCT(
    ::CVar,
    (stringvec, sVariable)
)
//The core of the grammar
varName = qi::char_("a-z");
start = *varName;
qi::rule<Iterator, std::string()> varName;
qi::rule<Iterator, std::vector<CVar>() > start;

http://coliru.stacked-crooked.com/a/56dd8325f854a8c9

Actually, I would have liked to omit the vector surrounding the class, but I ran into problems http://coliru.stacked-crooked.com/a/3719cee9c7594254 I thought I managed to match a class as the root of the tree at some point. The error message looks, like the adapt struct really wants to call push_back at some point. And somehow, it makes sense, in case there is no class to store.

但是,我想通过语义操作调用构造函数。

     start = (varName[qi::_val = boost::phoenix::construct<CVar>(qi::_1)]);

Coliru helped me a lot by printing a very direct error message (which I couldn't say about VSC++2022).\ error: no match for 'operator=' (operand types are 'std::vector' and 'CVar') http://coliru.stacked-crooked.com/a/7305e8483ee83b22 \

此外,我不想使用太多 boost::bind 或凤凰结构。一个普通的 lambda 会很好。 我知道,[] 运算符已经提供了一种 boost::lambda。但是,我不确定如何使用占位符 qi::_val 和 qi::_1。 我是否应该在 lambda 表达式中捕获它们以执行类似 qi::_val.push_back(Cvar(qi::_1))?

我研究的一些方向:

我原以为这样的东西会起作用:

[&]() { qi::_val.push_back(CVar((std::string)qi::_1)) }

但我怎样才能真正让它发挥作用?

要不是我不想用boost,为了push_back,我可以用boost::phoenix::construct(qi::_1),不行吗?

我觉得这个类似的问题很重要How to build a synthesized argument from a C++11 lambda semantic action in boost spirit? 我仍在寻找如何使用上下文来访问 qi::_val 和 qi::_1。 看起来我正在做某事,但我已经尝试了很多,我可能不得不搜索如何阅读 Visual Studio 错误的教程,或者我应该考虑切换编译器。

P.S.: 在 VSC++2022 中,我还收到两条警告,内容为“boost::function_base::functor 未初始化且 boost::detail::local_counted_base::count_type 没有范围限制。将枚举 class 排在枚举之前。如何触发它们?

有很多事情让我感到困惑。你说“我想要 XYZ”,但没有任何理由说明为什么那样更好。此外,您对 不同的 方法陈述相同,所以我不知道您更喜欢哪一种(以及为什么)。

示例代码定义

using stringvec = std::string;

这令人困惑,因为 stringvec 顾名思义 vector<string>,而不是 string?现在它看起来更像是 CVar “像一个字符串”,而你的属性是 vector<CVar>,即像一个字符串向量,而不是 std::string.

总而言之,我可以给你以下提示:

  • 一般来说,避免语义动作。它们对编译器、泄漏的抽象、选择退出属性兼容性¹、在回溯下产生原子性问题很重要(参见 Boost Spirit: "Semantic actions are evil"?

  • 其次,如果您使用语义操作,请意识到更多解析器表达式的原始合成属性是 Fusion sequences/containers.

    • 特别是要获得 std::string 使用 qi::as_string[]。如果你不使用语义动作,确实这种属性 compatibility/transformation 是自动的¹。
    • 类似地,要将单个项目解析为显式容器,请使用 repeat(1)[p]
  • 除了依赖语义操作的所有缺点外,构造函数的工作方式与您在 phx::construct<> 中展示的一样

Side observation: did you notice I reduced parser/AST friction in by replacing std::string with char?

应用答案:

Q. Actually, I would have liked to omit the vector surrounding the class, but I ran into problems http://coliru.stacked-crooked.com/a/3719cee9c7594254 I thought I managed to match a class as the root of the tree at some point. The error message looks, like the adapt struct really wants to call push_back at some point. And somehow, it makes sense, in case there is no class to store.

使用 as_string 进行了简化:Live On Coliru

#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iomanip>
#include <iostream>
namespace qi   = boost::spirit::qi;
using Iterator = std::string::const_iterator;

using stringval = std::string;
struct CVar { stringval sVariable; };
BOOST_FUSION_ADAPT_STRUCT(CVar, sVariable)

struct TestGrammar : qi::grammar<Iterator, CVar()> {
    TestGrammar() : TestGrammar::base_type(start) {
        start = qi::as_string[qi::char_("a-z")];
    }

private:
    qi::rule<Iterator, CVar() > start;
};

void do_test(std::string const& input) {
    CVar output;

    static const TestGrammar p;

    auto f = input.begin(), l = input.end();
    qi::parse(f, l, p, output);

    std::cout << std::quoted(input) << " -> " << std::quoted(output.sVariable) << "\n";
}

使用repeat(1)Live On Coliru

#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iomanip>
#include <iostream>
namespace qi   = boost::spirit::qi;
using Iterator = std::string::const_iterator;

using stringval = std::string;
struct CVar { stringval sVariable; };
BOOST_FUSION_ADAPT_STRUCT(CVar, sVariable)

struct TestGrammar : qi::grammar<Iterator, CVar()> {
    TestGrammar() : TestGrammar::base_type(start) {
        cvar  = qi::repeat(1)[qi::char_("a-z")];
        start = cvar;
    }

  private:
    qi::rule<Iterator, CVar()> start;
    qi::rule<Iterator, std::string()> cvar;
};

void do_test(std::string const& input) {
    CVar output;

    static const TestGrammar p;

    auto f = input.begin(), l = input.end();
    qi::parse(f, l, p, output);

    std::cout << std::quoted(input) << " -> " << std::quoted(output.sVariable) << "\n";
}

不使用 ADAPT_STRUCT:minimal change vs: simplified

#include <boost/spirit/include/qi.hpp>
#include <iomanip>
#include <iostream>
namespace qi   = boost::spirit::qi;
using Iterator = std::string::const_iterator;

using stringval = std::string;
struct CVar {
    CVar(std::string v = {}) : sVariable(std::move(v)) {}
    stringval sVariable;
};

struct TestGrammar : qi::grammar<Iterator, CVar()> {
    TestGrammar() : TestGrammar::base_type(start) {
        start = qi::as_string[qi::lower];
    }

  private:
    qi::rule<Iterator, CVar()> start;
};

使用语义操作(不推荐):4 种实时模式

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <iomanip>
#include <iostream>
namespace qi   = boost::spirit::qi;
namespace px   = boost::phoenix;
using Iterator = std::string::const_iterator;

using stringval = std::string;
struct CVar {
    CVar(std::string v = {}) : sVariable(std::move(v)) {}
    stringval sVariable;
};

enum mode {
    AS_STRING_CONSTRUCT      = 1,
    DIRECT_ASSIGN            = 2,
    USING_ACTOR              = 3,
    TRANSPARENT_CXX14_LAMBDA = 4,
};

struct TestGrammar : qi::grammar<Iterator, CVar()> {
    TestGrammar(mode m) : TestGrammar::base_type(start) {
        switch (m) {
            case AS_STRING_CONSTRUCT: {
                    using namespace qi::labels;
                    start = qi::as_string[qi::lower][_val = px::construct<CVar>(_1)];
                    break;
                }
            case DIRECT_ASSIGN: {
                    // or directly
                    using namespace qi::labels;
                    start = qi::lower[_val = px::construct<std::string>(1ull, _1)];
                    break;
                }
            case USING_ACTOR: {
                    // or... indeed
                    using namespace qi::labels;
                    px::function as_cvar = [](char var) -> CVar { return {{var}}; };
                    start = qi::lower[_val = as_cvar(_1)];
                    break;
                }
                case TRANSPARENT_CXX14_LAMBDA: {
                    // or even more bespoke: (this doesn't require qi::labels or phoenix.hpp)
                    auto propagate = [](auto& attr, auto& ctx) {
                        at_c<0>(ctx.attributes) = {{attr}};
                    };
                    start          = qi::lower[propagate];
                    break;
                }
        }
    }

  private:
    qi::rule<Iterator, CVar()> start;
};

void do_test(std::string const& input, mode m) {
    CVar output;

    const TestGrammar p(m);

    auto f = input.begin(), l = input.end();
    qi::parse(f, l, p, output);

    std::cout << std::quoted(input) << " -> " << std::quoted(output.sVariable) << "\n";
}

int main() {
    for (mode m : {AS_STRING_CONSTRUCT, DIRECT_ASSIGN, USING_ACTOR,
                   TRANSPARENT_CXX14_LAMBDA}) {
        std::cout << " ==== mode #" << static_cast<int>(m) << " === \n";
        for (auto s : {"a", "d", "ac"})
            do_test(s, m);
    }
}

Just to demonstrate how the latter two approaches can both do without the constructor or even without any phoenix support.

As before, by that point I'd recommend going C++14 with Boost Spirit X3 anyways: http://coliru.stacked-crooked.com/a/dbd61823354ea8b6 or even 20 LoC: http://coliru.stacked-crooked.com/a/b26b3db6115c14d4


¹ 有一个非主要的“hack”可以通过定义 BOOST_SPIRIT_ACTIONS_ALLOW_ATTR_COMPAT

来帮助你