Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
CalculatorViewModel.cs
1using CommunityToolkit.Mvvm.ComponentModel;
2using CommunityToolkit.Mvvm.Input;
5using System.Collections.ObjectModel;
6using System.ComponentModel;
7using System.Globalization;
8using System.Text;
9using Waher.Script;
11
13{
17 public partial class CalculatorViewModel : XmppViewModel
18 {
24 : base()
25 {
26 this.Stack = [];
27 this.MemoryItems = [];
28
29 if (Args is not null)
30 {
31 this.Entry = Args.Entry;
32 this.ViewModel = Args.ViewModel;
33 this.Property = Args.Property;
34
35 if (this.Entry is not null)
36 this.Value = this.Entry.Text;
37 else if (this.ViewModel is not null && this.Property is not null)
38 this.Value = this.ViewModel.GetValue(this.Property)?.ToString() ?? string.Empty;
39 else
40 this.Value = string.Empty;
41 }
42
43 this.DecimalSeparator = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator;
44 this.DisplayMain = true;
45 this.DisplayFunctions = false;
46 this.DisplayHyperbolic = false;
47 this.DisplayInverse = false;
48 this.DisplayEndParenthesis = false;
49 this.DisplayEquals = true;
50 this.Status = string.Empty;
51 this.Memory = null;
52 this.Entering = false;
53 this.NrParentheses = 0;
54 this.HasValue = !string.IsNullOrEmpty(this.Value);
55 this.HasStatistics = false;
56 }
57
59 protected override async Task OnDispose()
60 {
61 await this.EvaluateStack(true);
62
63 await base.OnDispose();
64 }
65
66 #region Properties
67
69 protected override void OnPropertyChanged(PropertyChangedEventArgs e)
70 {
71 base.OnPropertyChanged(e);
72
73 switch (e.PropertyName)
74 {
75 case nameof(this.Value):
76 this.HasValue = !string.IsNullOrEmpty(this.Value);
77
78 if (this.Entry is not null)
79 this.Entry.Text = this.Value;
80
81 if (this.ViewModel is not null && this.Property is not null)
82 this.ViewModel.SetValue(this.Property, this.Value);
83 break;
84
85 case nameof(this.NrParentheses):
86 case nameof(this.DisplayFunctions):
87 case nameof(this.DisplayHyperbolic):
88 case nameof(this.DisplayInverse):
89 this.CalcDisplay();
90 break;
91 }
92 }
93
97 [ObservableProperty]
98 private string? value;
99
103 [ObservableProperty]
104 private string? status;
105
109 [ObservableProperty]
110 private bool entering;
111
115 [ObservableProperty]
116 private bool hasValue;
117
121 [ObservableProperty]
122 private bool hasStatistics;
123
127 [ObservableProperty]
128 private int nrParentheses;
129
133 [ObservableProperty]
134 private object? memory;
135
139 [ObservableProperty]
140 private Entry? entry;
141
145 [ObservableProperty]
146 private BaseViewModel? viewModel;
147
151 [ObservableProperty]
152 private string? property;
153
157 [ObservableProperty]
158 private string? decimalSeparator;
159
163 [ObservableProperty]
164 private bool displayMain;
165
169 [ObservableProperty]
170 private bool displayFunctions;
171
175 [ObservableProperty]
176 private bool displayHyperbolic;
177
181 [ObservableProperty]
182 private bool displayInverse;
183
187 [ObservableProperty]
188 private bool displayNotHyperbolicNotInverse;
189
193 [ObservableProperty]
194 private bool displayHyperbolicNotInverse;
195
199 [ObservableProperty]
200 private bool displayNotHyperbolicInverse;
201
205 [ObservableProperty]
206 private bool displayHyperbolicInverse;
207
211 [ObservableProperty]
212 private bool displayEquals;
213
217 [ObservableProperty]
218 private bool displayEndParenthesis;
219
220 private void CalcDisplay()
221 {
222 this.DisplayHyperbolicInverse = this.DisplayFunctions && this.DisplayHyperbolic && this.DisplayInverse;
223 this.DisplayNotHyperbolicInverse = this.DisplayFunctions && !this.DisplayHyperbolic && this.DisplayInverse;
224 this.DisplayHyperbolicNotInverse = this.DisplayFunctions && this.DisplayHyperbolic && !this.DisplayInverse;
225 this.DisplayNotHyperbolicNotInverse = this.DisplayFunctions && !this.DisplayHyperbolic && !this.DisplayInverse;
226 this.DisplayEquals = this.DisplayMain && this.NrParentheses == 0;
227 this.DisplayEndParenthesis = this.DisplayMain && this.NrParentheses > 0;
228 }
229
233 public ObservableCollection<StackItem> Stack { get; }
234
238 public ObservableCollection<object> MemoryItems { get; }
239
240 #endregion
241
242 #region Commands
243
247 [RelayCommand]
248 private void Toggle()
249 {
250 this.DisplayMain = !this.DisplayMain;
251 this.DisplayFunctions = !this.DisplayFunctions;
252 }
253
257 [RelayCommand]
258 private void ToggleHyperbolic()
259 {
260 this.DisplayHyperbolic = !this.DisplayHyperbolic;
261 }
262
266 [RelayCommand]
267 private void ToggleInverse()
268 {
269 this.DisplayInverse = !this.DisplayInverse;
270 }
271
275 [RelayCommand]
276 private async Task KeyPress(object P)
277 {
278 try
279 {
280 string Key = P?.ToString() ?? string.Empty;
281
282 switch (Key)
283 {
284 // Key entry
285
286 case "0":
287 if (!this.Entering)
288 break;
289
290 this.Value += Key;
291 break;
292
293 case "1":
294 case "2":
295 case "3":
296 case "4":
297 case "5":
298 case "6":
299 case "7":
300 case "8":
301 case "9":
302 if (!this.Entering)
303 {
304 this.Value = string.Empty;
305 this.Entering = true;
306 }
307
308 this.Value += Key;
309 break;
310
311 case ".":
312 Key = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator;
313 this.Value += Key;
314 this.Entering = true;
315 break;
316
317 // Results
318
319 case "C":
320 this.Value = string.Empty;
321 this.Entering = false;
322 break;
323
324 case "CE":
325 this.Value = string.Empty;
326 this.Memory = null;
327 this.Stack.Clear();
328 this.MemoryItems.Clear();
329 this.Entering = false;
330 this.HasStatistics = false;
331
332 this.OnPropertyChanged(nameof(this.StackString));
333 this.OnPropertyChanged(nameof(this.MemoryString));
334 break;
335
336 case "=":
337 await this.EvaluateStack();
338 break;
339
340 // Unary operators
341
342 case "+-":
343 await this.Evaluate("-x");
344 break;
345
346 case "1/x":
347 await this.Evaluate("1/x");
348 break;
349
350 case "%":
351 await this.Evaluate("x%");
352 break;
353
354 case "%0":
355 await this.Evaluate("x‰");
356 break;
357
358 case "°":
359 await this.Evaluate("x°");
360 break;
361
362 case "x2":
363 await this.Evaluate("x^2");
364 break;
365
366 case "sqrt":
367 await this.Evaluate("sqrt(x)");
368 break;
369
370 case "10^x":
371 await this.Evaluate("10^x");
372 break;
373
374 case "2^x":
375 await this.Evaluate("2^x");
376 break;
377
378 case "rad":
379 await this.Evaluate("x*180/pi");
380 break;
381
382 // Binary operators
383
384 case "+":
385 await this.Evaluate("x+y", "+", OperatorPriority.Terms, false);
386 break;
387
388 case "-":
389 await this.Evaluate("x-y", "−", OperatorPriority.Terms, false);
390 break;
391
392 case "*":
393 await this.Evaluate("x*y", "⨉", OperatorPriority.Factors, false);
394 break;
395
396 case "/":
397 await this.Evaluate("x/y", "÷", OperatorPriority.Factors, false);
398 break;
399
400 case "^":
401 await this.Evaluate("x^y", "^", OperatorPriority.Powers, false);
402 break;
403
404 case "yrt":
405 await this.Evaluate("x^(1/y)", "ʸ√", OperatorPriority.Powers, false);
406 break;
407
408 // Order
409
410 case "(":
411
412 if (this.Entering)
413 {
414 await this.Evaluate("x*y", "⨉", OperatorPriority.Factors, true);
415 break;
416 }
417
418 if (this.Stack.Count > 0)
419 {
420 this.Stack[^1].StartParenthesis = true;
421 this.OnPropertyChanged(nameof(this.StackString));
422 }
423
424 break;
425
426 case ")":
427 await this.Evaluate(string.Empty, "=", OperatorPriority.Parenthesis, false);
428 if (this.Stack.Count > 0)
429 {
430 this.Stack[^1].StartParenthesis = false;
431 this.OnPropertyChanged(nameof(this.StackString));
432 }
433 break;
434
435 // Analytical Funcions
436
437 case "exp":
438 case "lg":
439 case "log2":
440 case "ln":
441 case "sin":
442 case "sinh":
443 case "asin":
444 case "asinh":
445 case "cos":
446 case "cosh":
447 case "acos":
448 case "acosh":
449 case "tan":
450 case "tanh":
451 case "atan":
452 case "atanh":
453 case "sec":
454 case "sech":
455 case "asec":
456 case "asech":
457 case "csc":
458 case "csch":
459 case "acsc":
460 case "acsch":
461 case "cot":
462 case "coth":
463 case "acot":
464 case "acoth":
465 await this.Evaluate(Key + "(x)");
466 break;
467
468 // Other scalar functions
469
470 case "abs":
471 case "sign":
472 case "round":
473 case "ceil":
474 case "floor":
475 await this.Evaluate(Key + "(x)");
476 break;
477
478 case "frac":
479 await this.Evaluate("x-floor(x)");
480 break;
481
482 // Statistics
483
484 case "M+":
485 await this.AddToMemory();
486 break;
487
488 case "M-":
489 await this.SubtractFromMemory();
490 break;
491
492 case "MR":
493 this.Value = Expression.ToString(this.Memory);
494 this.Entering = false;
495 break;
496
497 case "avg":
498 case "stddev":
499 case "sum":
500 case "prod":
501 case "min":
502 case "max":
503 await this.EvaluateStatistics(Key + "(x)");
504 break;
505 }
506 }
507 catch (Exception ex)
508 {
509 this.Status = ex.Message;
510 }
511 }
512
513 private async Task<object> Evaluate()
514 {
515 if (string.IsNullOrEmpty(this.Value))
516 throw new Exception(ServiceRef.Localizer[nameof(AppResources.EnterValue)]);
517
518 try
519 {
520 return await Expression.EvalAsync(this.Value);
521 }
522 catch (Exception)
523 {
524 throw new Exception(ServiceRef.Localizer[nameof(AppResources.EnterValidValue)]);
525 }
526 }
527
528 private async Task Evaluate(string Script)
529 {
530 object x = await this.Evaluate();
531
532 try
533 {
534 Variables v = [];
535
536 v["x"] = x;
537
538 object y = await Expression.EvalAsync(Script, v);
539
540 this.Value = Expression.ToString(y);
541 this.Entering = false;
542 }
543 catch (Exception)
544 {
545 throw new Exception(ServiceRef.Localizer[nameof(AppResources.CalculationError)]);
546 }
547 }
548
549 private async Task Evaluate(string Script, string Operator, OperatorPriority Priority, bool StartParenthesis)
550 {
551 object x = await this.Evaluate();
552 StackItem Item;
553 int c = this.Stack.Count;
554
555 // if (c > 0 && (Item = this.Stack[c - 1]).StartParenthesis)
556 // Priority = OperatorPriority.Parenthesis;
557
558 while (c > 0 && (Item = this.Stack[c - 1]).Priority >= Priority && !Item.StartParenthesis)
559 {
560 object y = x;
561
562 this.Value = Item.Entry ?? string.Empty;
563 x = await this.Evaluate();
564
565 try
566 {
567 Variables v = [];
568
569 v["x"] = x;
570 v["y"] = y;
571
572 x = await Expression.EvalAsync(Item.Script, v);
573
574 this.Value = Expression.ToString(x);
575 this.Entering = false;
576 }
577 catch (Exception)
578 {
579 throw new Exception(ServiceRef.Localizer[nameof(AppResources.CalculationError)]);
580 }
581
582 c--;
583 this.Stack.RemoveAt(c);
584 }
585
586 if (!string.IsNullOrEmpty(Script))
587 {
588 this.Stack.Add(new StackItem()
589 {
590 Entry = this.Value,
591 Script = Script,
592 Operator = Operator,
594 StartParenthesis = StartParenthesis
595 });
596
597 this.Value = string.Empty;
598 }
599
600 this.Entering = false;
601 this.OnPropertyChanged(nameof(this.StackString));
602 }
603
604 private async Task EvaluateStatistics(string Script)
605 {
606 List<Waher.Script.Abstraction.Elements.IElement> Elements = [];
607
608 foreach (object Item in this.MemoryItems)
609 Elements.Add(Expression.Encapsulate(Item));
610
611 try
612 {
613 Variables v = [];
614
615 v["x"] = VectorDefinition.Encapsulate(Elements, false, null);
616
617 object y = await Expression.EvalAsync(Script, v);
618
619 this.Value = Expression.ToString(y);
620 this.Entering = false;
621 }
622 catch (Exception)
623 {
624 throw new Exception(ServiceRef.Localizer[nameof(AppResources.CalculationError)]);
625 }
626 }
627
631 public string StackString
632 {
633 get
634 {
635 StringBuilder sb = new();
636 bool First = true;
637 int NrParantheses = 0;
638 OperatorPriority PrevPriority = OperatorPriority.Equals;
639 bool StartParenthesis = false;
640
641 foreach (StackItem Item in this.Stack)
642 {
643 if (First)
644 First = false;
645 else
646 sb.Append(' ');
647
648 if (Item.Priority < PrevPriority || StartParenthesis)
649 {
650 NrParantheses++;
651 sb.Append(" ( ");
652 }
653
654 PrevPriority = Item.Priority;
655 StartParenthesis = Item.StartParenthesis;
656
657 sb.Append(Item.Entry);
658 sb.Append(' ');
659 sb.Append(Item.Operator);
660 }
661
662 if (StartParenthesis)
663 {
664 sb.Append(" (");
665 NrParantheses++;
666 }
667
668 this.NrParentheses = NrParantheses;
669
670 while (NrParantheses > 0)
671 {
672 sb.Append(" )");
673 NrParantheses--;
674 }
675
676 return sb.ToString();
677 }
678 }
679
683 public Task EvaluateStack()
684 {
685 return this.EvaluateStack(false);
686 }
687
691 public async Task EvaluateStack(bool IgnoreError)
692 {
693 if (this.Stack.Count == 0 && string.IsNullOrEmpty(this.Value))
694 return;
695
696 if (IgnoreError)
697 {
698 try
699 {
700 await this.Evaluate(string.Empty, "=", OperatorPriority.Equals, false);
701 }
702 catch (Exception)
703 {
704 // Ignore
705 }
706 }
707 else
708 await this.Evaluate(string.Empty, "=", OperatorPriority.Equals, false);
709 }
710
714 public string MemoryString
715 {
716 get
717 {
718 if (this.Memory is null)
719 return string.Empty;
720
721 StringBuilder sb = new();
722
723 sb.Append("M: ");
724 sb.Append(Expression.ToString(this.Memory));
725 sb.Append(" (");
726 sb.Append(this.MemoryItems.Count.ToString(CultureInfo.InvariantCulture));
727 sb.Append(')');
728
729 return sb.ToString();
730 }
731 }
732
733 private async Task AddToMemory()
734 {
735 await this.EvaluateStack();
736
737 object x = await this.Evaluate();
738
739 this.MemoryItems.Add(x);
740
741 if (this.Memory is null)
742 this.Memory = x;
743 else
744 {
745 Variables v = [];
746
747 v["M"] = this.Memory;
748 v["x"] = x;
749
750 this.Memory = await Expression.EvalAsync("M+x", v);
751 }
752
753 this.HasStatistics = true;
754 this.OnPropertyChanged(nameof(this.MemoryString));
755 }
756
757 private async Task SubtractFromMemory()
758 {
759 await this.EvaluateStack();
760
761 object x = await this.Evaluate();
762
763 this.MemoryItems.Add(x);
764
765 if (this.Memory is null)
766 {
767 Variables v = [];
768
769 v["x"] = x;
770
771 this.Memory = await Expression.EvalAsync("-x", v);
772 }
773 else
774 {
775 Variables v = [];
776
777 v["M"] = this.Memory;
778 v["x"] = x;
779
780 this.Memory = await Expression.EvalAsync("M+x", v);
781 }
782
783 this.HasStatistics = true;
784 this.OnPropertyChanged(nameof(this.MemoryString));
785 }
786
787 #endregion
788 }
789}
Base class that references services in the app.
Definition: ServiceRef.cs:31
static IStringLocalizer Localizer
Localization service
Definition: ServiceRef.cs:235
A base class for all view models, inheriting from the BindableObject. NOTE: using this class requir...
string? Property
Property containing the value to calculate.
BaseViewModel? ViewModel
View model containing a bindable property with the value to calculate.
The view model to bind to for when displaying the calculator.
ObservableCollection< StackItem > Stack
Holds the contents of the calculation stack
ObservableCollection< object > MemoryItems
Holds the contents of the memory
async Task EvaluateStack(bool IgnoreError)
Evaluates the current stack.
string MemoryString
String representation of contents on the statistical memory.
string StackString
String representation of contents on the stack.
override void OnPropertyChanged(PropertyChangedEventArgs e)
CalculatorViewModel(CalculatorNavigationArgs? Args)
Creates an instance of the CalculatorViewModel class.
override async Task OnDispose()
Method called when the view is disposed, and will not be used more. Use this method to unregister eve...
OperatorPriority Priority
Priority level
Definition: StackItem.cs:52
bool StartParenthesis
If parenthesis was started
Definition: StackItem.cs:62
A view model that holds the XMPP state.
Class managing a script expression.
Definition: Expression.cs:39
static Task< object > EvalAsync(string Script)
Evaluates script, in string format.
Definition: Expression.cs:5488
static IElement Encapsulate(object Value)
Encapsulates an object.
Definition: Expression.cs:4955
static string ToString(double Value)
Converts a value to a string, that can be parsed as part of an expression.
Definition: Expression.cs:4496
static IElement Encapsulate(Array Elements, bool CanEncapsulateAsMatrix, ScriptNode Node)
Encapsulates the elements of a vector.
Collection of variables.
Definition: Variables.cs:25
Basic interface for all types of elements.
Definition: IElement.cs:20
OperatorPriority
Binary operator priority
Definition: StackItem.cs:7
Priority
Mail priority
Definition: Priority.cs:7
Definition: App.xaml.cs:4