Code View

cparse / cparse-1.3.0.2 / examples / toy-calc / src / toy_calc.cpp
// 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';
    }
}