Boost::Spirit 后跟默认值时加倍字符

Boost::Spirit doubles character when followed by a default value

我使用 boost::spirit 来解析单项式的(一部分),例如 x、y、xy、x^2、x^3yz。我想将单项式的变量保存到一个映射中,该映射还存储相应的指数。因此语法也应该保存 1 的隐式指数(所以 x 存储就像它被写成 x^1)。

start = +(potVar);
potVar=(varName>>'^'>>exponent)|(varName>> qi::attr(1));// First try: This doubles the variable name
//potVar = varName >> (('^' >> exponent) |  qi::attr(1));// Second try: This works as intended
exponent = qi::int_;
varName  = qi::char_("a-z");

当使用“第一次尝试”行中的默认属性时,Spirit 将变量名加倍。
当使用“第二次尝试”行中的默认属性时,一切都按预期工作。

'First try' 读取变量 x 并存储对 [xx, 1].
'Second try' 读取变量 x 并存储对 [x, 1].
我想我自己解决了原来的问题。第二次尝试有效。但是,我看不出我是如何将变量名加倍的。因为我即将熟悉 boost::spirit,这对我来说是一个 collection 的挑战,并且可能还会有更多挑战,所以我想了解这种行为。

这是重现问题的全部代码。语法框架是从 KIT https://panthema.net/2018/0912-Boost-Spirit-Tutorial/ 的演示文稿中复制的,当我需要 header 时,Whosebug 已经非常有帮助,这使我能够使用 std::pair.

#include <iostream>
#include <iomanip>
#include <stdexcept>
#include <cmath>
#include <map>
#include <utility>//for std::pair

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

namespace qi = boost::spirit::qi;

template <typename Parser, typename Skipper, typename ... Args>
void PhraseParseOrDie(
    const std::string& input, const Parser& p, const Skipper& s,
    Args&& ... args)
{
    std::string::const_iterator begin = input.begin(), end = input.end();
    boost::spirit::qi::phrase_parse(
        begin, end, p, s, std::forward<Args>(args) ...);
    if (begin != end) {
        std::cout << "Unparseable: "
            << std::quoted(std::string(begin, end)) << std::endl;
        throw std::runtime_error("Parse error");
    }
}

class ArithmeticGrammarMonomial : public qi::grammar<
    std::string::const_iterator,
    std::map<std::string, int>(), qi::space_type>
{
public:
    using Iterator = std::string::const_iterator;

    ArithmeticGrammarMonomial() : ArithmeticGrammarMonomial::base_type(start)
    {
        start = +(potVar);
        potVar=(varName>>'^'>>exponent)|(varName>> qi::attr(1));
        //potVar = varName >> (('^' >> exponent) |  qi::attr(1));
        exponent = qi::int_;
        varName  = qi::char_("a-z");
    }


    qi::rule<Iterator, std::map<std::string, int>(), qi::space_type> start;
    qi::rule<Iterator, std::pair<std::string, int>(), qi::space_type> potVar;
    qi::rule<Iterator, int()> exponent;
    qi::rule<Iterator, std::string()> varName;
};

void test2(std::string input)
{
    
    std::map<std::string, int> out_map;
    PhraseParseOrDie(input, ArithmeticGrammarMonomial(), qi::space, out_map);

    std::cout << "test2() parse result: "<<std::endl;
    for(auto &it: out_map)
        std::cout<< it.first<<it.second << std::endl;
}

/******************************************************************************/

int main(int argc, char* argv[])
{
    std::cout << "Parse Monomial 1" << std::endl;
    test2(argc >= 2 ? argv[1] : "x^3y^1");
    test2(argc >= 2 ? argv[1] : "xy");
    return 0;
}

Live demo

I think I solved the original problem myself. The second try works.

确实如此。这就是我这样做的方式(始终将 AST 与您的解析器表达式相匹配)。

However, I don't see how I doubled the variable name.

这是由于容器属性的回溯。他们不会回滚。所以第一个分支将potVar解析成一个字符串,然后解析器回溯到第二个分支,第二个分支将potVar解析成同一个字符串

  • boost::spirit::qi duplicate parsing on the output
  • Understanding Boost.spirit's string parser
  • Parsing with Boost::Spirit (V2.4) into container
  • Boost Spirit optional parser and backtracking
  • boost::spirit alternative parsers return duplicates

它还可以出现语义动作:

  • Boost Spirit optional parser and backtracking

简而言之:

  • 在你的规则表达式中匹配你的 AST 结构,或者使用 qi::hold 来强制这个问题(以性能为代价)

  • 避免语义操作 (Boost Spirit: "Semantic actions are evil"?)

为了获得灵感,下面是使用 Spirit X3 进行的简化拍摄

Live On Compiler Explorer

#include <boost/fusion/adapted.hpp>
#include <boost/spirit/home/x3.hpp>
#include <fmt/ranges.h>
#include <map>

namespace Parsing {
    namespace x3 = boost::spirit::x3;

    auto exponent = '^' >> x3::int_ | x3::attr(1);
    auto varName  = x3::repeat(1)[x3::char_("a-z")];

    auto potVar
        = x3::rule<struct P, std::pair<std::string, int>>{}
        = varName >> exponent;
    auto start  = x3::skip(x3::space)[+potVar >> x3::eoi];

    template <typename T = x3::unused_type>
    void StrictParse(std::string_view input, T&& into = {})
    {
        auto f = input.begin(), l = input.end();

        if (!x3::parse(f, l, start, into)) {
            fmt::print(stderr, "Error at: '{}'\n", std::string(f, l));
            throw std::runtime_error("Parse error");
        }
    }
} // namespace Parsing

void test2(std::string input) {
    std::map<std::string, int> out_map;
    Parsing::StrictParse(input, out_map);

    fmt::print("{} -> {}\n", input, out_map);
}

int main() {
    for (auto s : {"x^3y^1", "xy"})
        test2(s);
}

版画

x^3y^1 -> [("x", 3), ("y", 1)]
xy -> [("x", 1), ("y", 1)]

奖金

在我看来你应该更加小心。即使您假设所有变量都是 1 个字母并且不会出现任何项(只有因子),那么您仍然需要正确处理 x^5y^2x 成为 x^6y^2 对吗?

这里是 Qi 版本,它使用语义动作来正确地累积相似因子:

Live On Coliru

#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iomanip>
#include <iostream>
#include <map>

namespace qi   = boost::spirit::qi;
using Iterator = std::string::const_iterator;
using Monomial = std::map<char, int>;

struct ArithmeticGrammarMonomial : qi::grammar<Iterator, Monomial()> {
    ArithmeticGrammarMonomial() : ArithmeticGrammarMonomial::base_type(start) {
        using namespace qi;
        exp_  = '^' >> int_ | attr(1);
        start = skip(space)[                        //
            +(char_("a-z") >> exp_)[_val[_1] += _2] //
        ];
    }

  private:
    qi::rule<Iterator, Monomial()>            start;
    qi::rule<Iterator, int(), qi::space_type> exp_;
};

void do_test(std::string_view input) {
    Monomial output;

    static const ArithmeticGrammarMonomial p;
    Iterator f(begin(input)), l(end(input));
    qi::parse(f, l, qi::eps > p, output);

    std::cout << std::quoted(input) << " -> " << std::endl;
    for (auto& [var,exp] : output)
        std::cout << " - " << var << '^' << exp << std::endl;
}

int main() {
    for (auto s : {"x^3y^1", "xy", "x^5y^2x"})
        do_test(s);
}

版画

"x^3y^1" ->
 - x^3
 - y^1
"xy" ->
 - x^1
 - y^1
"x^5y^2x" ->
 - x^6
 - y^2