feat: add challenge code
This commit is contained in:
+156
@@ -0,0 +1,156 @@
|
||||
---
|
||||
Language: Cpp
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: AlwaysBreak
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignConsecutiveMacros: AcrossEmptyLines
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortEnumsOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: InlineOnly
|
||||
AllowShortIfStatementsOnASingleLine: true
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BraceWrapping:
|
||||
AfterClass: true
|
||||
AfterControlStatement: false
|
||||
AfterEnum: true
|
||||
AfterFunction: true
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: true
|
||||
AfterStruct: true
|
||||
AfterUnion: true
|
||||
AfterExternBlock: true
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: false
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Custom
|
||||
BreakBeforeConceptDeclarations: true
|
||||
BreakBeforeInheritanceComma: false
|
||||
BreakInheritanceList: AfterColon
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakConstructorInitializers: AfterColon
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 120
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: true
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: false
|
||||
DeriveLineEnding: true
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
EmptyLineBeforeAccessModifier: LogicalBlock
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
- Regex: '^<ext/.*\.h>'
|
||||
Priority: 2
|
||||
- Regex: '^<.*\.h>'
|
||||
Priority: 1
|
||||
- Regex: '^<.*'
|
||||
Priority: 2
|
||||
- Regex: '.*'
|
||||
Priority: 3
|
||||
IncludeIsMainRegex: '([-_](test|unittest))?$'
|
||||
IndentCaseLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentWidth: 4
|
||||
IndentRequires: false
|
||||
IndentWrappedFunctionNames: false
|
||||
InsertNewlineAtEOF: true
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
LineEnding: LF
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBinPackProtocolList: Never
|
||||
ObjCBlockIndentWidth: 2
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 1
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 200
|
||||
PointerAlignment: Left
|
||||
RawStringFormats:
|
||||
- Language: Cpp
|
||||
Delimiters:
|
||||
- cc
|
||||
- CC
|
||||
- cpp
|
||||
- Cpp
|
||||
- CPP
|
||||
- 'c++'
|
||||
- 'C++'
|
||||
CanonicalDelimiter: ''
|
||||
BasedOnStyle: google
|
||||
- Language: TextProto
|
||||
Delimiters:
|
||||
- pb
|
||||
- PB
|
||||
- proto
|
||||
- PROTO
|
||||
EnclosingFunctions:
|
||||
- EqualsProto
|
||||
- EquivToProto
|
||||
- PARSE_PARTIAL_TEXT_PROTO
|
||||
- PARSE_TEST_PROTO
|
||||
- PARSE_TEXT_PROTO
|
||||
- ParseTextOrDie
|
||||
- ParseTextProtoOrDie
|
||||
CanonicalDelimiter: ''
|
||||
BasedOnStyle: google
|
||||
ReferenceAlignment: Left
|
||||
ReflowComments: true
|
||||
ShortNamespaceLines: 0
|
||||
SortIncludes: true
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
SpacesInAngles: false
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Latest
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
...
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
---
|
||||
Checks: '*,-modernize-use-nodiscard'
|
||||
WarningsAsErrors: ''
|
||||
HeaderFilterRegex: ''
|
||||
AnalyzeTemporaryDtors: false
|
||||
FormatStyle: file
|
||||
CheckOptions:
|
||||
- key: bugprone-use-after-move
|
||||
value: '1'
|
||||
- key: bugprone-assert-side-effect.AssertMacros
|
||||
value: assert
|
||||
- key: bugprone-suspicious-enum-usage.StrictMode
|
||||
value: '0'
|
||||
- key: bugprone-unused-return-value.CheckedFunctions
|
||||
value: '::std::async;::std::launder;::std::remove;::std::remove_if;::std::unique;::std::unique_ptr::release;::std::basic_string::empty;::std::vector::empty'
|
||||
- key: cert-dcl59-cpp.HeaderFileExtensions
|
||||
value: ',h,hh,hpp,hxx'
|
||||
- key: cert-err09-cpp.CheckThrowTemporaries
|
||||
value: '1'
|
||||
- key: cert-err61-cpp.CheckThrowTemporaries
|
||||
value: '1'
|
||||
- key: cert-msc32-c.DisallowedSeedTypes
|
||||
value: 'time_t,std::time_t'
|
||||
- key: cert-msc51-cpp.DisallowedSeedTypes
|
||||
value: 'time_t,std::time_t'
|
||||
- key: cert-oop11-cpp.IncludeStyle
|
||||
value: llvm
|
||||
- key: cppcoreguidelines-no-malloc.Allocations
|
||||
value: '::malloc;::calloc'
|
||||
- key: cppcoreguidelines-no-malloc.Deallocations
|
||||
value: '::free'
|
||||
- key: cppcoreguidelines-no-malloc.Reallocations
|
||||
value: '::realloc'
|
||||
- key: cppcoreguidelines-pro-type-member-init.IgnoreArrays
|
||||
value: '0'
|
||||
- key: cppcoreguidelines-special-member-functions.AllowMissingMoveFunctions
|
||||
value: '0'
|
||||
- key: cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor
|
||||
value: '0'
|
||||
- key: hicpp-special-member-functions.AllowMissingMoveFunctions
|
||||
value: '0'
|
||||
- key: hicpp-special-member-functions.AllowSoleDefaultDtor
|
||||
value: '0'
|
||||
...
|
||||
@@ -0,0 +1,2 @@
|
||||
.cache/**
|
||||
build/**
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"compileCommands": "${workspaceFolder}/build/debug/compile_commands.json",
|
||||
"cppStandard": "c++23"
|
||||
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
||||
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug",
|
||||
"program": "${workspaceFolder}/build/calculator-challege",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "CMake: build"
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"testMate.cpp.test.advancedExecutables": [
|
||||
{
|
||||
"pattern": "${workspaceFolder}/build/*-tests",
|
||||
"runTask": {
|
||||
"before": [
|
||||
"CMake: build"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
}
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "cmake",
|
||||
"label": "CMake: build",
|
||||
"command": "build",
|
||||
"targets": [
|
||||
"all"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
"detail": "CMake template build task"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#ifndef CALCULATOR_CHALLENGE_BINARYOPERATORFACTORY_HPP
|
||||
#define CALCULATOR_CHALLENGE_BINARYOPERATORFACTORY_HPP
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "Operand.hpp"
|
||||
|
||||
template<typename T>
|
||||
class BinaryOperatorFactory
|
||||
{
|
||||
using Operator = BinaryOperatorType<T>;
|
||||
|
||||
public:
|
||||
BinaryOperatorFactory() noexcept;
|
||||
BinaryOperatorFactory(const BinaryOperatorFactory&) noexcept = delete;
|
||||
BinaryOperatorFactory(BinaryOperatorFactory&&) noexcept = delete;
|
||||
auto operator=(const BinaryOperatorFactory&) noexcept -> BinaryOperatorFactory& = delete;
|
||||
auto operator=(BinaryOperatorFactory&&) noexcept -> BinaryOperatorFactory& = delete;
|
||||
~BinaryOperatorFactory() noexcept = default;
|
||||
|
||||
auto make(std::string_view text) noexcept -> std::optional<Operator>;
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, Operator> _binary_operations;
|
||||
};
|
||||
|
||||
#include "BinaryOperatorFactory.tcc"
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,20 @@
|
||||
|
||||
#include "BinaryOperatorFactory.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
template<typename T>
|
||||
BinaryOperatorFactory<T>::BinaryOperatorFactory() noexcept
|
||||
{
|
||||
_binary_operations["+"] = [](auto lhs, auto rhs) { return lhs + rhs; };
|
||||
_binary_operations["-"] = [](auto lhs, auto rhs) { return lhs - rhs; };
|
||||
_binary_operations["*"] = [](auto lhs, auto rhs) { return lhs * rhs; };
|
||||
_binary_operations["/"] = [](auto lhs, auto rhs) { return lhs / rhs; };
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
auto BinaryOperatorFactory<T>::make(std::string_view text) noexcept -> std::optional<Operator>
|
||||
{
|
||||
auto found_op = _binary_operations.find(std::string{sanitize_text(text)});
|
||||
if (found_op == _binary_operations.end()) return std::nullopt;
|
||||
return found_op->second;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
#include <catch2/catch_template_test_macros.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include "BinaryOperatorFactory.hpp"
|
||||
|
||||
TEMPLATE_TEST_CASE("BinaryOperatorFactory", "[calculator-challenge]", std::int64_t, double)
|
||||
{
|
||||
constexpr TestType kLeftOperand{20};
|
||||
constexpr TestType kRightOperand{2};
|
||||
|
||||
BinaryOperatorFactory<TestType> factory;
|
||||
|
||||
auto check_factory = [&](std::string_view str, TestType result) {
|
||||
auto operation = factory.make(str);
|
||||
if (!operation.has_value()) {
|
||||
return false;
|
||||
}
|
||||
return (*operation)(kLeftOperand, kRightOperand) == result;
|
||||
};
|
||||
|
||||
CHECK(check_factory("+", kLeftOperand + kRightOperand));
|
||||
CHECK(check_factory(" + ", kLeftOperand + kRightOperand));
|
||||
CHECK(check_factory(" +", kLeftOperand + kRightOperand));
|
||||
CHECK(check_factory("+ ", kLeftOperand + kRightOperand));
|
||||
CHECK(check_factory("+ a", kLeftOperand + kRightOperand));
|
||||
CHECK(check_factory("-", kLeftOperand - kRightOperand));
|
||||
CHECK(check_factory("*", kLeftOperand * kRightOperand));
|
||||
CHECK(check_factory("/", kLeftOperand / kRightOperand));
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
cmake_minimum_required(VERSION 3.27)
|
||||
project(CalculatorChallenge CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_EXTENSIONS ON)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
|
||||
|
||||
INCLUDE(FetchContent)
|
||||
|
||||
FetchContent_Declare(
|
||||
fmtformat
|
||||
GIT_REPOSITORY https://github.com/fmtlib/fmt
|
||||
GIT_TAG 10.2.1
|
||||
)
|
||||
FetchContent_MakeAvailable(fmtformat)
|
||||
|
||||
FetchContent_Declare(
|
||||
Catch2
|
||||
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
|
||||
GIT_TAG v3.5.3
|
||||
)
|
||||
FetchContent_MakeAvailable(Catch2)
|
||||
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras)
|
||||
include(CTest)
|
||||
include(Catch)
|
||||
|
||||
set(CMAKE_C_COMPILER /opt/homebrew/Cellar/llvm@16/16.0.6/bin/clang)
|
||||
set(CMAKE_CXX_COMPILER /opt/homebrew/Cellar/llvm@16/16.0.6/bin/clang++)
|
||||
|
||||
SET(CALCULATOR_CHALLENGE_SOURCES
|
||||
Calculator.cpp
|
||||
CalculatorCli.cpp
|
||||
utils.cpp
|
||||
)
|
||||
|
||||
add_executable(calculator-challege
|
||||
main.cpp
|
||||
${CALCULATOR_CHALLENGE_SOURCES}
|
||||
)
|
||||
target_link_libraries(calculator-challege PRIVATE fmt::fmt)
|
||||
|
||||
|
||||
add_executable(calculator-challege-tests
|
||||
CalculatorTests.cpp
|
||||
BinaryOperatorFactoryTests.cpp
|
||||
CalculatorCliTests.cpp
|
||||
${CALCULATOR_CHALLENGE_SOURCES}
|
||||
)
|
||||
target_link_libraries(calculator-challege-tests PRIVATE Catch2::Catch2WithMain fmt::fmt)
|
||||
catch_discover_tests(calculator-challege-tests)
|
||||
@@ -0,0 +1,85 @@
|
||||
#include "Calculator.hpp"
|
||||
|
||||
#include <ranges>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "Operand.hpp"
|
||||
|
||||
namespace {
|
||||
using Workspace = std::unordered_map<std::string, double>;
|
||||
|
||||
class Visitor;
|
||||
|
||||
auto compute(Visitor& visitor, const Operation& binary_operation) noexcept -> std::optional<double>;
|
||||
|
||||
class Visitor
|
||||
{
|
||||
public:
|
||||
Visitor(std::span<const Operation> operations, Workspace& variables) noexcept :
|
||||
_operations{operations}, _variables{variables}
|
||||
{}
|
||||
|
||||
auto operator()(const IntegerOperand& operand) noexcept -> std::optional<double> { return operand.get(); }
|
||||
|
||||
auto operator()(const VariableOperand& operand) noexcept -> std::optional<double>
|
||||
{
|
||||
auto name = operand.get();
|
||||
auto found_variable = _variables.get().find(name);
|
||||
if (found_variable != _variables.get().end()) return found_variable->second;
|
||||
|
||||
auto found_operation = std::ranges::find(_operations, name, &Operation::variable);
|
||||
if (found_operation == _operations.end()) return std::nullopt;
|
||||
|
||||
return compute(*this, *found_operation);
|
||||
}
|
||||
|
||||
private:
|
||||
std::span<const Operation> _operations;
|
||||
std::reference_wrapper<Workspace> _variables;
|
||||
};
|
||||
|
||||
auto compute(Visitor& visitor, const Operation& operation) noexcept -> std::optional<double>
|
||||
{
|
||||
auto lhs = std::visit(visitor, operation.lhs);
|
||||
if (!lhs.has_value()) return std::nullopt;
|
||||
|
||||
auto rhs = std::visit(visitor, operation.rhs);
|
||||
if (!rhs.has_value()) return std::nullopt;
|
||||
|
||||
return operation.binary_op(lhs.value(), rhs.value());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void Calculator::addOperation(Operation operation) noexcept
|
||||
{
|
||||
_operations.push_back(std::move(operation));
|
||||
}
|
||||
|
||||
void Calculator::addOperation(std::string result, Operand lhs, Operand rhs, Operator binary_operator) noexcept
|
||||
{
|
||||
addOperation(Operation{std::move(result), std::move(lhs), std::move(rhs), std::move(binary_operator)});
|
||||
}
|
||||
|
||||
void Calculator::undoOperation(std::size_t count) noexcept
|
||||
{
|
||||
count = std::min(_operations.size(), count);
|
||||
_operations.erase(_operations.end() - static_cast<std::int64_t>(count), _operations.end());
|
||||
}
|
||||
|
||||
auto Calculator::computeLast() noexcept -> std::pair<std::string, std::optional<double>>
|
||||
{
|
||||
Workspace workspace;
|
||||
Visitor visitor{_operations, workspace};
|
||||
|
||||
for (auto& operation : std::views::reverse(_operations)) {
|
||||
auto result = compute(visitor, operation);
|
||||
if (result.has_value()) return std::make_pair(operation.variable, result.value());
|
||||
}
|
||||
return std::make_pair("na", std::nullopt);
|
||||
}
|
||||
|
||||
auto Calculator::computeLastValue() noexcept -> double
|
||||
{
|
||||
auto [_, result] = computeLast();
|
||||
return result.value_or(0.0);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#ifndef CALCULATOR_CHALLENGE_WORKSPACE_HPP
|
||||
#define CALCULATOR_CHALLENGE_WORKSPACE_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "Operand.hpp"
|
||||
|
||||
struct Operation
|
||||
{
|
||||
std::string variable;
|
||||
|
||||
Operand lhs;
|
||||
Operand rhs;
|
||||
|
||||
Operator binary_op;
|
||||
};
|
||||
|
||||
class Calculator
|
||||
{
|
||||
public:
|
||||
Calculator() noexcept = default;
|
||||
Calculator(const Calculator&) noexcept = default;
|
||||
Calculator(Calculator&&) noexcept = default;
|
||||
auto operator=(const Calculator&) noexcept -> Calculator& = default;
|
||||
auto operator=(Calculator&&) noexcept -> Calculator& = default;
|
||||
~Calculator() noexcept = default;
|
||||
|
||||
void addOperation(Operation operation) noexcept;
|
||||
void addOperation(std::string result, Operand lhs, Operand rhs, Operator binary_operator) noexcept;
|
||||
|
||||
void undoOperation(std::size_t count) noexcept;
|
||||
|
||||
auto computeLast() noexcept -> std::pair<std::string, std::optional<double>>;
|
||||
auto computeLastValue() noexcept -> double;
|
||||
|
||||
private:
|
||||
std::vector<Operation> _operations;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,131 @@
|
||||
#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
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef CALCULATOR_CHALLENGE_CALCULATORCLI_HPP
|
||||
#define CALCULATOR_CHALLENGE_CALCULATORCLI_HPP
|
||||
|
||||
#include "BinaryOperatorFactory.hpp"
|
||||
#include "Calculator.hpp"
|
||||
|
||||
class CalculatorCli
|
||||
{
|
||||
public:
|
||||
CalculatorCli() noexcept = default;
|
||||
CalculatorCli(const CalculatorCli&) noexcept = delete;
|
||||
CalculatorCli(CalculatorCli&&) noexcept = delete;
|
||||
auto operator=(const CalculatorCli&) noexcept -> CalculatorCli& = delete;
|
||||
auto operator=(CalculatorCli&&) noexcept -> CalculatorCli& = delete;
|
||||
~CalculatorCli() noexcept = default;
|
||||
|
||||
auto parse(std::string_view text) noexcept -> std::string;
|
||||
void runLoop() noexcept;
|
||||
|
||||
private:
|
||||
Calculator _calculator;
|
||||
BinaryOperatorFactory<double> _operation_factory;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,80 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include "CalculatorCli.hpp"
|
||||
|
||||
TEST_CASE("CalculatorCli - can parse operations", "[calculator-challenge]")
|
||||
{
|
||||
CalculatorCli calculator;
|
||||
|
||||
calculator.parse("a = 4 + 1");
|
||||
CHECK(calculator.parse("result") == "a = 5");
|
||||
|
||||
calculator.parse(" a = 4 + 1");
|
||||
CHECK(calculator.parse("result") == "a = 5");
|
||||
|
||||
calculator.parse("a = 4 + 1 ");
|
||||
CHECK(calculator.parse("result") == "a = 5");
|
||||
|
||||
calculator.parse("a = 4 + 1");
|
||||
CHECK(calculator.parse("result") == "a = 5");
|
||||
}
|
||||
|
||||
TEST_CASE("CalculatorCli - edge cases", "[calculator-challenge]")
|
||||
{
|
||||
CalculatorCli calculator;
|
||||
|
||||
calculator.parse("a=31+2");
|
||||
CHECK_FALSE(calculator.parse("result") == "a = 33"); // TODO: @JC do not require spaces
|
||||
}
|
||||
|
||||
TEST_CASE("CalculatorCli - can undo operations", "[calculator-challenge]")
|
||||
{
|
||||
CalculatorCli calculator;
|
||||
|
||||
calculator.parse("a = 4 + 1");
|
||||
CHECK(calculator.parse("result") == "a = 5");
|
||||
|
||||
calculator.parse(" a = 6 + 1");
|
||||
CHECK(calculator.parse("result") == "a = 7");
|
||||
|
||||
SECTION("simple command")
|
||||
{
|
||||
calculator.parse("undo");
|
||||
CHECK(calculator.parse("result") == "a = 5");
|
||||
}
|
||||
SECTION("two undos")
|
||||
{
|
||||
calculator.parse("undo");
|
||||
CHECK(calculator.parse("result") == "a = 5");
|
||||
|
||||
calculator.parse("undo");
|
||||
CHECK(calculator.parse("result") == "N/A");
|
||||
}
|
||||
SECTION("with space")
|
||||
{
|
||||
calculator.parse("undo ");
|
||||
CHECK(calculator.parse("result") == "a = 5");
|
||||
}
|
||||
SECTION("with zero")
|
||||
{
|
||||
calculator.parse("undo 0");
|
||||
CHECK(calculator.parse("result") == "a = 7");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("CalculatorCli - can undo multiple operations", "[calculator-challenge]")
|
||||
{
|
||||
CalculatorCli calculator;
|
||||
|
||||
calculator.parse("a = 4 + 1");
|
||||
CHECK(calculator.parse("result") == "a = 5");
|
||||
|
||||
calculator.parse(" a = 6 + 1");
|
||||
CHECK(calculator.parse("result") == "a = 7");
|
||||
|
||||
calculator.parse("b = 6 + 1");
|
||||
CHECK(calculator.parse("result") == "b = 7");
|
||||
|
||||
calculator.parse("undo 2");
|
||||
CHECK(calculator.parse("result") == "a = 5");
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include "Calculator.hpp"
|
||||
#include "Operand.hpp"
|
||||
|
||||
constexpr auto addiction = [](auto lhs, auto rhs) { return lhs + rhs; };
|
||||
constexpr auto subtraction = [](auto lhs, auto rhs) { return lhs - rhs; };
|
||||
|
||||
TEST_CASE("Calculator - can add operations", "[calculator-challenge]")
|
||||
{
|
||||
Calculator calculator;
|
||||
|
||||
calculator.addOperation("a", IntegerOperand{5}, IntegerOperand{10}, addiction);
|
||||
CHECK(calculator.computeLastValue() == 15.0);
|
||||
|
||||
calculator.addOperation("b", IntegerOperand{1}, IntegerOperand{5}, addiction);
|
||||
CHECK(calculator.computeLastValue() == 6.0);
|
||||
|
||||
calculator.addOperation("c", IntegerOperand{1}, IntegerOperand{5}, subtraction);
|
||||
CHECK(calculator.computeLastValue() == -4.0);
|
||||
}
|
||||
|
||||
TEST_CASE("Calculator - can undo operations", "[calculator-challenge]")
|
||||
{
|
||||
Calculator calculator;
|
||||
|
||||
calculator.addOperation("a", IntegerOperand{5}, IntegerOperand{10}, addiction);
|
||||
CHECK(calculator.computeLastValue() == 15.0);
|
||||
calculator.addOperation("b", IntegerOperand{1}, IntegerOperand{5}, subtraction);
|
||||
CHECK(calculator.computeLastValue() == -4.0);
|
||||
|
||||
SECTION("one by one")
|
||||
{
|
||||
calculator.undoOperation(1);
|
||||
CHECK(calculator.computeLastValue() == 15.0);
|
||||
|
||||
calculator.undoOperation(1);
|
||||
CHECK(calculator.computeLastValue() == 0.0);
|
||||
}
|
||||
|
||||
SECTION("all")
|
||||
{
|
||||
calculator.undoOperation(2);
|
||||
CHECK(calculator.computeLastValue() == 0.0);
|
||||
}
|
||||
|
||||
SECTION("more than was added")
|
||||
{
|
||||
calculator.undoOperation(50);
|
||||
CHECK(calculator.computeLastValue() == 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Calculator - can add operations with variables", "[calculator-challenge]")
|
||||
{
|
||||
Calculator calculator;
|
||||
|
||||
calculator.addOperation("a", IntegerOperand{1}, IntegerOperand{2}, addiction);
|
||||
CHECK(calculator.computeLastValue() == 3);
|
||||
|
||||
calculator.addOperation("b", VariableOperand{"a"}, IntegerOperand{10}, addiction);
|
||||
CHECK(calculator.computeLastValue() == 13);
|
||||
|
||||
calculator.addOperation("b", VariableOperand{"a"}, IntegerOperand{2}, addiction);
|
||||
CHECK(calculator.computeLastValue() == 5);
|
||||
}
|
||||
|
||||
TEST_CASE("Calculator - if a variable is missing the last available is returned", "[calculator-challenge]")
|
||||
{
|
||||
Calculator calculator;
|
||||
|
||||
calculator.addOperation("a", VariableOperand{"c"}, IntegerOperand{10}, addiction);
|
||||
CHECK(calculator.computeLastValue() == 0.0);
|
||||
|
||||
calculator.addOperation("a", IntegerOperand{1}, IntegerOperand{2}, addiction);
|
||||
CHECK(calculator.computeLastValue() == 3);
|
||||
|
||||
calculator.addOperation("a", VariableOperand{"c"}, IntegerOperand{10}, addiction);
|
||||
CHECK(calculator.computeLastValue() == 3.0);
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
#ifndef CALCULATOR_CHALLENGE_OPERAND_HPP
|
||||
#define CALCULATOR_CHALLENGE_OPERAND_HPP
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
template<typename T>
|
||||
using BinaryOperatorType = std::function<T(T, T)>;
|
||||
using Operator = BinaryOperatorType<double>;
|
||||
|
||||
class IntegerOperand
|
||||
{
|
||||
public:
|
||||
explicit IntegerOperand(double value) noexcept : _value{value} {}
|
||||
auto get() const noexcept -> double { return _value; };
|
||||
|
||||
private:
|
||||
double _value{0};
|
||||
};
|
||||
|
||||
class VariableOperand
|
||||
{
|
||||
public:
|
||||
explicit VariableOperand(std::string name) noexcept : _name{std::move(name)} {}
|
||||
auto get() const noexcept -> std::string { return _name; };
|
||||
|
||||
private:
|
||||
std::string _name;
|
||||
};
|
||||
|
||||
using Operand = std::variant<IntegerOperand, VariableOperand>;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,10 @@
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "CalculatorCli.hpp"
|
||||
|
||||
auto main() -> int
|
||||
{
|
||||
CalculatorCli cli;
|
||||
cli.runLoop();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#include "utils.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
constexpr char kSpace{' '};
|
||||
} // namespace
|
||||
|
||||
auto trim_whitespaces(std::string_view text) noexcept -> std::string_view
|
||||
{
|
||||
auto leading_spaces = text.find_first_not_of(kSpace);
|
||||
if (leading_spaces == std::string::npos) return {};
|
||||
|
||||
auto sub_view = text.substr(leading_spaces);
|
||||
|
||||
auto trailling_spaces = sub_view.find_last_not_of(kSpace);
|
||||
if (trailling_spaces == std::string::npos) return sub_view;
|
||||
|
||||
return sub_view.substr(0, trailling_spaces + 1);
|
||||
}
|
||||
|
||||
auto sanitize_text(std::string_view text) noexcept -> std::string_view
|
||||
{
|
||||
auto leading_spaces = text.find_first_not_of(kSpace);
|
||||
if (leading_spaces == std::string::npos) return {};
|
||||
|
||||
auto sub_view = text.substr(leading_spaces);
|
||||
|
||||
auto trailling_spaces = sub_view.find_first_of(kSpace);
|
||||
if (trailling_spaces == std::string::npos) return sub_view;
|
||||
|
||||
return sub_view.substr(0, trailling_spaces);
|
||||
}
|
||||
Reference in New Issue
Block a user