calculator_backend/lib.rs
1///./src/lib.rs
2/// This file contains the main logic for a calculator library that supports Reverse Polish Notation (RPN) and infix expressions.
3/// It includes functions for tokenization, conversion between infix and RPN, and evaluation of expressions.
4/// The library is designed to be used with a C library for evaluation, and it provides a C-compatible interface for integration.
5
6//use serde::{Serialize, Deserialize};
7use std::ffi::{CString, CStr, c_char};
8use std::os::raw::{c_double, c_int};
9
10// Error codes matching C
11const SUCCESS: c_int = 0;
12const DIVISION_BY_ZERO: c_int = 1;
13const INVALID_OPERATOR: c_int = 2;
14const STACK_UNDERFLOW: c_int = 3;
15const MEMORY_ERROR: c_int = 4;
16const UNDEFINED_VARIABLE: c_int = 5;
17const STACK_MAXIMUM: c_int = 6;
18const EXPR_LENGHT_MAXIMUM: c_int = 7;
19const FACTORIAL_ERROR: c_int = 8;
20const SQUARE_ROOT_ERROR: c_int = 9;
21const LOG_ERROR: c_int = 10;
22const LN_ERROR: c_int = 11;
23const TAN_INVALID_OPERATOR: c_int = 12;
24const INVALID_TRIG_OPERATOR: c_int = 13;
25
26/// Stores the result of the calculation
27/// S
28/// # Fields
29///
30/// * `result_value`: The calculated result, a C-compatible double-precision floating-point number (`c_double`).
31/// * `error_code`: An integer error code (`c_int`) indicating the success or failure of the calculation.
32/// * `0` represents success.
33/// * Other values indicate specific errors.
34#[repr(C)]
35#[derive(Debug)]
36pub struct CCalculationResult {
37 pub result_value: c_double,
38 pub error_code: c_int,
39}
40
41/// Returns a human-readable error message based on the provided error code.
42///
43/// This function acts as a wrapper around the `get_error_message` function, providing
44/// a simpler interface for retrieving error messages.
45///
46/// # Arguments
47///
48/// * `error_code`: An integer (`c_int`) representing the error code.
49///
50/// # Returns
51///
52/// A static string slice (`&'static str`) containing a human-readable description of the error.
53///
54/// # Supported Error Codes
55///
56/// * `0` (`SUCCESS`): "Success"
57/// * `1` (`DIVISION_BY_ZERO`): "Division by zero"
58/// * `2` (`INVALID_OPERATOR`): "Invalid operator"
59/// * `3` (`STACK_UNDERFLOW`): "Stack underflow - invalid expression"
60/// * `4` (`MEMORY_ERROR`): "Memory error"
61/// * `5` (`UNDEFINED_VARIABLE`): "Undefined variable in expression"
62/// * `6` (`STACK_MAXIMUM`): "Stack maximum exceeded"
63/// * `7` (`EXPR_LENGHT_MAXIMUM`): "Expression length maximum exceeded"
64/// * `8` (`FACTORIAL_ERROR`): "Factorial error"
65/// * `9` (`SQUARE_ROOT_ERROR`): "Square root error"
66/// * `10` (`LOG_ERROR`): "Log error"
67/// * `11` (`LN_ERROR`): "Natural logarithm error"
68/// * Any other value: "Unknown error"
69///
70pub fn get_error_str(error_code: c_int) -> &'static str {
71 get_error_message(error_code)
72}
73
74/// Reverse Polish Notatio3n (RPN) as a C syle array
75///
76/// # Fields
77///
78/// * `crpn_expression`: A pointer (`*const *const c_char`) to a read-only array of C-style strings. Each string represents an element of the RPN expression.
79/// * `length`: The number of elements in the `expression` array.
80#[repr(C)]
81#[derive(Debug)]
82pub struct CReversePolishExpression {
83 pub crpn_expression: *const *const c_char,
84 pub length: usize,
85}
86
87/// Represents the result of converting an infix expression to Reverse Polish Notation (RPN) in a C-compatible format.
88///
89/// # Fields
90///
91/// * `result_expression`: A fixed-size array of C-style characters (`c_char`) representing the converted RPN expression.
92/// * `error_code`: An integer error code (`c_int`) indicating the success or failure of the conversion.
93/// * `0` represents success.
94/// * Other values indicate specific errors.
95#[repr(C)]
96pub struct CConversionResult {
97 result_expression: [c_char; 1000], // MAX_EXPR_LENGTH from C
98 error_code: c_int,
99}
100
101// Externally defined C functions for Reverse Polish Notation (RPN) calculations and conversions.
102//
103// These functions are implemented in the C library and are exposed to Rust using the `extern "C"` block.
104//
105// # Functions
106//
107// ## `calculate_rpn`
108// Evaluates a Reverse Polish Notation (RPN) expression and returns the result.
109//
110// ### Arguments
111// * `expr`: A pointer to a `CReversePolishExpression` struct, which contains the RPN expression to be evaluated.
112//
113// ### Returns
114// A `CCalculationResult` struct containing:
115// * `result_value`: The calculated result as a `c_double`.
116// * `error_code`: An integer (`c_int`) indicating the success or failure of the calculation.
117// * `0` (`SUCCESS`) indicates success.
118// * Other values indicate specific errors (e.g., `DIVISION_BY_ZERO`, `INVALID_OPERATOR`).
119//
120// ## `convert_rpn_to_infix`
121// Converts a Reverse Polish Notation (RPN) expression to an infix expression.
122//
123// ### Arguments
124// * `expr`: A pointer to a `CReversePolishExpression` struct, which contains the RPN expression to be converted.
125//
126// ### Returns
127// A `CConversionResult` struct containing:
128// * `result_expression`: A fixed-size array of `c_char` representing the converted infix expression.
129// * `error_code`: An integer (`c_int`) indicating the success or failure of the conversion.
130// * `0` (`SUCCESS`) indicates success.
131// * Other values indicate specific errors.
132//
133// # Safety
134//
135// These functions are marked as `unsafe` because they involve raw pointers and interaction with a C library.
136// Ensure that the pointers passed to these functions are valid and properly aligned.
137extern "C" {
138 pub fn calculate_rpn(expr: *const CReversePolishExpression) -> CCalculationResult;
139 fn convert_rpn_to_infix(expr: *const CReversePolishExpression) -> CConversionResult;
140}
141
142/// Represents a history entry for a mathematical expression and its result.
143/// # Fields
144///
145/// * `input`: A string containing the original mathematical expression provided by the user.
146/// * `result`: An optional floating-point number (`Option<f64>`) representing the calculated result.
147/// * `error_message`: An optional string (`Option<String>`) containing an error message if the calculation failed.
148/// * `None` if there was no error.
149///
150/// This struct is used to log the history of calculations performed by the calculator.
151#[derive(Debug, Clone)]
152pub struct HistoryEntry {
153 pub input: String,
154 pub result: Option<f64>, // `None` if there was an error
155 pub error_message: Option<String>, // `None` if there was no error
156}
157
158/// Represents the history of calculations performed by the calculator.
159///
160/// # Fields
161///
162/// * `entries`: A vector of `HistoryEntry` structs, each representing a single calculation.
163/// * `last_result`: An optional floating-point number (`Option<f64>`) representing the result of the most recent successful calculation.
164///
165/// # Methods
166///
167/// ## `new`
168/// Creates a new, empty `History` instance.
169///
170/// ## `add_entry`
171/// Adds a new entry to the history.
172///
173/// ### Arguments
174/// * `input`: A string representing the mathematical expression provided by the user.
175/// * `result`: An optional floating-point number representing the result of the calculation. Use `None` if the calculation failed.
176/// * `error_message`: An optional string containing an error message if the calculation failed. Use `None` if there was no error.
177///
178/// ## `get_history`
179/// Returns a reference to the vector of `HistoryEntry` structs.
180///
181/// ## `get_last_result`
182/// Returns the result of the most recent successful calculation, or `None` if no successful calculation has been performed.
183#[derive(Debug)]
184pub struct History {
185 pub entries: Vec<HistoryEntry>,
186 pub last_result: Option<f64>,
187}
188
189/// Represents the history of calculations performed by the calculator.
190///
191/// # Fields
192///
193/// * `entries`: A vector of `HistoryEntry` structs, each representing a single calculation.
194/// * `last_result`: An optional floating-point number (`Option<f64>`) representing the result of the most recent successful calculation.
195///
196/// # Methods
197///
198/// ## `new`
199/// Creates a new, empty `History` instance.
200///
201/// ## `add_entry`
202/// Adds a new entry to the history.
203///
204/// ### Arguments
205/// * `input`: A string representing the mathematical expression provided by the user.
206/// * `result`: An optional floating-point number representing the result of the calculation. Use `None` if the calculation failed.
207/// * `error_message`: An optional string containing an error message if the calculation failed. Use `None` if there was no error.
208///
209/// ## `get_history`
210/// Returns a reference to the vector of `HistoryEntry` structs.
211///
212/// ## `get_last_result`
213/// Returns the result of the most recent successful calculation, or `None` if no successful calculation has been performed.
214impl History {
215 pub fn new() -> Self {
216 History {
217 entries: Vec::new(),
218 last_result: None,
219 }
220 }
221
222 pub fn add_entry(&mut self, input: String, result: Option<f64>, error_message: Option<String>) {
223 if let Some(res) = result {
224 self.last_result = Some(res); // Update the last result
225 }
226 self.entries.push(HistoryEntry {
227 input,
228 result,
229 error_message,
230 });
231 }
232
233 pub fn get_history(&self) -> &Vec<HistoryEntry> {
234 &self.entries
235 }
236
237 pub fn get_last_result(&self) -> Option<f64> {
238 self.last_result
239 }
240}
241
242/// Displays the history of calculations in a human-readable format.
243///
244/// # Arguments
245///
246/// * `history`: A reference to the `History` instance to be displayed.
247pub fn display_history(history: &History) {
248 for (i, entry) in history.get_history().iter().enumerate() {
249 println!("Entry {}:", i + 1);
250 println!(" Input: {}", entry.input);
251 match &entry.result {
252 Some(result) => println!(" Result: {}", result),
253 None => println!(" Error: {}", entry.error_message.as_ref().unwrap()),
254 }
255 }
256}
257
258
259/// Reverse Polish Notation (RPN) expression represented as a vector of strings
260///
261/// # Fields
262///
263/// * `rp_expression`: A vector of strings (`Vec<String>`) representing the expression in Reverse Polish Notation (RPN).
264/// * Each string in the vector is a single token of the RPN expression (e.g., "2", "3", "+").
265pub struct ReversePolish {
266 rp_expression: Vec<String>
267}
268
269/// Result of the calculations done through the C Library.
270///
271/// # Fields
272///
273/// * `success`: A boolean indicating whether the calculation was successful.
274/// * `expression`: The original mathematical expression provided by the user.
275/// * `rpn_expression`: The corresponding expression in Reverse Polish Notation (RPN).
276/// * `result`: The numerical result of the calculation.
277/// * `message`: An additional message providing details about the calculation outcome,
278/// such as errors or warnings.
279impl ReversePolish {
280 pub fn to_string(&self) -> String {
281 self.rp_expression.join(" ")
282 }
283
284 pub fn to_c_expr(&self) -> Result<(Vec<CString>, Vec<*const c_char>), String> {
285 let mut expr_cstrings = Vec::with_capacity(self.rp_expression.len());
286 let mut expr_ptrs = Vec::with_capacity(self.rp_expression.len());
287
288 for s in &self.rp_expression {
289 let cstring = CString::new(s.as_str())
290 .map_err(|_| "Failed to convert expression to CString".to_string())?;
291 expr_ptrs.push(cstring.as_ptr());
292 expr_cstrings.push(cstring);
293 }
294
295 Ok((expr_cstrings, expr_ptrs))
296 }
297}
298
299/// Represents the result of a calculation performed using the C Library.
300///
301/// # Fields
302///
303/// * `success`: A boolean indicating whether the calculation was successful.
304/// * `expression`: The original mathematical expression provided by the user.
305/// * `rpn_expression`: The corresponding expression in Reverse Polish Notation (RPN).
306/// * `result`: The numerical result of the calculation.
307/// * `message`: A string containing additional details about the calculation outcome,
308/// such as errors or warnings.
309pub struct CalculationResult {
310 pub success: bool,
311 pub expression: String,
312 pub rpn_expression: String,
313 pub result: f64,
314 pub message: String,
315}
316
317
318/// Returns an error message based on given error code.
319///
320/// # Arguments
321///
322/// * 'error_code': An error code represented by a C-style integer.
323///
324/// # Returns
325///
326/// A static string slice containing a human-readable message describing the error.
327pub fn get_error_message(error_code: c_int) -> &'static str {
328 match error_code {
329 SUCCESS => "Success",
330 DIVISION_BY_ZERO => "Division by zero",
331 INVALID_OPERATOR => "Invalid operator",
332 STACK_UNDERFLOW => "Stack underflow - invalid expression",
333 MEMORY_ERROR => "Memory error",
334 UNDEFINED_VARIABLE => "Undefined variable in expression",
335 STACK_MAXIMUM => "Stack maximum exceeded",
336 EXPR_LENGHT_MAXIMUM => "Exprestion lenght maximum exceeded",
337 FACTORIAL_ERROR => "Factorial error",
338 SQUARE_ROOT_ERROR => "Square root error",
339 LOG_ERROR => "Log error",
340 LN_ERROR => "Natural logarithm error",
341 TAN_INVALID_OPERATOR => "Invalid operator for tangent",
342 INVALID_TRIG_OPERATOR => "Invalid trigonometric operator",
343 _ => "Unknown error"
344 }
345}
346
347/// Token types for the tokenization process.
348///
349/// * `Operator`: Represents mathematical operators such as `+`, `-`, `*`, `/`, `^`, `!`, and `√`.
350/// - Trigonometric functions: "sin", "cos", "tan", "arcsin", "arccos", "arctan"
351/// - Square root: "sqrt", "√"
352/// - Logarithmic functions: "log" (base-10 logarithm), "ln" (natural logarithm)
353/// - Special constants or functions: "ans"
354/// * `Operand`: Represents numeric values, including integers and floating-point numbers.
355/// * Example values: "2", "3.14", "10", "0"
356/// * `Variable`: Represents alphanumeric symbols used as variables or identifiers.
357/// * Example values: "x", "y", "result", "my_variable"
358/// * `Bracket`: Represents parentheses used to group expressions.
359/// * Example values: "(", ")"
360#[derive(Debug, Clone, PartialEq)]
361pub enum TokenType {
362 Operator,
363 Operand,
364 Variable,
365 Bracket,
366 Function
367}
368
369/// Represents a single token produced during the tokenization process.
370///
371/// Each `Token` consists of a string `value` and its corresponding `TokenType`, which categorizes the token.
372///
373/// # Fields
374///
375/// * `token_value`: A string containing the actual content of the token.
376/// * `token_type`: The type of the token, as defined by the `TokenType` enum.
377#[derive(Debug, Clone)]
378pub struct Token {
379 token_value: String,
380 token_type: TokenType,
381}
382
383/// Classifies an identifier as either an operator or a variable.
384///
385/// # Arguments
386///
387/// * `ident` - A string slice representing the identifier to be classified.
388///
389/// # Returns
390///
391/// A `TokenType` enum value:
392/// - `TokenType::Operator` if the identifier matches a known operator name.
393/// - `TokenType::Variable` if the identifier does not match any known operator name.
394///
395/// # Known Operators
396///
397/// The function recognizes the following operators:
398/// - Trigonometric functions: `"sin"`, `"cos"`, `"tan"`, `"arcsin"`, `"arccos"`, `"arctan"`
399/// - Square root: `"sqrt"`
400/// - Logarithmic functions: `"log"` (base-10 logarithm), `"ln"` (natural logarithm)
401/// - Special constants or functions: `"ans"`
402fn classify_identifier(ident: &str, history: &History) -> TokenType {
403 if ident == "ans" {
404 // Always resolve `ans` to the last result
405 if let Some(_last_result) = history.get_last_result() {
406
407 TokenType::Operand
408 } else {
409 TokenType::Operand // Default to 0.0 if no previous result exists
410 }
411 } else {
412 // Known operators
413 const OPERATORS_NAMES: &[&str] = &[
414 "sin", "cos", "tan", "arcsin", "arccos", "arctan", "sqrt", "log", "ln",
415 ];
416 if OPERATORS_NAMES.contains(&ident) {
417 TokenType::Operator
418 } else {
419 TokenType::Variable
420 }
421 }
422}
423
424///Determines if token is a valid number.
425///
426/// # Fields
427///
428/// *`s`: A string slice representing the token
429///
430/// # Returns
431///
432/// A boolean:
433/// -'true' if valid number (e.g 123,1.23,-1.23,)
434/// -'false` otherwise
435fn is_numeric(s: &str) -> bool {
436 s.parse::<f64>().is_ok() //Standard rust parser that does a way better job than my implementation did :(
437}
438
439/// Tokenizes an input string into a vector of tokens.
440///
441/// # Arguments
442///
443/// * `input` - A string slice representing the input to be tokenized.
444///
445/// # Returns
446///
447/// A vector of `Token` structs, each representing a part of the input.
448///
449/// # Token Types
450///
451/// - `Operator`: Symbols representing mathematical operations (`+`, `-`, `*`, `/`, `^`, '!').
452/// - Trigonometric functions: "sin", "cos", "tan", "arcsin", "arccos", "arctan"
453/// - Square root: "sqrt"
454/// - Logarithmic functions: "log" (base-10 logarithm), "ln" (natural logarithm)
455/// - Special constants or functions: "ans"
456/// - `Operand`: Numeric values, which can include decimal points (`2`, `3`, `4`, `9.23`, `0`).
457/// - `Variable`: Alphanumeric symbols representing unknowns (`x`, `y`, `z`, `_`). (UNUSED CURRENTLY)
458/// - `Bracket`: Parentheses used in expressions (`(`, `)`).
459pub fn tokenize(input: &str, history: &History) -> Vec<Token> {
460 let mut tokens: Vec<Token> = Vec::new();
461 let binding = input.to_lowercase();
462 let mut chars = binding.chars().peekable();
463 let mut current_pos = 0; //Keeps track of where the tokenizer is in the expression
464 let _input = input.replace("√", "sqrt");
465
466 while let Some(&c) = chars.peek() {
467 current_pos += 1; // Increment position conceptually for error messages
468
469 match c {
470 // === Whitespace ===
471 w if w.is_whitespace() => {
472 chars.next(); // Consume whitespace
473 }
474
475 // === Brackets ==
476 '(' | ')' => {
477 tokens.push(Token {
478 token_value: c.to_string(),
479 token_type: TokenType::Bracket,
480 });
481 chars.next(); // Consume bracket
482 }
483
484 // === Operators (excluding '-') ===
485 '+' | '*' | '/' | '^' | '!' => {
486
487 tokens.push(Token {
488 token_value: c.to_string(),
489 token_type: TokenType::Operator,
490 });
491 chars.next(); // Consume operator
492 }
493 '√' => {
494 // Handle square root as a
495 tokens.push(Token {
496 token_value: "sqrt".to_string(),
497 token_type: TokenType::Operator,
498 });
499 chars.next(); // Consume '√'
500 }
501 // === Minus Sign or Negative Number(Operator or start of Number) ===
502 '-' => {
503 let is_unary = tokens.is_empty() || matches!(tokens.last(), Some(Token { token_type: TokenType::Operator | TokenType::Bracket, ..}));
504
505 if is_unary {
506 // Potentially start of a new negative number
507 let mut num_string = String::new();
508 num_string.push(chars.next().unwrap()); // Consume '-'
509
510 //Consume rest of digits and one optional '.'
511 while let Some(&next_c) = chars.peek() {
512 if next_c.is_digit(10) || (next_c == '.' && !num_string.contains('.')) {
513 // Consume the character *only if* it's part of the number
514 num_string.push(chars.next().unwrap());
515 } else {
516 break; // Not part of number
517 }
518 }
519
520 //Validate if a negative number was formed
521 if is_numeric(&num_string) {
522 tokens.push(Token {
523 token_value: num_string, // num_string owns the value now
524 token_type: TokenType::Operand,
525 });
526 } else {
527 //If just "-". gets treated as an operator
528 if num_string == "-" {
529 tokens.push(Token {
530 token_value: "-".to_string(),
531 token_type: TokenType::Operator,
532 });
533 } else {
534 eprintln!("Warning: Invalid sequence starting with '-' ('{}') at position {}", num_string, current_pos);
535 if num_string != "-" { // Avoid double-pushing if it was exactly "-"
536 tokens.push(Token {
537 token_value: "-".to_string(),
538 token_type: TokenType::Operator,
539 });
540 // The non-digit/dot character after '-' will be handled in the next loop iteration.
541 }
542
543 }
544 }
545 } else {
546 //Binary operator
547 tokens.push(Token {
548 token_value: c.to_string(),
549 token_type: TokenType::Operator,
550 });
551 chars.next(); // Consume '-' operator
552 }
553 }
554
555 // === Numbers (starting with digit or '.') ===
556 d if d.is_digit(10) || d == '.' => {
557 let mut num_str = String::new();
558 let mut has_decimal = d == '.';
559 num_str.push(chars.next().unwrap()); // Consume first digit or '.'
560
561 while let Some(&next_c) = chars.peek() {
562 if next_c.is_digit(10) {
563 num_str.push(chars.next().unwrap());
564 } else if next_c == '.' && !has_decimal {
565 has_decimal = true;
566 num_str.push(chars.next().unwrap());
567 } else {
568 break; // End of number
569 }
570 }
571
572 // === Scientific Notation ===
573 if let Some(&'e') = chars.peek() {
574 num_str.push(chars.next().unwrap()); // Consume e
575
576 //Catch direction decimal is moving in
577 if let Some(&next_c) = chars.peek() {
578 if next_c == '-' || next_c == '+' {
579 num_str.push(chars.next().unwrap());
580 }
581
582 //Consume the rest of the number
583 while let Some(&next_c) = chars.peek() {
584 if next_c.is_digit(10) {
585 num_str.push(chars.next().unwrap());
586 } else {
587 break;
588 }
589
590 }
591 }
592 }
593
594 // Validate if number was formed
595 if is_numeric(&num_str) {
596 tokens.push(Token {
597 token_value: num_str,
598 token_type: TokenType::Operand,
599 });
600 } else {
601 // Handle error: Invalid sequence like "." or "1.2.3"
602 eprintln!("Warning: Invalid numeric sequence '{}' at position {}", num_str, current_pos);
603 // How to recover? Skip? Push as unknown? For now, just warns.
604 }
605 }
606
607 // ==== Identifier (Variables or Functions) ===
608 // Original code only checked is_alphabetic(), let's add `|| a == '_'`
609 a if a.is_alphabetic() => {
610 let mut ident_str = String::new();
611 ident_str.push(chars.next().unwrap());
612
613 while let Some(&next_c) = chars.peek() {
614 if next_c.is_alphanumeric() {
615 ident_str.push(chars.next().unwrap());
616 } else {
617 break;
618 }
619 }
620
621 if ident_str == "ans" {
622 // Replace `ans` with the last result from history
623 let last_result = history.get_last_result().unwrap_or(0.0);
624 tokens.push(Token {
625 token_value: last_result.to_string(),
626 token_type: TokenType::Operand,
627 });
628 } else {
629 let token_type = classify_identifier(&ident_str, history);
630 tokens.push(Token {
631 token_value: ident_str,
632 token_type,
633 });
634 }
635 }
636
637
638 // == Unknown Character ===
639 _ => {
640 eprintln!("Warning: Skipping unknown character '{}' at position {}", c, current_pos);
641 chars.next(); // Consume the unknown character
642 }
643 }
644 }
645 println!("Tokens: {:?}", tokens);
646 tokens
647}
648
649/// Determines the precedence of an operator.
650///
651/// # Arguments
652///
653/// * `op`- A string slice representing the operator.
654///
655/// # Returns
656///
657/// An integer representing the precedence of the operator.
658/// Returns 0 for unsupported or invalid operators.
659pub fn get_precedence(op: &str) -> i32 {
660 match op {
661 "+" | "-" => 1,
662 "*" | "/" => 2,
663 "^" => 3,
664 "!" | "√" | "sqrt" | "log" | "ln" => 4,
665 "sin"| "cos"| "tan" | "arctan"| "arcsin"| "arccos" => 5,
666 _ => 0
667 }
668}
669
670/// Determines if operator is a right associative.
671///
672/// #Arguments
673///
674/// * 'op': A string slice representing the operator being checked.
675///
676/// #Returns
677///
678/// A boolean value:
679/// - `true` if the operator is right-associative (e.g., `^`, '!').
680/// - `false` otherwise.
681pub fn is_right_associative(op: &str) -> bool {
682 op == "^" || op == "!"
683}
684
685/// Takes an infix expression and converts it into a Reverse Polish Notation (RPN) expression.
686///
687/// # Arguments
688///
689/// * `input`: A string slice representing the infix expression to be converted to Reverse Polish Notation (RPN).
690///
691/// # Returns
692///
693/// * `Ok(ReversePolish)`: If the conversion is successful, where `ReversePolish` holds the Reverse Polish Notation (RPN) expression.
694/// * `Err(String)`: If the conversion fails.
695///
696/// # Errors
697///
698/// This function returns an `Err(String)` with a descriptive error message in the following cases:
699///
700/// * "Mismatched parentheses": If the input expression has an unbalanced number of opening and closing parentheses.
701/// * "Invalid token: ..." : If the tokenizer encounters an unexpected character or sequence of characters that cannot be recognized as a valid token.
702pub fn infix_to_rpn(input: &str, _history: &History) -> Result<ReversePolish, String> {
703 let tokens = tokenize(input, _history);
704 let mut output = Vec::new();
705 let mut op_stack: Vec<Token> = Vec::new();
706
707 fn is_prefix_unary(op: &str) -> bool {
708 op == "sqrt"
709 }
710
711 for token in tokens {
712 match token.token_type {
713 TokenType::Operand | TokenType::Variable => {
714 output.push(token.token_value.clone());
715 if let Some(last_op) = op_stack.last() {
716 if is_prefix_unary(&last_op.token_value) {
717 let op = op_stack.pop().unwrap();
718 output.push(op.token_value);
719 }
720 }
721 }
722 TokenType::Operator => {
723 if is_prefix_unary(&token.token_value) {
724 // Delay adding to output until we see the operand
725 op_stack.push(token);
726 }else {
727 while let Some(top) = op_stack.last() {
728 if top.token_value == "(" {
729 break;
730 }
731 let curr_prec = get_precedence(&token.token_value);
732 let top_prec = get_precedence(&top.token_value);
733
734 if curr_prec > top_prec || (curr_prec == top_prec && is_right_associative(&token.token_value)) {
735 break;
736 }
737 if let Some(op) = op_stack.pop() {
738 output.push(op.token_value);
739 }
740 }
741 op_stack.push(token);
742 }
743 }
744 TokenType::Bracket => {
745 if token.token_value == "(" {
746 op_stack.push(token);
747 } else {
748 while let Some(top) = op_stack.pop() {
749 if top.token_value == "(" {
750 break;
751 }
752 output.push(top.token_value);
753 }
754 }
755 }
756 _ => {}
757 }
758 }
759
760 while let Some(op) = op_stack.pop() {
761 output.push(op.token_value);
762 }
763
764 Ok(ReversePolish {
765 rp_expression: output,
766 })
767}
768
769/// Endpoint to process a mathematical expression and return the result as a string.
770///
771/// #Arguments
772/// * 'input': A string representation of the infix expression to be processed.
773///
774/// #Returns
775///
776/// 'CalculationResult' structure containing:
777/// - `success`: A boolean indicating whether the calculation was successful.
778/// - `expression`: The original mathematical expression provided by the user.
779/// - `rpn_expression`: The corresponding expression in Reverse Polish Notation (RPN).
780/// - `result`: The numerical result of the calculation.
781/// - `message`: An additional message providing details about the calculation outcome,
782/// such as errors or warnings.
783///
784/// #Errors
785///
786/// This function returns an error code in the following cases:
787/// - If the expression contains mismatched parentheses or cannot be tokenized, an error message is returned.
788/// - If the conversion to C-compatible format fails, an error message is returned.
789/// - If the C function for evaluation fails, an error message with details is included in the response.
790pub fn calculate_expression(input: &str, history: &mut History) -> CalculationResult {
791 let input = input.trim_matches('"');
792
793 match infix_to_rpn(input, history) {
794 Ok(rpn) => {
795 let rpn_str = rpn.to_string();
796
797 let (_expr_cstrings, expr_ptrs) = match rpn.to_c_expr() {
798 Ok(data) => data,
799 Err(e) => {
800 history.add_entry(
801 input.to_string(),
802 None,
803 Some(format!("Failed to convert to RPN: {}", e)),
804 );
805 return CalculationResult {
806 success: false,
807 expression: input.to_string(),
808 rpn_expression: rpn_str,
809 result: 0.0,
810 message: e,
811 };
812 }
813 };
814
815 let c_expr = CReversePolishExpression {
816 crpn_expression: expr_ptrs.as_ptr(),
817 length: expr_ptrs.len(),
818 };
819
820 let result = unsafe { calculate_rpn(&c_expr) };
821
822 let success = result.error_code == SUCCESS;
823 let message = get_error_message(result.error_code).to_string();
824
825 history.add_entry(
826 input.to_string(),
827 if success { Some(result.result_value) } else { None },
828 if success { None } else { Some(message.clone()) },
829 );
830
831 CalculationResult {
832 success,
833 expression: input.to_string(),
834 rpn_expression: rpn_str,
835 result: result.result_value,
836 message,
837 }
838 }
839 Err(e) => {
840 history.add_entry(
841 input.to_string(),
842 None,
843 Some(format!("Failed to parse expression: {}", e)),
844 );
845 CalculationResult {
846 success: false,
847 expression: input.to_string(),
848 rpn_expression: String::new(),
849 result: 0.0,
850 message: format!("Failed to parse expression: {}", e),
851 }
852 }
853 }
854}
855
856/// Represents the result of a conversion between Reverse Polish Notation (RPN) and infix notation.
857///
858/// # Fields
859///
860/// * `success` - A boolean indicating whether the conversion was successful.
861/// * `rpn_expression` - A string containing the RPN representation of the expression.
862/// * `infix_expression` - A string containing the infix representation of the expression.
863/// * `message` - A string with additional information, such as error messages or status notes.
864pub struct ConversionResult {
865 pub success: bool,
866 pub rpn_expression: String,
867 pub infix_expression: String,
868 pub message: String,
869}
870
871// Make it accessible in tests
872#[cfg(test)]
873impl CalculationResult {
874 pub fn success(&self) -> bool {
875 self.success
876 }
877
878 pub fn result(&self) -> f64 {
879 self.result
880 }
881}
882
883/// Endpoint to convert a Reverse Polish Notation (RPN) expression to an infix expression.
884///
885/// #Arguments
886///
887/// * 'input': A string containing the RPN expression to be converted
888///
889/// #Returns
890///
891/// A 'CalculationResult' structure containing:
892/// - `success`: A boolean indicating whether the calculation was successful.
893/// - `expression`: The original mathematical expression provided by the user.
894/// - `rpn_expression`: The corresponding expression in Reverse Polish Notation (RPN).
895/// - `result`: The numerical result of the calculation.
896/// - `message`: An additional message providing details about the calculation outcome,
897/// such as errors or warnings.
898///
899/// #Errors
900///
901/// This function returns an error code in the following cases:
902/// - If the expression contains mismatched parentheses or cannot be tokenized, an error message is returned.
903/// - If the conversion to C-compatible format fails, an error message is returned.
904/// - If the C function for evaluation fails, an error message with details is included in the response.
905pub fn convert_rpn(input: String) -> ConversionResult {
906 let input = input.trim_matches('"');
907 let tokens: Vec<String> = input.split_whitespace().map(String::from).collect();
908 let rpn = ReversePolish { rp_expression: tokens };
909
910 let (_expr_cstrings, expr_ptrs) = match rpn.to_c_expr() {
911 Ok(data) => data,
912 Err(e) => {
913 return ConversionResult {
914 success: false,
915 rpn_expression: input.to_string(),
916 infix_expression: String::new(),
917 message: e
918 };
919 }
920 };
921
922 let c_expr = CReversePolishExpression {
923 crpn_expression: expr_ptrs.as_ptr(),
924 length: expr_ptrs.len(),
925 };
926
927 let result = unsafe {
928 let c_result = convert_rpn_to_infix(&c_expr);
929 let infix_expression = match CStr::from_ptr(c_result.result_expression.as_ptr()).to_str() {
930 Ok(s) => s.to_owned(),
931 Err(_) => return ConversionResult {
932 success: false,
933 rpn_expression: input.to_string(),
934 infix_expression: String::new(),
935 message: "Invalid UTF-8 in result".to_string()
936 }
937 };
938 (c_result.error_code, infix_expression)
939 };
940
941 let (error_code, infix_expression) = result;
942 let success = error_code == SUCCESS;
943 let message = get_error_message(error_code).to_string();
944
945 ConversionResult {
946 success,
947 rpn_expression: input.to_string(),
948 infix_expression,
949 message
950 }
951}