From ffd2a283ad5ca06bafbfa789f4be0b30a6d5096e Mon Sep 17 00:00:00 2001 From: Jason Turner Date: Tue, 16 Jan 2024 09:16:23 -0700 Subject: [PATCH] Move from C++20 to C++23 C++23 features used: * attributes on lambdas C++23 features not used by design decision: * std::expected We chose to provide error handling by exceptions today. Why? Because it was an interesting experiment for today C++23 features not used (because of lack of compiler and tooling support) * modules * import std; * std::print --- CMakeLists.txt | 2 +- src/infiz/infiz.cpp | 20 ++++++----- src/libinfiz/Evaluator.hpp | 61 +++++++++++++++++++++++--------- src/libinfiz/RationalNumber.hpp | 1 + src/libinfiz/StringTokenizer.hpp | 16 +++++++-- test/constexpr_tests.cpp | 6 ++++ 6 files changed, 78 insertions(+), 28 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 056f7ad..e5dc19a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.21) # Only set the cxx_standard if it is not set by someone else if (NOT DEFINED CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 20) + set(CMAKE_CXX_STANDARD 23) endif() # strongly encouraged to enable this globally to avoid conflicts between diff --git a/src/infiz/infiz.cpp b/src/infiz/infiz.cpp index f089395..5cba3e8 100644 --- a/src/infiz/infiz.cpp +++ b/src/infiz/infiz.cpp @@ -15,14 +15,18 @@ auto main() -> int std::cin.getline(input.data(), max_line - 1, '\n'); while (std::cin.good()) { - const auto answer = evaluate(input.data()); - std::cout << "answer: "; - - if (answer.getDenominator() == 1) { - std::cout << std::format("{}\n", answer.getNumerator()); - } else { - std::cout << std::format( - "{}/{} ({})\n", answer.getNumerator(), answer.getDenominator(), answer.asFloat()); + try { + const auto answer = evaluate(input.data()); + std::cout << "answer: "; + + if (answer.getDenominator() == 1) { + std::cout << std::format("{}\n", answer.getNumerator()); + } else { + std::cout << std::format( + "{}/{} ({})\n", answer.getNumerator(), answer.getDenominator(), answer.asFloat()); + } + } catch (const std::runtime_error &err) { + std::cout << err.what() << '\n'; } std::cin.getline(input.data(), max_line - 1, '\n'); diff --git a/src/libinfiz/Evaluator.hpp b/src/libinfiz/Evaluator.hpp index 5e00414..c18505c 100644 --- a/src/libinfiz/Evaluator.hpp +++ b/src/libinfiz/Evaluator.hpp @@ -4,8 +4,9 @@ #include "RationalNumber.hpp" #include "Stack.hpp" #include "StringTokenizer.hpp" -#include #include +#include +#include enum struct Operators { PLUS_SIGN, CLOSE_PAREN, OPEN_PAREN, MINUS_SIGN, DIVIDE_SIGN, MULTIPLY_SIGN }; @@ -61,7 +62,9 @@ constexpr void evaluateStacks(Stack &numbers, Stack & case Operators::MINUS_SIGN: { operators.pop(); - numbers.push(-numbers.pop()); + const auto operand2 = numbers.pop(); + const auto operand1 = numbers.pop(); + numbers.push(operand1 - operand2); break; } @@ -88,16 +91,15 @@ constexpr void evaluateStacks(Stack &numbers, Stack & } -template -[[nodiscard]] constexpr auto from_chars(std::string_view input) -> Type +template [[nodiscard]] constexpr auto from_chars(std::string_view input) -> Type { - Type result{0}; + Type result{ 0 }; for (const char digit : input) { - result *= 10; // NOLINT + result *= 10;// NOLINT - if (digit >= '0' && digit <= '9') { - result += static_cast(digit - '0'); + if (digit >= '0' && digit <= '9') { result += static_cast(digit - '0'); } else { + throw std::range_error("not a number"); } } @@ -109,10 +111,29 @@ template Stack operators; Stack numbers; + const auto throw_error = [&tokenizer] [[noreturn]] () { + throw std::runtime_error(std::format( + R"(Unable to evaluate expression +{} +{}^ unevaluated)", + tokenizer.input(), + std::string(tokenizer.offset(), ' '))); + }; + + const auto evalStacks = [&]() { + try { + evaluateStacks(numbers, operators); + } catch (const std::runtime_error &) { + throw_error(); + } + }; + while (tokenizer.hasMoreTokens()) { auto next = tokenizer.nextToken(); + if (next.empty()) { throw_error(); } + auto value = Operators::PLUS_SIGN; if (!next.empty()) { @@ -145,8 +166,12 @@ template default: operation = false; - const std::integral auto parsed = from_chars(next); - numbers.emplace(parsed, 1); + try { + const std::integral auto parsed = from_chars(next); + numbers.emplace(parsed, 1); + } catch (const std::range_error &) { + throw_error(); + } break; } @@ -157,12 +182,10 @@ template break; case Operators::CLOSE_PAREN: operators.push(value); - evaluateStacks(numbers, operators); + evalStacks(); break; default: - if (operators.peek() != nullptr && precedence(value) <= precedence(*operators.peek())) { - evaluateStacks(numbers, operators); - } + if (operators.peek() != nullptr && precedence(value) <= precedence(*operators.peek())) { evalStacks(); } operators.push(value); break; } @@ -170,13 +193,17 @@ template } } - if (operators.peek() != nullptr) { evaluateStacks(numbers, operators); } + if (operators.peek() != nullptr) { evalStacks(); } + + if (!operators.empty() || tokenizer.hasUnparsedInput()) { + throw_error(); + } if (numbers.peek() != nullptr) { return *numbers.peek(); - } else { - return { 0, 0 }; } + + throw_error(); } [[nodiscard]] constexpr auto evaluate(std::string_view input) -> RationalNumber diff --git a/src/libinfiz/RationalNumber.hpp b/src/libinfiz/RationalNumber.hpp index da0ff79..556c8c3 100644 --- a/src/libinfiz/RationalNumber.hpp +++ b/src/libinfiz/RationalNumber.hpp @@ -1,6 +1,7 @@ #ifndef INFIZ_RATIONAL_NUMBER_H #define INFIZ_RATIONAL_NUMBER_H +#include #include /** diff --git a/src/libinfiz/StringTokenizer.hpp b/src/libinfiz/StringTokenizer.hpp index c6c118c..adf650a 100644 --- a/src/libinfiz/StringTokenizer.hpp +++ b/src/libinfiz/StringTokenizer.hpp @@ -57,7 +57,7 @@ [[nodiscard]] constexpr auto isWhiteSpace(char input) noexcept -> bool { - return !isNumber(input) && !isOperator(input); + return input == ' ' || input == '\t' || input == '\n' || input == '\r'; } @@ -85,10 +85,22 @@ class StringTokenizer return returnValue; } - [[nodiscard]] constexpr auto hasMoreTokens() const { + [[nodiscard]] constexpr auto hasUnparsedInput() const noexcept { + return currentOffset < string.size(); + } + + [[nodiscard]] constexpr auto hasMoreTokens() const noexcept { return moreTokens; } + [[nodiscard]] constexpr auto input() const noexcept { + return string; + } + + [[nodiscard]] constexpr auto offset() const noexcept { + return currentOffset; + } + private: std::string_view string; std::size_t currentOffset{0}; diff --git a/test/constexpr_tests.cpp b/test/constexpr_tests.cpp index 00a2734..f0fd692 100644 --- a/test/constexpr_tests.cpp +++ b/test/constexpr_tests.cpp @@ -28,6 +28,12 @@ TEST_CASE("Addition") STATIC_REQUIRE(evaluate("(3 + (2 + 4))") == RationalNumber(9, 1));// NOLINT } +TEST_CASE("Subtraction") +{ + STATIC_REQUIRE(evaluate("(3 - 2)") == RationalNumber(1, 1));// NOLINT + STATIC_REQUIRE(evaluate("(3 + (2 - 4))") == RationalNumber(1, 1));// NOLINT +} + TEST_CASE("Division") { STATIC_REQUIRE(evaluate("(3 / 2)") == RationalNumber(3, 2));// NOLINT