Boost::spirit 解析浮点数并对其进行格式化?
Boost::spirit parsing a float and also formatting it?
我有一个非常酷的浮点计算器实现 boost::spirit
。
它默认在 boost::spirit::qi::float_
上工作:它获取 std::string
输入,并计算表达式的结果 float
。
查看实际效果 here。
参考代码如下:
namespace calc {
namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
///////////////////////////////////////////////////////////////////////////
// Our calculator grammar
///////////////////////////////////////////////////////////////////////////
template <typename Iterator>
struct calculator : qi::grammar<Iterator, float(), ascii::space_type>
{
calculator() : calculator::base_type(expression)
{
using qi::_val;
using qi::_1;
using qi::float_;
expression =
term [_val = _1]
>> *( ('+' >> term [_val += _1])
| ('-' >> term [_val -= _1])
)
;
term =
factor [_val = _1]
>> *( ('*' >> factor [_val *= _1])
| ('/' >> factor [_val /= _1])
)
;
factor =
float_ [_val = _1]
| '(' >> expression [_val = _1] >> ')'
| ('-' >> factor [_val = -_1])
| ('+' >> factor [_val = _1])
;
}
qi::rule<Iterator, float(), ascii::space_type> expression, term, factor;
};
}
typedef calc::calculator<std::string::const_iterator> calculator;
int main()
{
calculator calc;
std::string expression = "3*5";
float result = 0;
std::string::const_iterator iter = expression.begin();
std::string::const_iterator end = expression.end();
std::stringstream resultstream;
bool r = boost::spirit::qi::phrase_parse(iter, end, calc, boost::spirit::ascii::space, result);
if (! (r && iter == end)) {
result = 0;
}
resultstream.clear();
resultstream << result;
std::cout << "Result: " << resultstream.str() << std::endl;
}
它将expression
的值计算为resultstream
。
工作完美,对于 3*5
它输出:
Result: 15
如果我将表达式更改为“5/3”,它会输出:
Result: 1.66667
我的愿望是总是有固定的位数:
对于 3*5:
Result: 15.0
对于 5/3:
Result: 1.7
我知道:将 std::setw
添加到 cout
可以解决这个问题。但我的目标是不同 (!):
我想直接从解析器中获取上述格式化结果到 resultstream
。
我的想法是让解析器解析更复杂的输入,例如:
3*5%.1 => 15.0
3*5%.2 => 15.00
3*5% => 15%
3*5%.2% => 15.00%
我该如何实现?是否值得更改计算器本身,或者它太重了,我应该更喜欢一些其他文本处理技术来解析所需的格式,并且仍然使用 std::setw
像这样:
resultstream << setw(required_width) << result;
My idea is to allow the parser to parse more complex inputs like:
3*5%.1 => 15.0
3*5%.2 => 15.00
3*5% => 15%
3*5%.2% => 15.00%
这告诉我,您并不是在创建表达式求值器,而是在制定格式规范。我同意其他人的看法:把你的顾虑分开。
因为它的价值 setw
对你没有帮助,但 std::fixed
和 std::setprecision
可能。无论如何,任何事情 C++ 都可以做,也可以发生在语义动作中,所以,这个地狱般的装置应该可以工作¹:
using calculator = calc::calculator<std::string::const_iterator>;
static calculator const calc;
for (std::string const expr : {"3*5", "5/3"}) {
std::stringstream result;
if (!parse(begin(expr), end(expr), (calc >> qi::eoi) //
[px::ref(result) << std::fixed << std::setprecision(1) << qi::_1])) {
result << "#ERROR"; // TODO FIXME error handling
}
std::cout << "Result: " << result.str() << std::endl;
}
看到了Live On Compiler Explorer,打印:
Result: 15.0
Result: 1.7
奖金
关于介绍梦:
How shall I achieve this? Is it worth changing the calculator itself,
or it's too heavy and I should prefer some other text processing
techniques to parse the required formatting and still do it with
std::setw like this:
不值得更换计算器,因为它不是计算器。它真的不是,一旦你用格式化的东西(即 non-expression 东西)扩展你的语法,肯定不再是了。
您当然可以创建这样的语法。让我们来描述一下 AST:
using Result = float;
namespace Formatting {
struct Format {
unsigned frac_digits;
std::string suffix_literal;
};
struct FormattedResult {
Result value;
Format spec;
friend std::ostream& operator<<(std::ostream& os, FormattedResult const& fr) {
auto& [val, fmt] = fr;
boost::io::ios_all_saver state(os);
return os << std::fixed << std::setprecision(fmt.frac_digits) << val << fmt.suffix_literal;
}
};
}
现在,我们可以制定顶层规则 return FormmattedResult
而不仅仅是 Result
(即 float
):
formatspec =
("%." >> precision | qi::attr(0u)) >> qi::raw[*qi::char_];
start = qi::skip(qi::space)[expression >> formatspec];
加上一些额外的声明:
using Skipper = qi::space_type;
qi::rule<Iterator, FormattedResult()> start;
qi::rule<Iterator, Result(), Skipper> expression, term, factor;
// lexemes:
qi::rule<Iterator, Format()> formatspec;
qi::real_parser<Result> number;
qi::uint_parser<unsigned, 10, 1, 2> precision;
//#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>
#include <boost/phoenix.hpp>
#include <boost/io/ios_state.hpp>
#include <iostream>
#include <iomanip>
namespace px = boost::phoenix;
namespace qi = boost::spirit::qi;
using Result = float;
namespace Formatting {
struct Format {
unsigned frac_digits;
std::string suffix_literal;
};
struct FormattedResult {
Result value;
Format spec;
friend std::ostream& operator<<(std::ostream& os, FormattedResult const& fr) {
auto& [val, fmt] = fr;
boost::io::ios_all_saver state(os);
return os << std::fixed << std::setprecision(fmt.frac_digits) << val << fmt.suffix_literal;
}
};
}
BOOST_FUSION_ADAPT_STRUCT(Formatting::Format, frac_digits, suffix_literal)
BOOST_FUSION_ADAPT_STRUCT(Formatting::FormattedResult, value, spec)
namespace Parsers {
using namespace Formatting;
template <typename Iterator>
struct FormattedExpression : qi::grammar<Iterator, FormattedResult()> {
FormattedExpression() : FormattedExpression::base_type(start) {
using qi::_1;
using qi::_val;
expression =
term [_val = _1]
>> *( ('+' >> term [_val += _1])
| ('-' >> term [_val -= _1])
)
;
term =
factor [_val = _1]
>> *( ('*' >> factor [_val *= _1])
| ('/' >> factor [_val /= _1])
)
;
factor =
number [_val = _1]
| '(' >> expression [_val = _1] >> ')'
| ('-' >> factor [_val = -_1])
| ('+' >> factor [_val = _1])
;
formatspec =
("%." >> precision | qi::attr(0u)) >> qi::raw[*qi::char_];
start = qi::skip(qi::space)[expression >> formatspec];
BOOST_SPIRIT_DEBUG_NODES((start)(expression)(
term)(factor)(formatspec))
}
private:
using Skipper = qi::space_type;
qi::rule<Iterator, FormattedResult()> start;
qi::rule<Iterator, Result(), Skipper> expression, term, factor;
// lexemes:
qi::rule<Iterator, Format()> formatspec;
qi::real_parser<Result> number;
qi::uint_parser<unsigned, 10, 1, 2> precision;
};
}
int main() {
using Parser = Parsers::FormattedExpression<std::string::const_iterator>;
static Parser const parser;
for (std::string const expr :
{
"3*5", //
"5/3", //
"5/3%.1", //
"5/3%.3...", //
"3*5%.1", // => 15.0
"3*5%.2", // => 15.00
"3*5%", // => 15%
"3*5%.2%", // => 15.00%
}) //
{
Formatting::FormattedResult fr;
if (parse(begin(expr), end(expr), parser >> qi::eoi, fr)) {
std::cout << std::left //
<< "Input: " << std::setw(12) << std::quoted(expr)
<< "Result: " << fr << "\n";
} else {
std::cout << std::left //
<< "Input: " << std::setw(12) << std::quoted(expr)
<< "Parse Error\n";
}
}
}
版画
Input: "3*5" Result: 15
Input: "5/3" Result: 2
Input: "5/3%.1" Result: 1.7
Input: "5/3%.3..." Result: 1.667...
Input: "3*5%.1" Result: 15.0
Input: "3*5%.2" Result: 15.00
Input: "3*5%" Result: 15%
Input: "3*5%.2%" Result: 15.00%
¹ 我希望我没有不小心让我的个人喜好暴露出来,但请参阅例如Boost Spirit: "Semantic actions are evil"?
我有一个非常酷的浮点计算器实现 boost::spirit
。
它默认在 boost::spirit::qi::float_
上工作:它获取 std::string
输入,并计算表达式的结果 float
。
查看实际效果 here。
参考代码如下:
namespace calc {
namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
///////////////////////////////////////////////////////////////////////////
// Our calculator grammar
///////////////////////////////////////////////////////////////////////////
template <typename Iterator>
struct calculator : qi::grammar<Iterator, float(), ascii::space_type>
{
calculator() : calculator::base_type(expression)
{
using qi::_val;
using qi::_1;
using qi::float_;
expression =
term [_val = _1]
>> *( ('+' >> term [_val += _1])
| ('-' >> term [_val -= _1])
)
;
term =
factor [_val = _1]
>> *( ('*' >> factor [_val *= _1])
| ('/' >> factor [_val /= _1])
)
;
factor =
float_ [_val = _1]
| '(' >> expression [_val = _1] >> ')'
| ('-' >> factor [_val = -_1])
| ('+' >> factor [_val = _1])
;
}
qi::rule<Iterator, float(), ascii::space_type> expression, term, factor;
};
}
typedef calc::calculator<std::string::const_iterator> calculator;
int main()
{
calculator calc;
std::string expression = "3*5";
float result = 0;
std::string::const_iterator iter = expression.begin();
std::string::const_iterator end = expression.end();
std::stringstream resultstream;
bool r = boost::spirit::qi::phrase_parse(iter, end, calc, boost::spirit::ascii::space, result);
if (! (r && iter == end)) {
result = 0;
}
resultstream.clear();
resultstream << result;
std::cout << "Result: " << resultstream.str() << std::endl;
}
它将expression
的值计算为resultstream
。
工作完美,对于 3*5
它输出:
Result: 15
如果我将表达式更改为“5/3”,它会输出:
Result: 1.66667
我的愿望是总是有固定的位数:
对于 3*5:
Result: 15.0
对于 5/3:
Result: 1.7
我知道:将 std::setw
添加到 cout
可以解决这个问题。但我的目标是不同 (!):
我想直接从解析器中获取上述格式化结果到 resultstream
。
我的想法是让解析器解析更复杂的输入,例如:
3*5%.1 => 15.0
3*5%.2 => 15.00
3*5% => 15%
3*5%.2% => 15.00%
我该如何实现?是否值得更改计算器本身,或者它太重了,我应该更喜欢一些其他文本处理技术来解析所需的格式,并且仍然使用 std::setw
像这样:
resultstream << setw(required_width) << result;
My idea is to allow the parser to parse more complex inputs like: 3*5%.1 => 15.0 3*5%.2 => 15.00 3*5% => 15% 3*5%.2% => 15.00%
这告诉我,您并不是在创建表达式求值器,而是在制定格式规范。我同意其他人的看法:把你的顾虑分开。
因为它的价值 setw
对你没有帮助,但 std::fixed
和 std::setprecision
可能。无论如何,任何事情 C++ 都可以做,也可以发生在语义动作中,所以,这个地狱般的装置应该可以工作¹:
using calculator = calc::calculator<std::string::const_iterator>;
static calculator const calc;
for (std::string const expr : {"3*5", "5/3"}) {
std::stringstream result;
if (!parse(begin(expr), end(expr), (calc >> qi::eoi) //
[px::ref(result) << std::fixed << std::setprecision(1) << qi::_1])) {
result << "#ERROR"; // TODO FIXME error handling
}
std::cout << "Result: " << result.str() << std::endl;
}
看到了Live On Compiler Explorer,打印:
Result: 15.0
Result: 1.7
奖金
关于介绍梦:
How shall I achieve this? Is it worth changing the calculator itself, or it's too heavy and I should prefer some other text processing techniques to parse the required formatting and still do it with std::setw like this:
不值得更换计算器,因为它不是计算器。它真的不是,一旦你用格式化的东西(即 non-expression 东西)扩展你的语法,肯定不再是了。
您当然可以创建这样的语法。让我们来描述一下 AST:
using Result = float;
namespace Formatting {
struct Format {
unsigned frac_digits;
std::string suffix_literal;
};
struct FormattedResult {
Result value;
Format spec;
friend std::ostream& operator<<(std::ostream& os, FormattedResult const& fr) {
auto& [val, fmt] = fr;
boost::io::ios_all_saver state(os);
return os << std::fixed << std::setprecision(fmt.frac_digits) << val << fmt.suffix_literal;
}
};
}
现在,我们可以制定顶层规则 return FormmattedResult
而不仅仅是 Result
(即 float
):
formatspec =
("%." >> precision | qi::attr(0u)) >> qi::raw[*qi::char_];
start = qi::skip(qi::space)[expression >> formatspec];
加上一些额外的声明:
using Skipper = qi::space_type;
qi::rule<Iterator, FormattedResult()> start;
qi::rule<Iterator, Result(), Skipper> expression, term, factor;
// lexemes:
qi::rule<Iterator, Format()> formatspec;
qi::real_parser<Result> number;
qi::uint_parser<unsigned, 10, 1, 2> precision;
//#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>
#include <boost/phoenix.hpp>
#include <boost/io/ios_state.hpp>
#include <iostream>
#include <iomanip>
namespace px = boost::phoenix;
namespace qi = boost::spirit::qi;
using Result = float;
namespace Formatting {
struct Format {
unsigned frac_digits;
std::string suffix_literal;
};
struct FormattedResult {
Result value;
Format spec;
friend std::ostream& operator<<(std::ostream& os, FormattedResult const& fr) {
auto& [val, fmt] = fr;
boost::io::ios_all_saver state(os);
return os << std::fixed << std::setprecision(fmt.frac_digits) << val << fmt.suffix_literal;
}
};
}
BOOST_FUSION_ADAPT_STRUCT(Formatting::Format, frac_digits, suffix_literal)
BOOST_FUSION_ADAPT_STRUCT(Formatting::FormattedResult, value, spec)
namespace Parsers {
using namespace Formatting;
template <typename Iterator>
struct FormattedExpression : qi::grammar<Iterator, FormattedResult()> {
FormattedExpression() : FormattedExpression::base_type(start) {
using qi::_1;
using qi::_val;
expression =
term [_val = _1]
>> *( ('+' >> term [_val += _1])
| ('-' >> term [_val -= _1])
)
;
term =
factor [_val = _1]
>> *( ('*' >> factor [_val *= _1])
| ('/' >> factor [_val /= _1])
)
;
factor =
number [_val = _1]
| '(' >> expression [_val = _1] >> ')'
| ('-' >> factor [_val = -_1])
| ('+' >> factor [_val = _1])
;
formatspec =
("%." >> precision | qi::attr(0u)) >> qi::raw[*qi::char_];
start = qi::skip(qi::space)[expression >> formatspec];
BOOST_SPIRIT_DEBUG_NODES((start)(expression)(
term)(factor)(formatspec))
}
private:
using Skipper = qi::space_type;
qi::rule<Iterator, FormattedResult()> start;
qi::rule<Iterator, Result(), Skipper> expression, term, factor;
// lexemes:
qi::rule<Iterator, Format()> formatspec;
qi::real_parser<Result> number;
qi::uint_parser<unsigned, 10, 1, 2> precision;
};
}
int main() {
using Parser = Parsers::FormattedExpression<std::string::const_iterator>;
static Parser const parser;
for (std::string const expr :
{
"3*5", //
"5/3", //
"5/3%.1", //
"5/3%.3...", //
"3*5%.1", // => 15.0
"3*5%.2", // => 15.00
"3*5%", // => 15%
"3*5%.2%", // => 15.00%
}) //
{
Formatting::FormattedResult fr;
if (parse(begin(expr), end(expr), parser >> qi::eoi, fr)) {
std::cout << std::left //
<< "Input: " << std::setw(12) << std::quoted(expr)
<< "Result: " << fr << "\n";
} else {
std::cout << std::left //
<< "Input: " << std::setw(12) << std::quoted(expr)
<< "Parse Error\n";
}
}
}
版画
Input: "3*5" Result: 15
Input: "5/3" Result: 2
Input: "5/3%.1" Result: 1.7
Input: "5/3%.3..." Result: 1.667...
Input: "3*5%.1" Result: 15.0
Input: "3*5%.2" Result: 15.00
Input: "3*5%" Result: 15%
Input: "3*5%.2%" Result: 15.00%
¹ 我希望我没有不小心让我的个人喜好暴露出来,但请参阅例如Boost Spirit: "Semantic actions are evil"?