1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
import { GLib, GObject, property, readFile, register, writeFileAsync } from "astal";
import { derivative, evaluate, rationalize, simplify } from "mathjs/number";
import { math as config } from "../../config";
export interface HistoryItem {
equation: string;
result: string;
icon: string;
}
@register({ GTypeName: "Math" })
export default class Math extends GObject.Object {
static instance: Math;
static get_default() {
if (!this.instance) this.instance = new Math();
return this.instance;
}
readonly #maxHistory = config.maxHistory;
readonly #path = `${CACHE}/math-history.json`;
readonly #history: HistoryItem[] = [];
#variables: Record<string, string> = {};
#lastExpression: HistoryItem | null = null;
@property(Object)
get history() {
return this.#history;
}
#save() {
writeFileAsync(this.#path, JSON.stringify(this.#history)).catch(console.error);
}
/**
* Commits the last evaluated expression to the history
*/
commit() {
if (!this.#lastExpression) return;
// Try select first to prevent duplicates, if it fails, add it
if (!this.select(this.#lastExpression)) {
this.#history.unshift(this.#lastExpression);
if (this.#history.length > this.#maxHistory) this.#history.pop();
this.notify("history");
this.#save();
}
this.#lastExpression = null;
}
/**
* Moves an item in the history to the top
* @param item The item to select
* @returns If the item was successfully selected
*/
select(item: HistoryItem) {
const idx = this.#history.findIndex(i => i.equation === item.equation && i.result === item.result);
if (idx >= 0) {
this.#history.splice(idx, 1);
this.#history.unshift(item);
this.notify("history");
this.#save();
return true;
}
return false;
}
/**
* Clears the history and variables
*/
clear() {
if (this.#history.length > 0) {
this.#history.length = 0;
this.notify("history");
this.#save();
}
this.#lastExpression = null;
this.#variables = {};
}
/**
* Evaluates an equation and adds it to the history
* @param equation The equation to evaluate
* @returns A {@link HistoryItem} representing the result of the equation
*/
evaluate(equation: string): HistoryItem {
if (equation.startsWith("clear"))
return {
equation: "Clear history",
result: "Delete history and previously set variables",
icon: "delete_forever",
};
let result: string, icon: string;
try {
if (equation.startsWith("help")) {
equation = "Help";
result =
"This is a calculator powered by Math.js.\nAvailable functions:\n\thelp: show help\n\tclear: clear history\n\t<x> = <equation>: sets <x> to <equation>\n\tsimplify <equation>: simplifies <equation>\n\tderive <x> <equation>: derives <equation> with respect to <x>\n\tdd<x> <equation>: short form of derive\n\trationalize <equation>: rationalizes <equation>\n\t<equation>: evaluates <equation>\nSee the documentation for syntax and inbuilt functions.";
icon = "help";
} else if (equation.includes("=")) {
const [left, right] = equation.split("=");
try {
this.#variables[left.trim()] = simplify(right).toString();
} catch {
this.#variables[left.trim()] = right.trim();
}
result = this.#variables[left.trim()];
icon = "equal";
} else if (equation.startsWith("simplify")) {
result = simplify(equation.slice(8), this.#variables).toString();
icon = "function";
} else if (equation.startsWith("derive") || equation.startsWith("dd")) {
const isShortForm = equation.startsWith("dd");
const respectTo = isShortForm ? equation.split(" ")[0].slice(2) : equation.split(" ")[1];
if (!respectTo) throw new Error(`Format: ${isShortForm ? "dd" : "derive "}<respect-to> <equation>`);
result = derivative(equation.slice((isShortForm ? 2 : 7) + respectTo.length), respectTo).toString();
icon = "function";
} else if (equation.startsWith("rationalize")) {
result = rationalize(equation.slice(11), this.#variables).toString();
icon = "function";
} else {
result = evaluate(equation, this.#variables).toString();
icon = "calculate";
}
} catch (e) {
equation = "Invalid equation: " + equation;
result = String(e);
icon = "error";
}
return (this.#lastExpression = { equation, result, icon });
}
constructor() {
super();
// Load history
if (GLib.file_test(this.#path, GLib.FileTest.EXISTS)) {
try {
this.#history = JSON.parse(readFile(this.#path));
// Init eval to create variables and last expression
for (const item of this.#history) this.evaluate(item.equation);
} catch (e) {
console.error("Math - Unable to load history", e);
}
}
}
}
|