aprender_test_showcase/wasm/
calculator.rs1use crate::core::evaluator::Evaluator;
9use crate::core::history::{History, HistoryEntry};
10use crate::core::{AnomalyValidator, CalcError, CalcResult};
11
12#[derive(Debug)]
14pub struct WasmCalculator {
15 evaluator: Evaluator,
17 history: History,
19 input: String,
21 last_result: Option<Result<f64, CalcError>>,
23 jidoka_status: Vec<String>,
25}
26
27impl Default for WasmCalculator {
28 fn default() -> Self {
29 Self::new()
30 }
31}
32
33impl WasmCalculator {
34 #[must_use]
36 pub fn new() -> Self {
37 Self {
38 evaluator: Evaluator::new(),
39 history: History::new(),
40 input: String::new(),
41 last_result: None,
42 jidoka_status: vec!["Ready".to_string()],
43 }
44 }
45
46 #[must_use]
48 pub fn with_validator(validator: AnomalyValidator) -> Self {
49 Self {
50 evaluator: Evaluator::with_validator(validator),
51 history: History::new(),
52 input: String::new(),
53 last_result: None,
54 jidoka_status: vec!["Ready (with validator)".to_string()],
55 }
56 }
57
58 pub fn set_input(&mut self, expr: &str) {
60 self.input = expr.to_string();
61 }
62
63 #[must_use]
65 pub fn input(&self) -> &str {
66 &self.input
67 }
68
69 pub fn append_input(&mut self, s: &str) {
71 self.input.push_str(s);
72 }
73
74 pub fn backspace(&mut self) {
76 self.input.pop();
77 }
78
79 pub fn clear(&mut self) {
81 self.input.clear();
82 self.last_result = None;
83 self.jidoka_status = vec!["Cleared".to_string()];
84 }
85
86 pub fn clear_input(&mut self) {
88 self.input.clear();
89 }
90
91 pub fn evaluate(&mut self) -> CalcResult<f64> {
93 if self.input.is_empty() {
94 let err = CalcError::EmptyExpression;
95 self.last_result = Some(Err(err.clone()));
96 self.jidoka_status = vec!["Error: Empty expression".to_string()];
97 return Err(err);
98 }
99
100 let result = self.evaluator.evaluate_str(&self.input);
101
102 match &result {
103 Ok(value) => {
104 self.history.record(&self.input, *value);
105 self.jidoka_status = vec![
106 format!("✓ Result: {}", value),
107 "✓ Calculation successful".to_string(),
108 "✓ All constraints satisfied".to_string(),
109 ];
110 }
111 Err(e) => {
112 self.jidoka_status = vec![format!("✗ Error: {}", e)];
113 }
114 }
115
116 self.last_result = Some(result.clone());
117 result
118 }
119
120 #[must_use]
122 pub fn result(&self) -> Option<&Result<f64, CalcError>> {
123 self.last_result.as_ref()
124 }
125
126 #[must_use]
128 pub fn result_display(&self) -> String {
129 match &self.last_result {
130 Some(Ok(value)) => format_number(*value),
131 Some(Err(e)) => format!("Error: {}", e),
132 None => String::new(),
133 }
134 }
135
136 #[must_use]
138 pub fn history(&self) -> &History {
139 &self.history
140 }
141
142 #[must_use]
144 pub fn history_entries(&self) -> Vec<String> {
145 self.history
146 .iter()
147 .map(|entry| format!("{} = {}", entry.expression, format_number(entry.result)))
148 .collect()
149 }
150
151 #[must_use]
153 pub fn history_entries_rev(&self) -> Vec<String> {
154 self.history
155 .iter_rev()
156 .map(|entry| format!("{} = {}", entry.expression, format_number(entry.result)))
157 .collect()
158 }
159
160 #[must_use]
162 pub fn jidoka_status(&self) -> &[String] {
163 &self.jidoka_status
164 }
165
166 pub fn clear_history(&mut self) {
168 self.history.clear();
169 self.evaluator.clear_history();
170 }
171
172 #[must_use]
174 pub fn history_len(&self) -> usize {
175 self.history.len()
176 }
177
178 pub fn recall_history(&self, index: usize) -> Option<&HistoryEntry> {
180 self.history.get(index)
181 }
182
183 pub fn use_last_result(&mut self) {
185 if let Some(Ok(value)) = &self.last_result {
186 self.input = format_number(*value);
187 }
188 }
189}
190
191fn format_number(n: f64) -> String {
193 if n.fract() == 0.0 {
194 format!("{}", n as i64)
195 } else {
196 let s = format!("{:.10}", n);
198 let s = s.trim_end_matches('0');
199 let s = s.trim_end_matches('.');
200 s.to_string()
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[test]
211 fn test_wasm_calculator_new() {
212 let calc = WasmCalculator::new();
213 assert!(calc.input().is_empty());
214 assert!(calc.result().is_none());
215 }
216
217 #[test]
218 fn test_wasm_calculator_default() {
219 let calc = WasmCalculator::default();
220 assert!(calc.input().is_empty());
221 }
222
223 #[test]
224 fn test_wasm_calculator_with_validator() {
225 let validator = AnomalyValidator::with_max_magnitude(100.0);
226 let mut calc = WasmCalculator::with_validator(validator);
227 calc.set_input("100 * 2");
229 let _ = calc.evaluate();
230 assert!(matches!(
231 calc.result(),
232 Some(Err(CalcError::AnomalyViolation(_)))
233 ));
234 }
235
236 #[test]
237 fn test_wasm_calculator_debug() {
238 let calc = WasmCalculator::new();
239 let debug = format!("{:?}", calc);
240 assert!(debug.contains("WasmCalculator"));
241 }
242
243 #[test]
246 fn test_set_input() {
247 let mut calc = WasmCalculator::new();
248 calc.set_input("2 + 2");
249 assert_eq!(calc.input(), "2 + 2");
250 }
251
252 #[test]
253 fn test_append_input() {
254 let mut calc = WasmCalculator::new();
255 calc.append_input("2");
256 calc.append_input("+");
257 calc.append_input("3");
258 assert_eq!(calc.input(), "2+3");
259 }
260
261 #[test]
262 fn test_backspace() {
263 let mut calc = WasmCalculator::new();
264 calc.set_input("123");
265 calc.backspace();
266 assert_eq!(calc.input(), "12");
267 }
268
269 #[test]
270 fn test_backspace_empty() {
271 let mut calc = WasmCalculator::new();
272 calc.backspace(); assert!(calc.input().is_empty());
274 }
275
276 #[test]
277 fn test_clear() {
278 let mut calc = WasmCalculator::new();
279 calc.set_input("5 * 5");
280 calc.evaluate().unwrap();
281 calc.clear();
282 assert!(calc.input().is_empty());
283 assert!(calc.result().is_none());
284 }
285
286 #[test]
287 fn test_clear_input() {
288 let mut calc = WasmCalculator::new();
289 calc.set_input("5 * 5");
290 calc.evaluate().unwrap();
291 calc.clear_input();
292 assert!(calc.input().is_empty());
293 assert!(calc.result().is_some()); }
295
296 #[test]
299 fn test_evaluate_simple() {
300 let mut calc = WasmCalculator::new();
301 calc.set_input("2 + 3");
302 let result = calc.evaluate();
303 assert_eq!(result, Ok(5.0));
304 }
305
306 #[test]
307 fn test_evaluate_complex() {
308 let mut calc = WasmCalculator::new();
309 calc.set_input("42 * (3 + 7)");
310 let result = calc.evaluate();
311 assert_eq!(result, Ok(420.0));
312 }
313
314 #[test]
315 fn test_evaluate_empty() {
316 let mut calc = WasmCalculator::new();
317 let result = calc.evaluate();
318 assert!(matches!(result, Err(CalcError::EmptyExpression)));
319 }
320
321 #[test]
322 fn test_evaluate_division_by_zero() {
323 let mut calc = WasmCalculator::new();
324 calc.set_input("1 / 0");
325 let result = calc.evaluate();
326 assert!(matches!(result, Err(CalcError::DivisionByZero)));
327 }
328
329 #[test]
330 fn test_evaluate_parse_error() {
331 let mut calc = WasmCalculator::new();
332 calc.set_input("2 + +");
333 let result = calc.evaluate();
334 assert!(matches!(result, Err(CalcError::ParseError(_))));
335 }
336
337 #[test]
340 fn test_result_display_integer() {
341 let mut calc = WasmCalculator::new();
342 calc.set_input("5 * 5");
343 calc.evaluate().unwrap();
344 assert_eq!(calc.result_display(), "25");
345 }
346
347 #[test]
348 fn test_result_display_decimal() {
349 let mut calc = WasmCalculator::new();
350 calc.set_input("7 / 2");
351 calc.evaluate().unwrap();
352 assert_eq!(calc.result_display(), "3.5");
353 }
354
355 #[test]
356 fn test_result_display_error() {
357 let mut calc = WasmCalculator::new();
358 calc.set_input("1 / 0");
359 calc.evaluate().ok();
360 assert!(calc.result_display().contains("Error"));
361 }
362
363 #[test]
364 fn test_result_display_none() {
365 let calc = WasmCalculator::new();
366 assert!(calc.result_display().is_empty());
367 }
368
369 #[test]
372 fn test_history_recording() {
373 let mut calc = WasmCalculator::new();
374 calc.set_input("1 + 1");
375 calc.evaluate().unwrap();
376 calc.set_input("2 + 2");
377 calc.evaluate().unwrap();
378 assert_eq!(calc.history_len(), 2);
379 }
380
381 #[test]
382 fn test_history_entries() {
383 let mut calc = WasmCalculator::new();
384 calc.set_input("3 + 3");
385 calc.evaluate().unwrap();
386 let entries = calc.history_entries();
387 assert_eq!(entries.len(), 1);
388 assert_eq!(entries[0], "3 + 3 = 6");
389 }
390
391 #[test]
392 fn test_history_entries_rev() {
393 let mut calc = WasmCalculator::new();
394 calc.set_input("1 + 1");
395 calc.evaluate().unwrap();
396 calc.set_input("2 + 2");
397 calc.evaluate().unwrap();
398 let entries = calc.history_entries_rev();
399 assert_eq!(entries[0], "2 + 2 = 4"); assert_eq!(entries[1], "1 + 1 = 2");
401 }
402
403 #[test]
404 fn test_clear_history() {
405 let mut calc = WasmCalculator::new();
406 calc.set_input("5 + 5");
407 calc.evaluate().unwrap();
408 calc.clear_history();
409 assert_eq!(calc.history_len(), 0);
410 }
411
412 #[test]
413 fn test_recall_history() {
414 let mut calc = WasmCalculator::new();
415 calc.set_input("10 + 10");
416 calc.evaluate().unwrap();
417 let entry = calc.recall_history(0);
418 assert!(entry.is_some());
419 assert_eq!(entry.unwrap().expression, "10 + 10");
420 }
421
422 #[test]
423 fn test_recall_history_out_of_bounds() {
424 let calc = WasmCalculator::new();
425 assert!(calc.recall_history(0).is_none());
426 }
427
428 #[test]
431 fn test_jidoka_status_initial() {
432 let calc = WasmCalculator::new();
433 assert!(!calc.jidoka_status().is_empty());
434 assert!(calc.jidoka_status()[0].contains("Ready"));
435 }
436
437 #[test]
438 fn test_jidoka_status_after_success() {
439 let mut calc = WasmCalculator::new();
440 calc.set_input("2 + 2");
441 calc.evaluate().unwrap();
442 let status = calc.jidoka_status();
443 assert!(status.iter().any(|s| s.contains('✓')));
444 }
445
446 #[test]
447 fn test_jidoka_status_after_error() {
448 let mut calc = WasmCalculator::new();
449 calc.set_input("1 / 0");
450 calc.evaluate().ok();
451 let status = calc.jidoka_status();
452 assert!(status
453 .iter()
454 .any(|s| s.contains('✗') || s.contains("Error")));
455 }
456
457 #[test]
458 fn test_jidoka_status_after_clear() {
459 let mut calc = WasmCalculator::new();
460 calc.set_input("5 + 5");
461 calc.evaluate().unwrap();
462 calc.clear();
463 assert!(calc.jidoka_status()[0].contains("Cleared"));
464 }
465
466 #[test]
469 fn test_use_last_result() {
470 let mut calc = WasmCalculator::new();
471 calc.set_input("5 + 5");
472 calc.evaluate().unwrap();
473 calc.use_last_result();
474 assert_eq!(calc.input(), "10");
475 }
476
477 #[test]
478 fn test_use_last_result_no_result() {
479 let mut calc = WasmCalculator::new();
480 calc.use_last_result(); assert!(calc.input().is_empty());
482 }
483
484 #[test]
485 fn test_chained_calculations() {
486 let mut calc = WasmCalculator::new();
487 calc.set_input("10 + 10");
488 calc.evaluate().unwrap();
489 calc.use_last_result();
490 calc.append_input(" * 2");
491 calc.evaluate().unwrap();
492 assert_eq!(calc.result_display(), "40");
493 }
494
495 #[test]
498 fn test_format_number_integer() {
499 assert_eq!(format_number(42.0), "42");
500 }
501
502 #[test]
503 fn test_format_number_decimal() {
504 assert_eq!(format_number(3.5), "3.5");
505 }
506
507 #[test]
508 fn test_format_number_negative() {
509 assert_eq!(format_number(-5.0), "-5");
510 }
511
512 #[test]
513 fn test_format_number_small_decimal() {
514 assert_eq!(format_number(0.125), "0.125");
515 }
516
517 #[test]
518 fn test_format_number_trailing_zeros() {
519 assert_eq!(format_number(2.500), "2.5");
520 }
521
522 #[test]
525 fn test_all_operations() {
526 let mut calc = WasmCalculator::new();
527
528 calc.set_input("10 + 5");
529 assert_eq!(calc.evaluate(), Ok(15.0));
530
531 calc.set_input("10 - 3");
532 assert_eq!(calc.evaluate(), Ok(7.0));
533
534 calc.set_input("6 * 7");
535 assert_eq!(calc.evaluate(), Ok(42.0));
536
537 calc.set_input("20 / 4");
538 assert_eq!(calc.evaluate(), Ok(5.0));
539
540 calc.set_input("17 % 5");
541 assert_eq!(calc.evaluate(), Ok(2.0));
542
543 calc.set_input("2 ^ 10");
544 assert_eq!(calc.evaluate(), Ok(1024.0));
545 }
546
547 #[test]
550 fn test_full_workflow() {
551 let mut calc = WasmCalculator::new();
552
553 assert!(calc.input().is_empty());
555 assert!(calc.result().is_none());
556
557 calc.set_input("100 / 4");
559 assert_eq!(calc.input(), "100 / 4");
560
561 let result = calc.evaluate();
563 assert_eq!(result, Ok(25.0));
564 assert_eq!(calc.result_display(), "25");
565
566 assert_eq!(calc.history_len(), 1);
568 let entries = calc.history_entries();
569 assert_eq!(entries[0], "100 / 4 = 25");
570
571 calc.use_last_result();
573 calc.append_input(" + 75");
574 calc.evaluate().unwrap();
575 assert_eq!(calc.result_display(), "100");
576
577 calc.clear();
579 assert!(calc.input().is_empty());
580 assert!(calc.result().is_none());
581 }
582}