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