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;
}
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 进行的简化拍摄
#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 版本,它使用语义动作来正确地累积相似因子:
#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
我使用 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;
}
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 进行的简化拍摄
#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 版本,它使用语义动作来正确地累积相似因子:
#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