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::fixedstd::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;

看到了Live On Compiler Explorer

//#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"?