// SPDX-License-Identifier: MIT
#include <cparse/Parser.hh>
#include <cctype>
#include <iostream>
#include <optional>
#include <sstream>
#include <variant>
#include <vector>
using fedem::parser::ParseError;
class Calculator : public fedem::parser::Parser
{
public:
std::string getGrammarName() const noexcept override { return "toy-calc"; }
// Public entry point: parse a string, return the computed value or
// nullopt on error (with diagnostics in errors()).
std::optional<double> evaluate(std::string const& source)
{
auto ss = std::make_shared<std::istringstream>(source);
pushCursor("<expr>", ss);
if (!start()) return std::nullopt;
return result_;
}
protected:
bool start() override
{
skipWhiteSpaces();
if (!parseExpression(result_)) return false;
skipWhiteSpaces();
if (getCursor().safePeek() != '\032')
{
recordError(ParseError::Kind::Syntax,
"trailing characters after expression");
return false;
}
return true;
}
private:
// expression := term (('+' | '-') term)*
bool parseExpression(double& out)
{
if (!parseTerm(out)) return false;
skipWhiteSpaces();
while (true)
{
char const op = getCursor().safePeek();
if (op != '+' && op != '-') return true;
getCursor().get();
skipWhiteSpaces();
double rhs;
if (!parseTerm(rhs))
{
recordError(ParseError::Kind::Syntax,
std::string{"expected term after '"} + op + "'");
return false;
}
out = (op == '+') ? out + rhs : out - rhs;
skipWhiteSpaces();
}
}
// term := factor (('*' | '/') factor)*
bool parseTerm(double& out)
{
if (!parseFactor(out)) return false;
skipWhiteSpaces();
while (true)
{
char const op = getCursor().safePeek();
if (op != '*' && op != '/') return true;
getCursor().get();
skipWhiteSpaces();
// The divisor is consumed before it can be checked: keep where
// it starts so "division by zero" points at it, not past it.
unsigned long const rhsLine = getCursor().getNextLineNumber();
unsigned long const rhsCol = getCursor().getNextColumnNumber();
double rhs;
if (!parseFactor(rhs))
{
recordError(ParseError::Kind::Syntax,
std::string{"expected factor after '"} + op + "'");
return false;
}
if (op == '/' && rhs == 0.0)
{
recordError(ParseError::Kind::Faulty, "division by zero",
rhsLine, rhsCol);
return false;
}
out = (op == '*') ? out * rhs : out / rhs;
skipWhiteSpaces();
}
}
// factor := number | '(' expression ')' | '-' factor
bool parseFactor(double& out)
{
skipWhiteSpaces();
char const c = getCursor().safePeek();
if (c == '(')
{
getCursor().get();
enterScope();
if (!parseExpression(out)) return false;
skipWhiteSpaces();
if (!extractCharacter(')'))
{
recordError(ParseError::Kind::Syntax, "expected ')'");
return false;
}
leaveScope();
return true;
}
if (c == '-')
{
getCursor().get();
if (!parseFactor(out)) return false;
out = -out;
return true;
}
if (std::isdigit(static_cast<unsigned char>(c)) || c == '.')
{
return parseNumber(out);
}
recordError(ParseError::Kind::Syntax, "expected number, '(' or '-'");
return false;
}
bool parseNumber(double& out)
{
std::string s;
while (true)
{
char const c = getCursor().safePeek();
if (!std::isdigit(static_cast<unsigned char>(c)) && c != '.') break;
s += getCursor().safeGet();
}
if (s.empty()) return false;
try { out = std::stod(s); }
catch (...) {
recordError(ParseError::Kind::Type, "invalid number '" + s + "'");
return false;
}
return true;
}
double result_ = 0.0;
};
int main()
{
for (std::string line; std::getline(std::cin, line); )
{
if (line.empty()) continue;
Calculator c;
if (auto v = c.evaluate(line))
std::cout << *v << '\n';
else
for (auto const& e : c.errors())
std::cerr << e.filename << ':' << e.line << ':' << e.col
<< ": " << e.message << '\n';
}
}