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;
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
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"?