Boost :: Spirit解析漂浮物并格式化它吗?

发布于 2025-01-23 21:57:27 字数 3337 浏览 0 评论 0原文

我有一个非常酷的浮点计算器实现,boost :: Spirit

它可以在boost :: spirit :: qi :: float _默认情况下使用:它获取std :: string输入,并计算结果float 。

在操作中看到它在这里

这是引用的代码:

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;
}

它将表达式的值计算到ResulteTream中。

完美工作,对于3*5它输出:

结果:1​​5

如果我将表达式更改为“ 5/3”,则输出:

结果:1​​.66667

我的愿望是始终拥有固定数量的数字:

3*5:

结果:1​​5.0

5/3的15.0:

结果:1​​.7

我知道:将std :: setw添加到cout解决此问题。但是我的目标是不同的(!):

我想直接从解析器中将上述格式的结果直接从resulttream中。

我的想法是让解析器解析更复杂的输入,例如:

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;

I have a very cool float calculator implementation with boost::spirit.

It works on a boost::spirit::qi::float_ by default: it gets an std::string input, and calculates the result float of the expression.

See it in action here.

Here is the code for reference:

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;
}

It calculates the expression's value into theresultstream.

Works perfectly, for 3*5 it outputs:

Result: 15

If I change the expression to "5/3" it outputs:

Result: 1.66667

My desire is to always have a fixed number of digits:

For 3*5:

Result: 15.0

For 5/3:

Result: 1.7

I know: adding std::setw to cout solve this. But my goal is different (!):

I want to get the above formatted result into the resultstream, directly from the parser.

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%

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:

resultstream << setw(required_width) << result;

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

淤浪 2025-01-30 21:57:27
 我的想法是允许解析器解析更复杂的输入,例如:

3*5%.1 =&gt; 15.0
3*5%.2 =&gt; 15.00
3*5%=&gt; 15%
3*5%.2%=&gt; 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;
}

。 com/a/20e01563be9d25e8“ rel =“ nofollow noreferrer”> live on Compiler Explorer ,打印:

Result: 15.0
Result: 1.7

奖励

关于简介梦的

我该如何实现?是否值得更改计算器本身?
否则太重了,我应该更喜欢其他文本处理
解析所需格式的技术,但仍在
STD :: SETW如下:

不值得更改计算器,因为它不是计算器。确实不是,一旦您使用格式化的内容扩展了语法(即非表达的东西),那肯定不再了。

可以当然会创建这样的语法。让我们描述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;
        }
    };
}

现在,我们可以使Toplevel规则返回formmattedResult,而不是结果(即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:“语义动作是邪恶的”吗?

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%

This tells me you're not so much creating an expression evaluator, but rather making a format specification. I'm with others that say: separate your concerns.

For what it's worth setw doesn't help you, but std::fixed and std::setprecision might. Regardless, anything C++ can do, can also happen in a semantic action, so, this hellish contraption should work¹:

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;
}

See it Live On Compiler Explorer, printing:

Result: 15.0
Result: 1.7

BONUS

Regarding the intro dreams:

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:

It's not worth changing the calculator, because it isn't a calculator. It really wasn't, and certainly not anymore once you extend your grammar with formatting things (i.e. non-expression things).

You can of course create such a grammar. Let's describe the 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;
        }
    };
}

Now, we can make the toplevel rule return FormmattedResult instead of just Result (i.e. float):

formatspec =
    ("%." >> precision | qi::attr(0u)) >> qi::raw[*qi::char_];

start = qi::skip(qi::space)[expression >> formatspec];

With some additional declarations:

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;

See it 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";
        }
    }
}

Prints

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%

¹ I hope I didn't accidentally let my personal preference shine through, but see e.g. Boost Spirit: "Semantic actions are evil"?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文