77#include < concepts>
88#include < string_view>
99#include < format>
10+ #include < expected>
11+ #include < stdexcept>
1012
1113enum struct Operators { PLUS_SIGN, CLOSE_PAREN, OPEN_PAREN, MINUS_SIGN, DIVIDE_SIGN, MULTIPLY_SIGN };
1214
15+ struct EvaluationError {
16+ enum class ErrorType {
17+ INVALID_NUMBER,
18+ INVALID_EXPRESSION,
19+ EMPTY_TOKEN,
20+ STACK_EMPTY
21+ };
22+
23+ ErrorType type;
24+ std::string message;
25+
26+ constexpr EvaluationError (ErrorType t, std::string_view msg) : type(t), message(msg) {}
27+ };
28+
1329constexpr auto precedence (Operators input) noexcept -> int
1430{
1531 switch (input) {
@@ -29,7 +45,8 @@ constexpr auto precedence(Operators input) noexcept -> int
2945}
3046
3147
32- constexpr void evaluateStacks (Stack<RationalNumber> &numbers, Stack<Operators> &operators)
48+ constexpr auto evaluateStacks (Stack<RationalNumber> &numbers, Stack<Operators> &operators)
49+ -> std::expected<void, EvaluationError>
3350{
3451 bool eatOpenParen = false ;
3552 bool cont = true ;
@@ -54,85 +71,148 @@ constexpr void evaluateStacks(Stack<RationalNumber> &numbers, Stack<Operators> &
5471
5572 case Operators::PLUS_SIGN: {
5673 operators.pop ();
57- const auto operand2 = numbers.pop ();
58- const auto operand1 = numbers.pop ();
59- numbers.push (operand1 + operand2);
74+ auto operand2_result = numbers.pop ();
75+ if (!operand2_result) {
76+ return std::unexpected (EvaluationError (
77+ EvaluationError::ErrorType::STACK_EMPTY,
78+ std::format (" Missing second operand for + operation: {}" , operand2_result.error ().message )));
79+ }
80+
81+ auto operand1_result = numbers.pop ();
82+ if (!operand1_result) {
83+ return std::unexpected (EvaluationError (
84+ EvaluationError::ErrorType::STACK_EMPTY,
85+ std::format (" Missing first operand for + operation: {}" , operand1_result.error ().message )));
86+ }
87+
88+ numbers.push (*operand1_result + *operand2_result);
6089 break ;
6190 }
6291
6392 case Operators::MINUS_SIGN: {
6493 operators.pop ();
65- const auto operand2 = numbers.pop ();
66- const auto operand1 = numbers.pop ();
67- numbers.push (operand1 - operand2);
94+ auto operand2_result = numbers.pop ();
95+ if (!operand2_result) {
96+ return std::unexpected (EvaluationError (
97+ EvaluationError::ErrorType::STACK_EMPTY,
98+ std::format (" Missing second operand for - operation: {}" , operand2_result.error ().message )));
99+ }
100+
101+ auto operand1_result = numbers.pop ();
102+ if (!operand1_result) {
103+ return std::unexpected (EvaluationError (
104+ EvaluationError::ErrorType::STACK_EMPTY,
105+ std::format (" Missing first operand for - operation: {}" , operand1_result.error ().message )));
106+ }
107+
108+ numbers.push (*operand1_result - *operand2_result);
68109 break ;
69110 }
70111
71112 case Operators::MULTIPLY_SIGN: {
72113 operators.pop ();
73- const auto operand2 = numbers.pop ();
74- const auto operand1 = numbers.pop ();
75- numbers.push (operand1 * operand2);
114+ auto operand2_result = numbers.pop ();
115+ if (!operand2_result) {
116+ return std::unexpected (EvaluationError (
117+ EvaluationError::ErrorType::STACK_EMPTY,
118+ std::format (" Missing second operand for * operation: {}" , operand2_result.error ().message )));
119+ }
120+
121+ auto operand1_result = numbers.pop ();
122+ if (!operand1_result) {
123+ return std::unexpected (EvaluationError (
124+ EvaluationError::ErrorType::STACK_EMPTY,
125+ std::format (" Missing first operand for * operation: {}" , operand1_result.error ().message )));
126+ }
127+
128+ numbers.push (*operand1_result * *operand2_result);
76129 break ;
77130 }
78131
79132 case Operators::DIVIDE_SIGN: {
80133 operators.pop ();
81- const auto operand2 = numbers.pop ();
82- const auto operand1 = numbers.pop ();
83- numbers.push (operand1 / operand2);
134+ auto operand2_result = numbers.pop ();
135+ if (!operand2_result) {
136+ return std::unexpected (EvaluationError (
137+ EvaluationError::ErrorType::STACK_EMPTY,
138+ std::format (" Missing second operand for / operation: {}" , operand2_result.error ().message )));
139+ }
140+
141+ auto operand1_result = numbers.pop ();
142+ if (!operand1_result) {
143+ return std::unexpected (EvaluationError (
144+ EvaluationError::ErrorType::STACK_EMPTY,
145+ std::format (" Missing first operand for / operation: {}" , operand1_result.error ().message )));
146+ }
147+
148+ numbers.push (*operand1_result / *operand2_result);
84149 break ;
85150 }
86151
87152 case Operators::CLOSE_PAREN:
88153 break ;// we want to continue
89154 }
90155 }
156+
157+ return {};
91158}
92159
93160
94- template <std::integral Type> [[nodiscard]] constexpr auto from_chars (std::string_view input) -> Type
161+ template <std::integral Type>
162+ [[nodiscard]] constexpr auto from_chars (std::string_view input) -> std::expected<Type, EvaluationError>
95163{
96164 Type result{ 0 };
97165
98166 for (const char digit : input) {
99167 result *= 10 ;// NOLINT
100168
101- if (digit >= ' 0' && digit <= ' 9' ) { result += static_cast <Type>(digit - ' 0' ); } else {
102- throw std::range_error (" not a number" );
169+ if (digit >= ' 0' && digit <= ' 9' ) {
170+ result += static_cast <Type>(digit - ' 0' );
171+ } else {
172+ return std::unexpected (EvaluationError (
173+ EvaluationError::ErrorType::INVALID_NUMBER,
174+ std::format (" Invalid digit '{}' in number" , digit)));
103175 }
104176 }
105177
106178 return result;
107179}
108180
109- [[nodiscard]] constexpr auto evaluateExpression (StringTokenizer &tokenizer) -> RationalNumber
181+ [[nodiscard]] constexpr auto evaluateExpression (StringTokenizer &tokenizer)
182+ -> std::expected<RationalNumber, EvaluationError>
110183{
111184 Stack<Operators> operators;
112185 Stack<RationalNumber> numbers;
113186
114- const auto throw_error = [&tokenizer] [[noreturn]] () {
115- throw std::runtime_error ( std::format (
187+ const auto make_error = [&tokenizer](EvaluationError::ErrorType type, std::string_view msg = " " ) -> EvaluationError {
188+ std::string contextual_message = std::format (
116189 R"( Unable to evaluate expression
117190{}
118191{}^ unevaluated)" ,
119192 tokenizer.input (),
120- std::string (tokenizer.offset (), ' ' )));
193+ std::string (tokenizer.offset (), ' ' ));
194+
195+ if (!msg.empty ()) {
196+ contextual_message = std::format (" {}: {}" , contextual_message, msg);
197+ }
198+
199+ return EvaluationError (type, contextual_message);
121200 };
122201
123- const auto evalStacks = [&]() {
124- try {
125- evaluateStacks (numbers, operators);
126- } catch (const std::runtime_error &) {
127- throw_error ();
202+ const auto evalStacks = [&]() -> std::expected<void , EvaluationError> {
203+ auto result = ::evaluateStacks (numbers, operators);
204+ if (!result) {
205+ return std::unexpected (make_error (result.error ().type , result.error ().message ));
128206 }
207+ return {};
129208 };
130209
131210 while (tokenizer.hasMoreTokens ()) {
132-
133211 auto next = tokenizer.nextToken ();
134212
135- if (next.empty ()) { throw_error (); }
213+ if (next.empty ()) {
214+ return std::unexpected (make_error (EvaluationError::ErrorType::EMPTY_TOKEN));
215+ }
136216
137217 auto value = Operators::PLUS_SIGN;
138218
@@ -166,55 +246,84 @@ template<std::integral Type> [[nodiscard]] constexpr auto from_chars(std::string
166246
167247 default :
168248 operation = false ;
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 ();
249+ auto parsed_result = from_chars<int >(next);
250+ if (!parsed_result) {
251+ return std::unexpected (make_error (parsed_result.error ().type , parsed_result.error ().message ));
174252 }
253+ numbers.emplace (*parsed_result, 1 );
175254 break ;
176255 }
177256
178257 if (operation) {
179258 switch (value) {
180- case Operators::OPEN_PAREN:
259+ case Operators::OPEN_PAREN: {
181260 operators.push (value);
182261 break ;
183- case Operators::CLOSE_PAREN:
262+ }
263+ case Operators::CLOSE_PAREN: {
184264 operators.push (value);
185- evalStacks ();
265+ auto eval_result = evalStacks ();
266+ if (!eval_result) {
267+ return std::unexpected (eval_result.error ());
268+ }
186269 break ;
187- default :
188- if (operators.peek () != nullptr && precedence (value) <= precedence (*operators.peek ())) { evalStacks (); }
270+ }
271+ case Operators::PLUS_SIGN:
272+ case Operators::MINUS_SIGN:
273+ case Operators::MULTIPLY_SIGN:
274+ case Operators::DIVIDE_SIGN: {
275+ if (operators.peek () != nullptr && precedence (value) <= precedence (*operators.peek ())) {
276+ auto eval_result = evalStacks ();
277+ if (!eval_result) {
278+ return std::unexpected (eval_result.error ());
279+ }
280+ }
189281 operators.push (value);
190282 break ;
191283 }
284+ }
192285 }
193286 }
194287 }
195288
196- if (operators.peek () != nullptr ) { evalStacks (); }
289+ if (operators.peek () != nullptr ) {
290+ auto eval_result = evalStacks ();
291+ if (!eval_result) {
292+ return std::unexpected (eval_result.error ());
293+ }
294+ }
197295
198296 if (!operators.empty () || tokenizer.hasUnparsedInput ()) {
199- throw_error ( );
297+ return std::unexpected ( make_error (EvaluationError::ErrorType::INVALID_EXPRESSION) );
200298 }
201299
202300 if (numbers.peek () != nullptr ) {
203301 return *numbers.peek ();
204302 }
205303
206- throw_error ( );
304+ return std::unexpected ( make_error (EvaluationError::ErrorType::INVALID_EXPRESSION) );
207305}
208306
209- [[nodiscard]] constexpr auto evaluate (std::string_view input) -> RationalNumber
307+ [[nodiscard]] constexpr auto evaluate (std::string_view input) -> std::expected< RationalNumber, EvaluationError>
210308{
211309 StringTokenizer tokenizer (input);
212310 return evaluateExpression (tokenizer);
213311}
214312
313+ consteval auto evaluate_or_throw (std::string_view input) -> RationalNumber
314+ {
315+ StringTokenizer tokenizer (input);
316+ auto result = evaluateExpression (tokenizer);
317+ if (!result) {
318+ // During compile-time evaluation, we can't do much better than a generic error message
319+ throw std::runtime_error (" Invalid expression during consteval" );
320+ }
321+ return *result;
322+ }
323+
215324consteval auto operator " " _rn(const char *str, std::size_t len) -> RationalNumber
216325{
217- return evaluate (std::string_view (str, len));
326+ return evaluate_or_throw (std::string_view (str, len));
218327}
219328
220329
0 commit comments