132 lines
3.6 KiB
C++
132 lines
3.6 KiB
C++
#include "CalculatorCli.hpp"
|
|
|
|
#include <fmt/format.h>
|
|
|
|
#include <cassert>
|
|
#include <iostream>
|
|
#include <string_view>
|
|
#include <type_traits>
|
|
|
|
#include "Operand.hpp"
|
|
#include "utils.hpp"
|
|
|
|
namespace {
|
|
constexpr std::string_view kExit{"exit"};
|
|
constexpr std::string_view kUndo{"undo"};
|
|
constexpr std::string_view kResult{"result"};
|
|
|
|
constexpr auto kSeparator{' '};
|
|
constexpr auto kEqualOperator{'='};
|
|
|
|
struct OperationMembers
|
|
{
|
|
std::string_view result;
|
|
std::string_view lhs;
|
|
std::string_view rhs;
|
|
std::string_view operation;
|
|
};
|
|
|
|
template<typename T>
|
|
auto text_to_number(std::string_view text) noexcept -> T
|
|
{
|
|
if (text.empty()) return T{};
|
|
|
|
if constexpr (std::is_integral_v<T>) {
|
|
return std::stoll(text.data());
|
|
}
|
|
|
|
if constexpr (std::is_floating_point_v<T>) {
|
|
return T{0}; // TODO: @JC check how to do doubles
|
|
}
|
|
|
|
return T{};
|
|
}
|
|
|
|
auto parse_operation_members(std::string_view text) noexcept -> std::optional<OperationMembers>
|
|
{
|
|
OperationMembers result;
|
|
|
|
auto result_sep = text.find_first_of(kEqualOperator);
|
|
if (result_sep == std::string::npos) return {};
|
|
|
|
result.result = sanitize_text(text.substr(0, result_sep));
|
|
|
|
auto operation_text = trim_whitespaces(text.substr(result_sep + 1));
|
|
auto left_sep = operation_text.find_first_of(kSeparator);
|
|
if (left_sep == std::string::npos) return {};
|
|
|
|
result.lhs = operation_text.substr(0, left_sep);
|
|
|
|
auto right_side = operation_text.substr(left_sep + 1);
|
|
auto right_sep = right_side.find_first_of(kSeparator);
|
|
result.operation = right_side.substr(0, right_sep);
|
|
result.rhs = sanitize_text(right_side.substr(right_sep + 1));
|
|
return result;
|
|
}
|
|
|
|
auto parse_operand(std::string_view text) noexcept -> Operand
|
|
{
|
|
assert(!text.empty());
|
|
if (!std::isdigit(text.front())) { // NOLINT
|
|
return VariableOperand{std::string{text}};
|
|
}
|
|
auto operand{static_cast<double>(text_to_number<std::int64_t>(text))};
|
|
return IntegerOperand{operand};
|
|
}
|
|
} // namespace
|
|
|
|
auto CalculatorCli::parse(std::string_view text) noexcept -> std::string
|
|
{
|
|
auto sanitized_text = trim_whitespaces(text);
|
|
|
|
if (sanitized_text.empty()) return {};
|
|
|
|
if (sanitized_text == kUndo) {
|
|
_calculator.undoOperation(1);
|
|
return {};
|
|
}
|
|
|
|
if (sanitized_text.starts_with(kUndo)) {
|
|
auto left_sep = sanitized_text.find_first_of(kSeparator);
|
|
_calculator.undoOperation(text_to_number<std::int64_t>(text.substr(left_sep)));
|
|
return {};
|
|
}
|
|
|
|
if (sanitized_text.starts_with(kResult)) {
|
|
auto [variable, value] = _calculator.computeLast();
|
|
if (!value.has_value()) return "N/A";
|
|
return fmt::format("{} = {}", variable, value.value());
|
|
}
|
|
|
|
auto components = parse_operation_members(sanitized_text);
|
|
if (!components.has_value()) return {};
|
|
|
|
auto operation = _operation_factory.make(components->operation);
|
|
if (!operation.has_value()) return {};
|
|
|
|
_calculator.addOperation(
|
|
std::string{components->result},
|
|
parse_operand(components->lhs),
|
|
parse_operand(components->rhs),
|
|
operation.value());
|
|
|
|
return fmt::format("{} = {} {} {}", components->result, components->lhs, components->operation, components->rhs);
|
|
}
|
|
|
|
void CalculatorCli::runLoop() noexcept
|
|
{
|
|
std::string line;
|
|
while (true) {
|
|
std::cout << "> ";
|
|
std::getline(std::cin, line);
|
|
if (std::cin.fail()) {
|
|
std::cin.clear();
|
|
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
|
|
}
|
|
|
|
if (line == kExit) return;
|
|
auto result = parse(line);
|
|
std::cout << result << std::endl;
|
|
}
|
|
}
|