Skip to content

Commit ffd2a28

Browse files
committed
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
1 parent a91eb91 commit ffd2a28

File tree

6 files changed

+78
-28
lines changed

6 files changed

+78
-28
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.21)
66

77
# Only set the cxx_standard if it is not set by someone else
88
if (NOT DEFINED CMAKE_CXX_STANDARD)
9-
set(CMAKE_CXX_STANDARD 20)
9+
set(CMAKE_CXX_STANDARD 23)
1010
endif()
1111

1212
# strongly encouraged to enable this globally to avoid conflicts between

src/infiz/infiz.cpp

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,18 @@ auto main() -> int
1515
std::cin.getline(input.data(), max_line - 1, '\n');
1616

1717
while (std::cin.good()) {
18-
const auto answer = evaluate(input.data());
19-
std::cout << "answer: ";
20-
21-
if (answer.getDenominator() == 1) {
22-
std::cout << std::format("{}\n", answer.getNumerator());
23-
} else {
24-
std::cout << std::format(
25-
"{}/{} ({})\n", answer.getNumerator(), answer.getDenominator(), answer.asFloat<double>());
18+
try {
19+
const auto answer = evaluate(input.data());
20+
std::cout << "answer: ";
21+
22+
if (answer.getDenominator() == 1) {
23+
std::cout << std::format("{}\n", answer.getNumerator());
24+
} else {
25+
std::cout << std::format(
26+
"{}/{} ({})\n", answer.getNumerator(), answer.getDenominator(), answer.asFloat<double>());
27+
}
28+
} catch (const std::runtime_error &err) {
29+
std::cout << err.what() << '\n';
2630
}
2731

2832
std::cin.getline(input.data(), max_line - 1, '\n');

src/libinfiz/Evaluator.hpp

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
#include "RationalNumber.hpp"
55
#include "Stack.hpp"
66
#include "StringTokenizer.hpp"
7-
#include <string_view>
87
#include <concepts>
8+
#include <string_view>
9+
#include <format>
910

1011
enum struct Operators { PLUS_SIGN, CLOSE_PAREN, OPEN_PAREN, MINUS_SIGN, DIVIDE_SIGN, MULTIPLY_SIGN };
1112

@@ -61,7 +62,9 @@ constexpr void evaluateStacks(Stack<RationalNumber> &numbers, Stack<Operators> &
6162

6263
case Operators::MINUS_SIGN: {
6364
operators.pop();
64-
numbers.push(-numbers.pop());
65+
const auto operand2 = numbers.pop();
66+
const auto operand1 = numbers.pop();
67+
numbers.push(operand1 - operand2);
6568
break;
6669
}
6770

@@ -88,16 +91,15 @@ constexpr void evaluateStacks(Stack<RationalNumber> &numbers, Stack<Operators> &
8891
}
8992

9093

91-
template<std::integral Type>
92-
[[nodiscard]] constexpr auto from_chars(std::string_view input) -> Type
94+
template<std::integral Type> [[nodiscard]] constexpr auto from_chars(std::string_view input) -> Type
9395
{
94-
Type result{0};
96+
Type result{ 0 };
9597

9698
for (const char digit : input) {
97-
result *= 10; // NOLINT
99+
result *= 10;// NOLINT
98100

99-
if (digit >= '0' && digit <= '9') {
100-
result += static_cast<Type>(digit - '0');
101+
if (digit >= '0' && digit <= '9') { result += static_cast<Type>(digit - '0'); } else {
102+
throw std::range_error("not a number");
101103
}
102104
}
103105

@@ -109,10 +111,29 @@ template<std::integral Type>
109111
Stack<Operators> operators;
110112
Stack<RationalNumber> numbers;
111113

114+
const auto throw_error = [&tokenizer] [[noreturn]] () {
115+
throw std::runtime_error(std::format(
116+
R"(Unable to evaluate expression
117+
{}
118+
{}^ unevaluated)",
119+
tokenizer.input(),
120+
std::string(tokenizer.offset(), ' ')));
121+
};
122+
123+
const auto evalStacks = [&]() {
124+
try {
125+
evaluateStacks(numbers, operators);
126+
} catch (const std::runtime_error &) {
127+
throw_error();
128+
}
129+
};
130+
112131
while (tokenizer.hasMoreTokens()) {
113132

114133
auto next = tokenizer.nextToken();
115134

135+
if (next.empty()) { throw_error(); }
136+
116137
auto value = Operators::PLUS_SIGN;
117138

118139
if (!next.empty()) {
@@ -145,8 +166,12 @@ template<std::integral Type>
145166

146167
default:
147168
operation = false;
148-
const std::integral auto parsed = from_chars<int>(next);
149-
numbers.emplace(parsed, 1);
169+
try {
170+
const std::integral auto parsed = from_chars<int>(next);
171+
numbers.emplace(parsed, 1);
172+
} catch (const std::range_error &) {
173+
throw_error();
174+
}
150175
break;
151176
}
152177

@@ -157,26 +182,28 @@ template<std::integral Type>
157182
break;
158183
case Operators::CLOSE_PAREN:
159184
operators.push(value);
160-
evaluateStacks(numbers, operators);
185+
evalStacks();
161186
break;
162187
default:
163-
if (operators.peek() != nullptr && precedence(value) <= precedence(*operators.peek())) {
164-
evaluateStacks(numbers, operators);
165-
}
188+
if (operators.peek() != nullptr && precedence(value) <= precedence(*operators.peek())) { evalStacks(); }
166189
operators.push(value);
167190
break;
168191
}
169192
}
170193
}
171194
}
172195

173-
if (operators.peek() != nullptr) { evaluateStacks(numbers, operators); }
196+
if (operators.peek() != nullptr) { evalStacks(); }
197+
198+
if (!operators.empty() || tokenizer.hasUnparsedInput()) {
199+
throw_error();
200+
}
174201

175202
if (numbers.peek() != nullptr) {
176203
return *numbers.peek();
177-
} else {
178-
return { 0, 0 };
179204
}
205+
206+
throw_error();
180207
}
181208

182209
[[nodiscard]] constexpr auto evaluate(std::string_view input) -> RationalNumber

src/libinfiz/RationalNumber.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#ifndef INFIZ_RATIONAL_NUMBER_H
22
#define INFIZ_RATIONAL_NUMBER_H
33

4+
#include <compare>
45
#include <numeric>
56

67
/**

src/libinfiz/StringTokenizer.hpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757

5858
[[nodiscard]] constexpr auto isWhiteSpace(char input) noexcept -> bool
5959
{
60-
return !isNumber(input) && !isOperator(input);
60+
return input == ' ' || input == '\t' || input == '\n' || input == '\r';
6161
}
6262

6363

@@ -85,10 +85,22 @@ class StringTokenizer
8585
return returnValue;
8686
}
8787

88-
[[nodiscard]] constexpr auto hasMoreTokens() const {
88+
[[nodiscard]] constexpr auto hasUnparsedInput() const noexcept {
89+
return currentOffset < string.size();
90+
}
91+
92+
[[nodiscard]] constexpr auto hasMoreTokens() const noexcept {
8993
return moreTokens;
9094
}
9195

96+
[[nodiscard]] constexpr auto input() const noexcept {
97+
return string;
98+
}
99+
100+
[[nodiscard]] constexpr auto offset() const noexcept {
101+
return currentOffset;
102+
}
103+
92104
private:
93105
std::string_view string;
94106
std::size_t currentOffset{0};

test/constexpr_tests.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ TEST_CASE("Addition")
2828
STATIC_REQUIRE(evaluate("(3 + (2 + 4))") == RationalNumber(9, 1));// NOLINT
2929
}
3030

31+
TEST_CASE("Subtraction")
32+
{
33+
STATIC_REQUIRE(evaluate("(3 - 2)") == RationalNumber(1, 1));// NOLINT
34+
STATIC_REQUIRE(evaluate("(3 + (2 - 4))") == RationalNumber(1, 1));// NOLINT
35+
}
36+
3137
TEST_CASE("Division")
3238
{
3339
STATIC_REQUIRE(evaluate("(3 / 2)") == RationalNumber(3, 2));// NOLINT

0 commit comments

Comments
 (0)