@signpostmarv/intermediary-number 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -8
- package/package.json +9 -11
- package/index.d.ts +0 -4
- package/index.js +0 -4
- package/lib/Docs.json.d.ts +0 -7
- package/lib/Docs.json.js +0 -6
- package/lib/IntermediaryNumber.d.ts +0 -221
- package/lib/IntermediaryNumber.js +0 -1116
- package/lib/IntermediaryNumberTypes.d.ts +0 -6
- package/lib/IntermediaryNumberTypes.js +0 -1
- package/lib/NumberStrings.d.ts +0 -14
- package/lib/NumberStrings.js +0 -53
- package/lib/Numbers.d.ts +0 -15
- package/lib/Numbers.js +0 -49
|
@@ -1,1116 +0,0 @@
|
|
|
1
|
-
import assert from 'assert';
|
|
2
|
-
import BigNumber from 'bignumber.js';
|
|
3
|
-
import Fraction from 'fraction.js';
|
|
4
|
-
import { is_string, } from './Docs.json';
|
|
5
|
-
import { NumberStrings, } from './NumberStrings';
|
|
6
|
-
export const regex_recurring_number = /^-?(\d+\.)(\d+r|\d*\[\d+\]r?|\d*\(\d+\)r?)$/;
|
|
7
|
-
//#endregion
|
|
8
|
-
//#region utility functions
|
|
9
|
-
function do_math(left_operand, operator, right_operand) {
|
|
10
|
-
return IntermediaryCalculation.maybe_reduce_operands(left_operand, operator, IntermediaryNumber.reuse_or_create(right_operand));
|
|
11
|
-
}
|
|
12
|
-
function abs(value) {
|
|
13
|
-
if (value.isZero()) {
|
|
14
|
-
return value;
|
|
15
|
-
}
|
|
16
|
-
return (value.isLessThan(0)
|
|
17
|
-
? IntermediaryNumber.Zero.minus(value)
|
|
18
|
-
: value);
|
|
19
|
-
}
|
|
20
|
-
function compare(value, to) {
|
|
21
|
-
const comparable = IntermediaryNumber.reuse_or_create(value).toBigNumberOrFraction();
|
|
22
|
-
let result;
|
|
23
|
-
if (comparable instanceof BigNumber) {
|
|
24
|
-
result = to.toBigNumber().comparedTo(comparable);
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
result = to.toFraction().compare(comparable);
|
|
28
|
-
}
|
|
29
|
-
assert.strictEqual((-1 === result
|
|
30
|
-
|| 0 === result
|
|
31
|
-
|| 1 === result), true, `Expecting -1, 0, or 1, receieved ${JSON.stringify(result)}`);
|
|
32
|
-
return result;
|
|
33
|
-
}
|
|
34
|
-
const conversion_cache = new class {
|
|
35
|
-
toAmountString_cache;
|
|
36
|
-
toBigNumber_cache;
|
|
37
|
-
toFraction_cache;
|
|
38
|
-
toString_cache;
|
|
39
|
-
get AmountString() {
|
|
40
|
-
if (!this.toAmountString_cache) {
|
|
41
|
-
this.toAmountString_cache = new WeakMap();
|
|
42
|
-
}
|
|
43
|
-
return this.toAmountString_cache;
|
|
44
|
-
}
|
|
45
|
-
get BigNumber() {
|
|
46
|
-
if (!this.toBigNumber_cache) {
|
|
47
|
-
this.toBigNumber_cache = new WeakMap();
|
|
48
|
-
}
|
|
49
|
-
return this.toBigNumber_cache;
|
|
50
|
-
}
|
|
51
|
-
get Fraction() {
|
|
52
|
-
if (!this.toFraction_cache) {
|
|
53
|
-
this.toFraction_cache = new WeakMap();
|
|
54
|
-
}
|
|
55
|
-
return this.toFraction_cache;
|
|
56
|
-
}
|
|
57
|
-
get String() {
|
|
58
|
-
if (!this.toString_cache) {
|
|
59
|
-
this.toString_cache = new WeakMap();
|
|
60
|
-
}
|
|
61
|
-
return this.toString_cache;
|
|
62
|
-
}
|
|
63
|
-
dispose(of) {
|
|
64
|
-
for (const cache of [
|
|
65
|
-
this.toAmountString_cache,
|
|
66
|
-
this.toBigNumber_cache,
|
|
67
|
-
this.toFraction_cache,
|
|
68
|
-
this.toString_cache,
|
|
69
|
-
]) {
|
|
70
|
-
if (cache) {
|
|
71
|
-
cache.delete(of);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
export function dispose(value) {
|
|
77
|
-
conversion_cache.dispose(value);
|
|
78
|
-
}
|
|
79
|
-
function max(first, second, ...remaining) {
|
|
80
|
-
let max = IntermediaryNumber.reuse_or_create(first);
|
|
81
|
-
for (const entry of [second, ...remaining]) {
|
|
82
|
-
const maybe = IntermediaryNumber.reuse_or_create(entry);
|
|
83
|
-
if (-1 === max.compare(maybe)) {
|
|
84
|
-
max = maybe;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return IntermediaryNumber.reuse_or_create(max);
|
|
88
|
-
}
|
|
89
|
-
function min(first, second, ...remaining) {
|
|
90
|
-
let min = IntermediaryNumber.reuse_or_create(first);
|
|
91
|
-
for (const entry of [second, ...remaining]) {
|
|
92
|
-
const maybe = IntermediaryNumber.reuse_or_create(entry);
|
|
93
|
-
if (-1 === maybe.compare(min)) {
|
|
94
|
-
min = maybe;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return IntermediaryNumber.reuse_or_create(min);
|
|
98
|
-
}
|
|
99
|
-
//#region TokenScan utility functions
|
|
100
|
-
function default_tokenizer_state() {
|
|
101
|
-
return {
|
|
102
|
-
outter_stack: [],
|
|
103
|
-
left_operand: undefined,
|
|
104
|
-
operation: '',
|
|
105
|
-
right_operand: undefined,
|
|
106
|
-
operand_mode: 'left',
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
function is_nesting_open(maybe) {
|
|
110
|
-
return 'nesting_open' === maybe.type;
|
|
111
|
-
}
|
|
112
|
-
function is_nesting_close(maybe) {
|
|
113
|
-
return 'nesting_close' === maybe.type;
|
|
114
|
-
}
|
|
115
|
-
function is_numeric(maybe) {
|
|
116
|
-
return 'numeric' === maybe.type;
|
|
117
|
-
}
|
|
118
|
-
function is_infinity(maybe) {
|
|
119
|
-
return 'Infinity' === maybe.type;
|
|
120
|
-
}
|
|
121
|
-
export function is_operation_value(maybe) {
|
|
122
|
-
if (!(maybe.length === 1
|
|
123
|
-
&& '+-/x*%'.includes(maybe))) {
|
|
124
|
-
throw new TokenScanError(`Expected operation value, found "${maybe}"`);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
//#endregion
|
|
128
|
-
//#endregion
|
|
129
|
-
//#region IntermediaryNumber
|
|
130
|
-
export class IntermediaryNumber {
|
|
131
|
-
value;
|
|
132
|
-
static One = new this('1');
|
|
133
|
-
static Zero = new this('0');
|
|
134
|
-
constructor(value) {
|
|
135
|
-
this.value = value;
|
|
136
|
-
}
|
|
137
|
-
get resolve_type() {
|
|
138
|
-
return this.type;
|
|
139
|
-
}
|
|
140
|
-
get type() {
|
|
141
|
-
if (this.value instanceof BigNumber) {
|
|
142
|
-
return 'BigNumber';
|
|
143
|
-
}
|
|
144
|
-
else if (this.value instanceof Fraction) {
|
|
145
|
-
return 'Fraction';
|
|
146
|
-
}
|
|
147
|
-
else if (NumberStrings.is_amount_string(this.value)) {
|
|
148
|
-
return 'amount_string';
|
|
149
|
-
}
|
|
150
|
-
return 'numeric_string';
|
|
151
|
-
}
|
|
152
|
-
abs() {
|
|
153
|
-
return abs(this);
|
|
154
|
-
}
|
|
155
|
-
compare(value) {
|
|
156
|
-
return compare(value, this);
|
|
157
|
-
}
|
|
158
|
-
divide(value) {
|
|
159
|
-
return do_math(this, '/', value);
|
|
160
|
-
}
|
|
161
|
-
do_math_then_dispose(operator, right_operand) {
|
|
162
|
-
const result = this[operator](right_operand);
|
|
163
|
-
if (result !== this) {
|
|
164
|
-
dispose(this);
|
|
165
|
-
}
|
|
166
|
-
return result;
|
|
167
|
-
}
|
|
168
|
-
isGreaterThan(value) {
|
|
169
|
-
return 1 === this.compare(value);
|
|
170
|
-
}
|
|
171
|
-
isLessThan(value) {
|
|
172
|
-
return -1 === this.compare(value);
|
|
173
|
-
}
|
|
174
|
-
isOne() {
|
|
175
|
-
return 0 === this.compare(1);
|
|
176
|
-
}
|
|
177
|
-
isZero() {
|
|
178
|
-
return 0 === this.compare(0);
|
|
179
|
-
}
|
|
180
|
-
max(first, ...remaining) {
|
|
181
|
-
return max(this, first, ...remaining);
|
|
182
|
-
}
|
|
183
|
-
min(first, ...remaining) {
|
|
184
|
-
return min(this, first, ...remaining);
|
|
185
|
-
}
|
|
186
|
-
minus(value) {
|
|
187
|
-
return do_math(this, '-', value);
|
|
188
|
-
}
|
|
189
|
-
modulo(value) {
|
|
190
|
-
return do_math(this, '%', value);
|
|
191
|
-
}
|
|
192
|
-
plus(value) {
|
|
193
|
-
if (this.isZero()) {
|
|
194
|
-
return IntermediaryNumber.reuse_or_create(value);
|
|
195
|
-
}
|
|
196
|
-
return do_math(this, '+', value);
|
|
197
|
-
}
|
|
198
|
-
times(value) {
|
|
199
|
-
return do_math(this, 'x', value);
|
|
200
|
-
}
|
|
201
|
-
toAmountString() {
|
|
202
|
-
if (NumberStrings.is_amount_string(this.value)) {
|
|
203
|
-
return this.value;
|
|
204
|
-
}
|
|
205
|
-
return NumberStrings.round_off(this.toBigNumberOrFraction());
|
|
206
|
-
}
|
|
207
|
-
toBigNumber() {
|
|
208
|
-
if (this.value instanceof BigNumber) {
|
|
209
|
-
return this.value;
|
|
210
|
-
}
|
|
211
|
-
else if (this.value instanceof Fraction) {
|
|
212
|
-
return BigNumber(this.value.valueOf());
|
|
213
|
-
}
|
|
214
|
-
const cache = conversion_cache.BigNumber;
|
|
215
|
-
if (cache.has(this)) {
|
|
216
|
-
return cache.get(this);
|
|
217
|
-
}
|
|
218
|
-
const value = BigNumber(this.value);
|
|
219
|
-
cache.set(this, value);
|
|
220
|
-
return value;
|
|
221
|
-
}
|
|
222
|
-
toBigNumberOrFraction() {
|
|
223
|
-
return ('Fraction' === this.type)
|
|
224
|
-
? this.toFraction()
|
|
225
|
-
: this.toBigNumber();
|
|
226
|
-
}
|
|
227
|
-
toFraction() {
|
|
228
|
-
if (this.value instanceof Fraction) {
|
|
229
|
-
return this.value;
|
|
230
|
-
}
|
|
231
|
-
const cache = conversion_cache.Fraction;
|
|
232
|
-
if (cache.has(this)) {
|
|
233
|
-
return cache.get(this);
|
|
234
|
-
}
|
|
235
|
-
const value = new Fraction(this.toString());
|
|
236
|
-
cache.set(this, value);
|
|
237
|
-
return value;
|
|
238
|
-
}
|
|
239
|
-
toJSON() {
|
|
240
|
-
if (this.isOne()) {
|
|
241
|
-
return {
|
|
242
|
-
type: 'IntermediaryNumber',
|
|
243
|
-
value: '1',
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
else if (this.isZero()) {
|
|
247
|
-
return {
|
|
248
|
-
type: 'IntermediaryNumber',
|
|
249
|
-
value: '0',
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
if (this.value instanceof Fraction) {
|
|
253
|
-
const [left, right] = this.value.toFraction().split('/');
|
|
254
|
-
if (undefined === right) {
|
|
255
|
-
return {
|
|
256
|
-
type: 'IntermediaryNumber',
|
|
257
|
-
value: left,
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
return {
|
|
261
|
-
type: 'IntermediaryCalculation',
|
|
262
|
-
left: {
|
|
263
|
-
type: 'IntermediaryNumber',
|
|
264
|
-
value: left,
|
|
265
|
-
},
|
|
266
|
-
operation: '/',
|
|
267
|
-
right: {
|
|
268
|
-
type: 'IntermediaryNumber',
|
|
269
|
-
value: right,
|
|
270
|
-
},
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
else if (this.value instanceof BigNumber) {
|
|
274
|
-
return {
|
|
275
|
-
type: 'IntermediaryNumber',
|
|
276
|
-
value: this.value.toFixed(),
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
return {
|
|
280
|
-
type: 'IntermediaryNumber',
|
|
281
|
-
value: this.value,
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
toString() {
|
|
285
|
-
if (this.value instanceof BigNumber) {
|
|
286
|
-
return this.value.toFixed();
|
|
287
|
-
}
|
|
288
|
-
return this.value.toString();
|
|
289
|
-
}
|
|
290
|
-
toStringCalculation() {
|
|
291
|
-
return this.toString();
|
|
292
|
-
}
|
|
293
|
-
static create(input) {
|
|
294
|
-
if ('' === input) {
|
|
295
|
-
return IntermediaryNumber.Zero;
|
|
296
|
-
}
|
|
297
|
-
if (input instanceof Fraction) {
|
|
298
|
-
return new this(input.simplify(1 / (2 ** 52)));
|
|
299
|
-
}
|
|
300
|
-
if (input instanceof BigNumber
|
|
301
|
-
|| NumberStrings.is_numeric_string(input)) {
|
|
302
|
-
return new this(input);
|
|
303
|
-
}
|
|
304
|
-
else if ('number' === typeof input) {
|
|
305
|
-
return new this(BigNumber(input));
|
|
306
|
-
}
|
|
307
|
-
else if (is_string(input) && regex_recurring_number.test(input)) {
|
|
308
|
-
let only_last_digit_recurring = false;
|
|
309
|
-
if (/^\d*\.\d+r$/.test(input)) {
|
|
310
|
-
only_last_digit_recurring = true;
|
|
311
|
-
}
|
|
312
|
-
if (input.endsWith('r')) {
|
|
313
|
-
input = input.substring(0, input.length - 1);
|
|
314
|
-
}
|
|
315
|
-
if (only_last_digit_recurring) {
|
|
316
|
-
input = input.replace(/(\d)$/, '($1)');
|
|
317
|
-
}
|
|
318
|
-
else if (input.includes('[')) {
|
|
319
|
-
input = input.replace(/\[(\d+)\]/, '($1)');
|
|
320
|
-
}
|
|
321
|
-
return new this(new Fraction(input));
|
|
322
|
-
}
|
|
323
|
-
else if ('Infinity' === input) {
|
|
324
|
-
return new IntermediaryNumberInfinity(new BigNumber('Infinity'));
|
|
325
|
-
}
|
|
326
|
-
throw new Error('Unsupported argument specified!');
|
|
327
|
-
}
|
|
328
|
-
static create_if_valid(input) {
|
|
329
|
-
const maybe = input.trim();
|
|
330
|
-
if (NumberStrings.is_amount_string(maybe)
|
|
331
|
-
|| NumberStrings.is_numeric_string(maybe)) {
|
|
332
|
-
return IntermediaryNumber.create(maybe);
|
|
333
|
-
}
|
|
334
|
-
else if (/^(\d+|\d*\.\d+)\s*[+/*x%-]\s*(\d+|\d*\.\d+)$/.test(maybe)) {
|
|
335
|
-
return TokenScan.create(input).parsed;
|
|
336
|
-
}
|
|
337
|
-
const scientific = /^(-?\d+(?:\.\d+))e([+-])(\d+)$/.exec(maybe);
|
|
338
|
-
if (scientific) {
|
|
339
|
-
const calc = new IntermediaryCalculation(IntermediaryNumber.Zero, scientific[2], IntermediaryNumber.create(scientific[3])).toBigNumber();
|
|
340
|
-
return IntermediaryNumber.create(scientific[1]).times((new BigNumber(10)).pow(calc));
|
|
341
|
-
}
|
|
342
|
-
try {
|
|
343
|
-
return IntermediaryCalculation.fromString(maybe);
|
|
344
|
-
}
|
|
345
|
-
catch (err) {
|
|
346
|
-
return new NotValid(maybe, err);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
static fromJson(json) {
|
|
350
|
-
if ('IntermediaryNumber' === json.type) {
|
|
351
|
-
return this.create(json.value);
|
|
352
|
-
}
|
|
353
|
-
else if ('TokenScan' === json.type) {
|
|
354
|
-
return TokenScan.create(json.value);
|
|
355
|
-
}
|
|
356
|
-
return new IntermediaryCalculation(this.fromJson(json.left), json.operation, this.fromJson(json.right));
|
|
357
|
-
}
|
|
358
|
-
static reuse_or_create(input) {
|
|
359
|
-
return (((input instanceof IntermediaryNumber)
|
|
360
|
-
|| (input instanceof IntermediaryCalculation)
|
|
361
|
-
|| (input instanceof TokenScan))
|
|
362
|
-
? input
|
|
363
|
-
: this.create(input));
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
//#endregion
|
|
367
|
-
//#region IntermediaryNumberInfinity
|
|
368
|
-
export class IntermediaryNumberInfinity extends IntermediaryNumber {
|
|
369
|
-
static One = new this(new BigNumber('Infinity'));
|
|
370
|
-
static Zero = new this(new BigNumber('Infinity'));
|
|
371
|
-
isOne() {
|
|
372
|
-
return false;
|
|
373
|
-
}
|
|
374
|
-
isZero() {
|
|
375
|
-
return false;
|
|
376
|
-
}
|
|
377
|
-
toFraction() {
|
|
378
|
-
throw new Error('Cannot convert infinity to Fraction');
|
|
379
|
-
}
|
|
380
|
-
toString() {
|
|
381
|
-
return 'Infinity';
|
|
382
|
-
}
|
|
383
|
-
toStringCalculation() {
|
|
384
|
-
return 'Infinity';
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
//#endregion
|
|
388
|
-
//#region IntermediaryCalculation
|
|
389
|
-
export class NotValid extends Error {
|
|
390
|
-
reason;
|
|
391
|
-
value;
|
|
392
|
-
constructor(not_valid, reason) {
|
|
393
|
-
super('Value given was not valid!');
|
|
394
|
-
this.value = not_valid;
|
|
395
|
-
this.reason = reason;
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
const BigNumber_operation_map = {
|
|
399
|
-
'+': (a, b) => a.plus(b),
|
|
400
|
-
'-': (a, b) => a.minus(b),
|
|
401
|
-
'x': (a, b) => a.times(b),
|
|
402
|
-
'*': (a, b) => a.times(b),
|
|
403
|
-
'%': (a, b) => a.modulo(b),
|
|
404
|
-
};
|
|
405
|
-
const Fraction_operation_map = {
|
|
406
|
-
'+': (a, b) => a.add(b),
|
|
407
|
-
'-': (a, b) => a.sub(b),
|
|
408
|
-
'x': (a, b) => a.mul(b),
|
|
409
|
-
'*': (a, b) => a.mul(b),
|
|
410
|
-
'/': (a, b) => a.div(b),
|
|
411
|
-
'%': (a, b) => a.mod(b),
|
|
412
|
-
};
|
|
413
|
-
export class IntermediaryCalculation {
|
|
414
|
-
left_operand;
|
|
415
|
-
operation;
|
|
416
|
-
right_operand;
|
|
417
|
-
constructor(left, operation, right) {
|
|
418
|
-
this.left_operand = left;
|
|
419
|
-
this.operation = operation;
|
|
420
|
-
this.right_operand = right;
|
|
421
|
-
}
|
|
422
|
-
get has_infinity() {
|
|
423
|
-
return (this.left_operand instanceof IntermediaryNumberInfinity
|
|
424
|
-
|| this.right_operand instanceof IntermediaryNumberInfinity);
|
|
425
|
-
}
|
|
426
|
-
get left_type() {
|
|
427
|
-
if (this.left_operand instanceof IntermediaryCalculation) {
|
|
428
|
-
return 'IntermediaryCalculation';
|
|
429
|
-
}
|
|
430
|
-
return this.left_operand.type;
|
|
431
|
-
}
|
|
432
|
-
get resolve_type() {
|
|
433
|
-
return `${this.left_type} ${this.operation} ${this.right_type}`;
|
|
434
|
-
}
|
|
435
|
-
get right_type() {
|
|
436
|
-
if (this.right_operand instanceof IntermediaryCalculation) {
|
|
437
|
-
return 'IntermediaryCalculation';
|
|
438
|
-
}
|
|
439
|
-
return this.right_operand.type;
|
|
440
|
-
}
|
|
441
|
-
get type() {
|
|
442
|
-
return 'IntermediaryCalculation';
|
|
443
|
-
}
|
|
444
|
-
abs() {
|
|
445
|
-
return abs(this);
|
|
446
|
-
}
|
|
447
|
-
compare(value) {
|
|
448
|
-
return compare(value, this);
|
|
449
|
-
}
|
|
450
|
-
divide(value) {
|
|
451
|
-
return do_math(this, '/', value);
|
|
452
|
-
}
|
|
453
|
-
do_math_then_dispose(operator, right_operand) {
|
|
454
|
-
const result = this[operator](right_operand);
|
|
455
|
-
if (result !== this) {
|
|
456
|
-
dispose(this);
|
|
457
|
-
}
|
|
458
|
-
return result;
|
|
459
|
-
}
|
|
460
|
-
isGreaterThan(value) {
|
|
461
|
-
return 1 === this.compare(value);
|
|
462
|
-
}
|
|
463
|
-
isLessThan(value) {
|
|
464
|
-
return -1 === this.compare(value);
|
|
465
|
-
}
|
|
466
|
-
isOne() {
|
|
467
|
-
return 0 === this.compare(1);
|
|
468
|
-
}
|
|
469
|
-
isZero() {
|
|
470
|
-
return 0 === this.compare(0);
|
|
471
|
-
}
|
|
472
|
-
max(first, ...remaining) {
|
|
473
|
-
return max(this, first, ...remaining);
|
|
474
|
-
}
|
|
475
|
-
min(first, ...remaining) {
|
|
476
|
-
return min(this, first, ...remaining);
|
|
477
|
-
}
|
|
478
|
-
minus(value) {
|
|
479
|
-
return do_math(this, '-', value);
|
|
480
|
-
}
|
|
481
|
-
modulo(value) {
|
|
482
|
-
return do_math(this, '%', value);
|
|
483
|
-
}
|
|
484
|
-
plus(value) {
|
|
485
|
-
if (this.isZero()) {
|
|
486
|
-
return IntermediaryNumber.reuse_or_create(value);
|
|
487
|
-
}
|
|
488
|
-
return do_math(this, '+', value);
|
|
489
|
-
}
|
|
490
|
-
resolve() {
|
|
491
|
-
const reduced = IntermediaryCalculation.maybe_short_circuit(this.left_operand, this.operation, this.right_operand);
|
|
492
|
-
if (reduced instanceof IntermediaryNumber) {
|
|
493
|
-
return reduced;
|
|
494
|
-
}
|
|
495
|
-
const left_operand = this.operand_to_IntermediaryNumber(this.left_operand);
|
|
496
|
-
const right_operand = this.operand_to_IntermediaryNumber(this.right_operand);
|
|
497
|
-
const left = left_operand.toBigNumberOrFraction();
|
|
498
|
-
const right = right_operand.toBigNumberOrFraction();
|
|
499
|
-
if ('/' === this.operation
|
|
500
|
-
|| left instanceof Fraction
|
|
501
|
-
|| right instanceof Fraction) {
|
|
502
|
-
if (left_operand instanceof IntermediaryNumberInfinity) {
|
|
503
|
-
return left_operand;
|
|
504
|
-
}
|
|
505
|
-
else if (right_operand instanceof IntermediaryNumberInfinity) {
|
|
506
|
-
return right_operand;
|
|
507
|
-
}
|
|
508
|
-
return IntermediaryNumber.create(Fraction_operation_map[this.operation](((left instanceof BigNumber)
|
|
509
|
-
? left_operand.toFraction()
|
|
510
|
-
: left), ((right instanceof BigNumber)
|
|
511
|
-
? right_operand.toFraction()
|
|
512
|
-
: right)));
|
|
513
|
-
}
|
|
514
|
-
return IntermediaryNumber.create(BigNumber_operation_map[this.operation](left, right));
|
|
515
|
-
}
|
|
516
|
-
times(value) {
|
|
517
|
-
return do_math(this, 'x', value);
|
|
518
|
-
}
|
|
519
|
-
toAmountString() {
|
|
520
|
-
const cache = conversion_cache.AmountString;
|
|
521
|
-
if (cache.has(this)) {
|
|
522
|
-
return cache.get(this);
|
|
523
|
-
}
|
|
524
|
-
const value = this.resolve().toAmountString();
|
|
525
|
-
cache.set(this, value);
|
|
526
|
-
return value;
|
|
527
|
-
}
|
|
528
|
-
toBigNumber() {
|
|
529
|
-
const cache = conversion_cache.BigNumber;
|
|
530
|
-
if (cache.has(this)) {
|
|
531
|
-
return cache.get(this);
|
|
532
|
-
}
|
|
533
|
-
const value = this.resolve().toBigNumber();
|
|
534
|
-
cache.set(this, value);
|
|
535
|
-
return value;
|
|
536
|
-
}
|
|
537
|
-
toBigNumberOrFraction() {
|
|
538
|
-
return this.resolve().toBigNumberOrFraction();
|
|
539
|
-
}
|
|
540
|
-
toFraction() {
|
|
541
|
-
const cache = conversion_cache.Fraction;
|
|
542
|
-
if (cache.has(this)) {
|
|
543
|
-
return cache.get(this);
|
|
544
|
-
}
|
|
545
|
-
const value = this.resolve().toFraction();
|
|
546
|
-
cache.set(this, value);
|
|
547
|
-
return value;
|
|
548
|
-
}
|
|
549
|
-
toJSON() {
|
|
550
|
-
const left = (this.left_operand instanceof IntermediaryCalculation
|
|
551
|
-
&& this.left_operand.has_infinity)
|
|
552
|
-
? this.left_operand
|
|
553
|
-
: this.operand_to_IntermediaryNumber(this.left_operand);
|
|
554
|
-
const right = (this.right_operand instanceof IntermediaryCalculation
|
|
555
|
-
&& this.right_operand.has_infinity)
|
|
556
|
-
? this.right_operand
|
|
557
|
-
: this.operand_to_IntermediaryNumber(this.right_operand);
|
|
558
|
-
const maybe = IntermediaryCalculation.maybe_short_circuit(left, this.operation, right);
|
|
559
|
-
if (maybe) {
|
|
560
|
-
return maybe.toJSON();
|
|
561
|
-
}
|
|
562
|
-
return {
|
|
563
|
-
type: 'IntermediaryCalculation',
|
|
564
|
-
left: left.toJSON(),
|
|
565
|
-
operation: this.operation,
|
|
566
|
-
right: right.toJSON(),
|
|
567
|
-
};
|
|
568
|
-
}
|
|
569
|
-
toString() {
|
|
570
|
-
const cache = conversion_cache.String;
|
|
571
|
-
if (cache.has(this)) {
|
|
572
|
-
return cache.get(this);
|
|
573
|
-
}
|
|
574
|
-
const value = this.resolve().toString();
|
|
575
|
-
cache.set(this, value);
|
|
576
|
-
return value;
|
|
577
|
-
}
|
|
578
|
-
toStringCalculation() {
|
|
579
|
-
return `${(this.left_operand instanceof IntermediaryCalculation)
|
|
580
|
-
? `(${this.left_operand.toStringCalculation()})`
|
|
581
|
-
: this.left_operand.toString()} ${this.operation} ${(this.right_operand instanceof IntermediaryCalculation)
|
|
582
|
-
? `(${this.right_operand.toStringCalculation()})`
|
|
583
|
-
: this.right_operand.toString()}`;
|
|
584
|
-
}
|
|
585
|
-
operand_to_IntermediaryNumber(operand) {
|
|
586
|
-
if ((operand instanceof IntermediaryCalculation)
|
|
587
|
-
|| (operand instanceof TokenScan)) {
|
|
588
|
-
return operand.resolve();
|
|
589
|
-
}
|
|
590
|
-
else if ('amount_string' === operand.type
|
|
591
|
-
|| 'numeric_string' === operand.type) {
|
|
592
|
-
return IntermediaryNumber.create('/' === this.operation
|
|
593
|
-
? operand.toFraction()
|
|
594
|
-
: operand.toBigNumberOrFraction());
|
|
595
|
-
}
|
|
596
|
-
return operand;
|
|
597
|
-
}
|
|
598
|
-
static fromString(input) {
|
|
599
|
-
return TokenScan.create(input).parsed;
|
|
600
|
-
}
|
|
601
|
-
static is(maybe) {
|
|
602
|
-
return maybe instanceof this;
|
|
603
|
-
}
|
|
604
|
-
static maybe_reduce_operands(left, operation, right) {
|
|
605
|
-
let value = this.maybe_short_circuit(left, operation, right);
|
|
606
|
-
if (undefined === value) {
|
|
607
|
-
value = new IntermediaryCalculation(left, operation, right);
|
|
608
|
-
}
|
|
609
|
-
if (value instanceof IntermediaryCalculation) {
|
|
610
|
-
return value.resolve();
|
|
611
|
-
}
|
|
612
|
-
return value;
|
|
613
|
-
}
|
|
614
|
-
static require_is(maybe) {
|
|
615
|
-
if (!this.is(maybe)) {
|
|
616
|
-
throw new Error('Argument is not an instanceof IntermediaryCalculation');
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
static maybe_short_circuit(left, operation, right) {
|
|
620
|
-
let value = undefined;
|
|
621
|
-
if (left instanceof IntermediaryNumberInfinity
|
|
622
|
-
&& right instanceof IntermediaryNumberInfinity) {
|
|
623
|
-
if ('+' === operation
|
|
624
|
-
|| '*' === operation) {
|
|
625
|
-
// infinity plus or multiplied by infintiy is infinity
|
|
626
|
-
return left;
|
|
627
|
-
}
|
|
628
|
-
else if ('-' === operation) {
|
|
629
|
-
return IntermediaryNumber.Zero;
|
|
630
|
-
}
|
|
631
|
-
else if ('/' === operation) {
|
|
632
|
-
return IntermediaryNumber.One;
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
if (left instanceof IntermediaryCalculation
|
|
636
|
-
&& left.has_infinity
|
|
637
|
-
&& (('+' === left.operation
|
|
638
|
-
&& '-' === operation)
|
|
639
|
-
|| ('-' === left.operation
|
|
640
|
-
&& '+' === operation))
|
|
641
|
-
&& (right instanceof IntermediaryNumberInfinity)
|
|
642
|
-
&& (!(left.left_operand instanceof IntermediaryNumberInfinity)
|
|
643
|
-
|| !(left.right_operand instanceof IntermediaryNumberInfinity))) {
|
|
644
|
-
return (left.left_operand instanceof IntermediaryNumberInfinity
|
|
645
|
-
? left.right_operand
|
|
646
|
-
: left.left_operand);
|
|
647
|
-
}
|
|
648
|
-
else if (right instanceof IntermediaryCalculation
|
|
649
|
-
&& right.has_infinity
|
|
650
|
-
&& (('+' === right.operation
|
|
651
|
-
&& '-' === operation)
|
|
652
|
-
|| ('-' === right.operation
|
|
653
|
-
&& '+' === operation))
|
|
654
|
-
&& (left instanceof IntermediaryNumberInfinity)
|
|
655
|
-
&& (!(right.left_operand instanceof IntermediaryNumberInfinity)
|
|
656
|
-
|| !(right.right_operand instanceof IntermediaryNumberInfinity))) {
|
|
657
|
-
return (right.left_operand instanceof IntermediaryNumberInfinity
|
|
658
|
-
? right.right_operand
|
|
659
|
-
: right.left_operand);
|
|
660
|
-
}
|
|
661
|
-
else if ('/' === operation
|
|
662
|
-
&& !right.isOne()
|
|
663
|
-
&& left instanceof IntermediaryCalculation
|
|
664
|
-
&& left.has_infinity
|
|
665
|
-
&& !(left.left_operand instanceof IntermediaryNumberInfinity)
|
|
666
|
-
&& '*x'.includes(left.operation)
|
|
667
|
-
&& right instanceof IntermediaryNumberInfinity) {
|
|
668
|
-
return left.left_operand;
|
|
669
|
-
}
|
|
670
|
-
if ('+' === operation) {
|
|
671
|
-
if (left.isZero()) {
|
|
672
|
-
value = right;
|
|
673
|
-
}
|
|
674
|
-
else if (right.isZero()) {
|
|
675
|
-
value = left;
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
else if ('-' === operation && right.isZero()) {
|
|
679
|
-
value = left;
|
|
680
|
-
}
|
|
681
|
-
else if ('*x'.includes(operation)) {
|
|
682
|
-
if (left.isZero() || right.isOne()) {
|
|
683
|
-
value = left;
|
|
684
|
-
}
|
|
685
|
-
else if (right.isZero() || left.isOne()) {
|
|
686
|
-
value = right;
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
else if ('/' === operation && right.isOne()) {
|
|
690
|
-
value = left;
|
|
691
|
-
}
|
|
692
|
-
return value;
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
//#endregion
|
|
696
|
-
//#region TokenScan
|
|
697
|
-
class TokenSpan {
|
|
698
|
-
from;
|
|
699
|
-
to;
|
|
700
|
-
type;
|
|
701
|
-
constructor(from, to, type) {
|
|
702
|
-
this.from = from;
|
|
703
|
-
this.to = to;
|
|
704
|
-
this.type = type;
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
export class TokenScanError extends Error {
|
|
708
|
-
}
|
|
709
|
-
export class TokenScanParseError extends Error {
|
|
710
|
-
current;
|
|
711
|
-
scan;
|
|
712
|
-
state;
|
|
713
|
-
constructor(message, scan, state, current) {
|
|
714
|
-
super(message);
|
|
715
|
-
this.scan = scan;
|
|
716
|
-
this.state = state;
|
|
717
|
-
this.current = current;
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
const regex_numeric = (/(?:\d*\.\d*\(\d+\)r?|\d*\.\d*\[\d+\]r?|\d+(?:\.\d+r)?|\.\d+r?)/g);
|
|
721
|
-
export class TokenScan {
|
|
722
|
-
internal = {
|
|
723
|
-
parsed: undefined,
|
|
724
|
-
tokens: undefined,
|
|
725
|
-
valid: undefined,
|
|
726
|
-
};
|
|
727
|
-
value;
|
|
728
|
-
constructor(value) {
|
|
729
|
-
this.value = value;
|
|
730
|
-
}
|
|
731
|
-
get parsed() {
|
|
732
|
-
return this.#parse();
|
|
733
|
-
}
|
|
734
|
-
get resolve_type() {
|
|
735
|
-
return this.parsed.resolve_type;
|
|
736
|
-
}
|
|
737
|
-
get tokens() {
|
|
738
|
-
if (undefined === this.internal.tokens) {
|
|
739
|
-
this.internal.tokens = TokenScan.determine_tokens_from_scan(this);
|
|
740
|
-
}
|
|
741
|
-
return this.internal.tokens;
|
|
742
|
-
}
|
|
743
|
-
get type() {
|
|
744
|
-
return 'TokenScan';
|
|
745
|
-
}
|
|
746
|
-
get valid() {
|
|
747
|
-
if (undefined === this.internal.valid) {
|
|
748
|
-
try {
|
|
749
|
-
this.#parse();
|
|
750
|
-
this.internal.valid = true;
|
|
751
|
-
}
|
|
752
|
-
catch {
|
|
753
|
-
this.internal.valid = false;
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
return this.internal.valid;
|
|
757
|
-
}
|
|
758
|
-
#parse() {
|
|
759
|
-
if (undefined === this.internal.parsed) {
|
|
760
|
-
this.internal.parsed = TokenScan.parse_scan(this);
|
|
761
|
-
}
|
|
762
|
-
return this.internal.parsed;
|
|
763
|
-
}
|
|
764
|
-
abs() {
|
|
765
|
-
return this.parsed.abs();
|
|
766
|
-
}
|
|
767
|
-
compare(value) {
|
|
768
|
-
return this.parsed.compare(value);
|
|
769
|
-
}
|
|
770
|
-
divide(value) {
|
|
771
|
-
if (IntermediaryNumber.reuse_or_create(value).isOne()) {
|
|
772
|
-
return this;
|
|
773
|
-
}
|
|
774
|
-
return new TokenScan([
|
|
775
|
-
this,
|
|
776
|
-
'/',
|
|
777
|
-
value,
|
|
778
|
-
]);
|
|
779
|
-
}
|
|
780
|
-
do_math_then_dispose(operator, right_operand) {
|
|
781
|
-
const result = this.parsed.do_math_then_dispose(operator, right_operand);
|
|
782
|
-
this.internal.parsed = undefined;
|
|
783
|
-
return result;
|
|
784
|
-
}
|
|
785
|
-
isGreaterThan(value) {
|
|
786
|
-
return this.parsed.isGreaterThan(value);
|
|
787
|
-
}
|
|
788
|
-
isLessThan(value) {
|
|
789
|
-
return this.parsed.isLessThan(value);
|
|
790
|
-
}
|
|
791
|
-
isOne() {
|
|
792
|
-
return ((is_string(this.value)
|
|
793
|
-
&& '1' === this.value.trim())
|
|
794
|
-
|| this.parsed.isOne());
|
|
795
|
-
}
|
|
796
|
-
isZero() {
|
|
797
|
-
return ((is_string(this.value)
|
|
798
|
-
&& '0' === this.value.trim())
|
|
799
|
-
|| this.parsed.isZero());
|
|
800
|
-
}
|
|
801
|
-
max(first, ...remaining) {
|
|
802
|
-
return this.parsed.max(first, ...remaining);
|
|
803
|
-
}
|
|
804
|
-
min(first, ...remaining) {
|
|
805
|
-
return this.parsed.min(first, ...remaining);
|
|
806
|
-
}
|
|
807
|
-
minus(value) {
|
|
808
|
-
if (IntermediaryNumber.reuse_or_create(value).isZero()) {
|
|
809
|
-
return this;
|
|
810
|
-
}
|
|
811
|
-
return new TokenScan([
|
|
812
|
-
this,
|
|
813
|
-
'-',
|
|
814
|
-
value,
|
|
815
|
-
]);
|
|
816
|
-
}
|
|
817
|
-
modulo(value) {
|
|
818
|
-
return new TokenScan([
|
|
819
|
-
this,
|
|
820
|
-
'%',
|
|
821
|
-
value,
|
|
822
|
-
]);
|
|
823
|
-
}
|
|
824
|
-
plus(value) {
|
|
825
|
-
if (IntermediaryNumber.reuse_or_create(value).isZero()) {
|
|
826
|
-
return this;
|
|
827
|
-
}
|
|
828
|
-
return new TokenScan([
|
|
829
|
-
this,
|
|
830
|
-
'+',
|
|
831
|
-
value,
|
|
832
|
-
]);
|
|
833
|
-
}
|
|
834
|
-
resolve() {
|
|
835
|
-
const parsed = this.parsed;
|
|
836
|
-
return IntermediaryCalculation.is(parsed) ? parsed.resolve() : parsed;
|
|
837
|
-
}
|
|
838
|
-
times(value) {
|
|
839
|
-
if (IntermediaryNumber.reuse_or_create(value).isOne()) {
|
|
840
|
-
return this;
|
|
841
|
-
}
|
|
842
|
-
return new TokenScan([
|
|
843
|
-
this,
|
|
844
|
-
'x',
|
|
845
|
-
value,
|
|
846
|
-
]);
|
|
847
|
-
}
|
|
848
|
-
toAmountString() {
|
|
849
|
-
return this.parsed.toAmountString();
|
|
850
|
-
}
|
|
851
|
-
toBigNumber() {
|
|
852
|
-
return this.parsed.toBigNumber();
|
|
853
|
-
}
|
|
854
|
-
toBigNumberOrFraction() {
|
|
855
|
-
return this.parsed.toBigNumberOrFraction();
|
|
856
|
-
}
|
|
857
|
-
toFraction() {
|
|
858
|
-
return this.parsed.toFraction();
|
|
859
|
-
}
|
|
860
|
-
toJSON() {
|
|
861
|
-
return {
|
|
862
|
-
type: 'TokenScan',
|
|
863
|
-
value: this.toStringCalculation(),
|
|
864
|
-
};
|
|
865
|
-
}
|
|
866
|
-
toString() {
|
|
867
|
-
return this.parsed.toString();
|
|
868
|
-
}
|
|
869
|
-
toStringCalculation() {
|
|
870
|
-
if (this.value instanceof Array) {
|
|
871
|
-
const left_operand = this.value[0];
|
|
872
|
-
const right_operand = IntermediaryNumber.reuse_or_create(this.value[2]);
|
|
873
|
-
return `${(left_operand.parsed instanceof IntermediaryNumber)
|
|
874
|
-
? left_operand.toString()
|
|
875
|
-
: `(${left_operand.toStringCalculation()})`} ${this.value[1]} ${(right_operand instanceof IntermediaryNumber)
|
|
876
|
-
? right_operand.toStringCalculation()
|
|
877
|
-
: `(${right_operand.toStringCalculation()})`}`;
|
|
878
|
-
}
|
|
879
|
-
return this.value;
|
|
880
|
-
}
|
|
881
|
-
static create(value) {
|
|
882
|
-
return new TokenScan(value);
|
|
883
|
-
}
|
|
884
|
-
static is(maybe) {
|
|
885
|
-
return maybe instanceof TokenScan;
|
|
886
|
-
}
|
|
887
|
-
static require_is(maybe) {
|
|
888
|
-
if (!this.is(maybe)) {
|
|
889
|
-
throw new Error('Argument is not an instanceof TokenScan');
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
static determine_tokens_from_scan(scan) {
|
|
893
|
-
let tokens = [];
|
|
894
|
-
const value = scan.toStringCalculation();
|
|
895
|
-
for (const entry of value.matchAll(/([\s]+)/g)) {
|
|
896
|
-
tokens.push(new TokenSpan(entry.index, entry.index + entry[0].length, 'ignore'));
|
|
897
|
-
}
|
|
898
|
-
for (const entry of value.matchAll(regex_numeric)) {
|
|
899
|
-
tokens.push(new TokenSpan(entry.index, entry.index + entry[0].length, 'numeric'));
|
|
900
|
-
}
|
|
901
|
-
for (const entry of value.matchAll(/([+/*x%-])/g)) {
|
|
902
|
-
tokens.push(new TokenSpan(entry.index, entry.index + entry[0].length, 'operation'));
|
|
903
|
-
}
|
|
904
|
-
for (const entry of value.matchAll(/(\()/g)) {
|
|
905
|
-
tokens.push(new TokenSpan(entry.index, entry.index + entry[0].length, 'nesting_open'));
|
|
906
|
-
}
|
|
907
|
-
for (const entry of value.matchAll(/(\))/g)) {
|
|
908
|
-
tokens.push(new TokenSpan(entry.index, entry.index + entry[0].length, 'nesting_close'));
|
|
909
|
-
}
|
|
910
|
-
for (const entry of value.matchAll(/(\bInfinity\b)/g)) {
|
|
911
|
-
tokens.push(new TokenSpan(entry.index, entry.index + entry[0].length, 'Infinity'));
|
|
912
|
-
}
|
|
913
|
-
tokens = tokens.sort((a, b) => {
|
|
914
|
-
return a.from - b.from;
|
|
915
|
-
});
|
|
916
|
-
const recursive_numerics = tokens.filter(maybe => ('numeric' === maybe.type
|
|
917
|
-
&& /[()]/.test(value.substring(maybe.from, maybe.to))));
|
|
918
|
-
tokens = tokens.filter((maybe) => {
|
|
919
|
-
if ('nesting_open' === maybe.type
|
|
920
|
-
|| 'nesting_close' === maybe.type) {
|
|
921
|
-
return !recursive_numerics.find(maybe_numeric => (maybe.from >= maybe_numeric.from
|
|
922
|
-
&& maybe.to <= maybe_numeric.to));
|
|
923
|
-
}
|
|
924
|
-
return true;
|
|
925
|
-
});
|
|
926
|
-
if (tokens.length < 1) {
|
|
927
|
-
throw new TokenScanError('No tokens found!');
|
|
928
|
-
}
|
|
929
|
-
else if (0 !== tokens[0].from) {
|
|
930
|
-
throw new TokenScanError('First token not at index 0!');
|
|
931
|
-
}
|
|
932
|
-
else if (value.length !== tokens[tokens.length - 1].to) {
|
|
933
|
-
throw new TokenScanError('Last token does not end at end of string!');
|
|
934
|
-
}
|
|
935
|
-
let nesting_balance = 0;
|
|
936
|
-
for (let index = 0; index < tokens.length; ++index) {
|
|
937
|
-
const token = tokens[index];
|
|
938
|
-
if ('nesting_open' === token.type) {
|
|
939
|
-
nesting_balance += (token.to - token.from);
|
|
940
|
-
}
|
|
941
|
-
else if ('nesting_close' === token.type) {
|
|
942
|
-
nesting_balance -= (token.to - token.from);
|
|
943
|
-
}
|
|
944
|
-
if (index > 0
|
|
945
|
-
&& tokens[index - 1].to !== token.from) {
|
|
946
|
-
console.error(tokens, index);
|
|
947
|
-
throw new TokenScanError(`Token expected to be found at index ${index}`);
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
if (0 !== nesting_balance) {
|
|
951
|
-
throw new TokenScanError('Imbalanced nesting in string!');
|
|
952
|
-
}
|
|
953
|
-
return this.massage_part_baked_tokens(scan, tokens.filter((maybe) => 'ignore' !== maybe.type));
|
|
954
|
-
}
|
|
955
|
-
static massage_part_baked_tokens(scan, tokens) {
|
|
956
|
-
const smoosh_numerics = [];
|
|
957
|
-
const value = scan.toStringCalculation();
|
|
958
|
-
for (let token_index = tokens.length - 1; token_index > 0; --token_index) {
|
|
959
|
-
const previous = tokens[token_index - 1];
|
|
960
|
-
const current = tokens[token_index];
|
|
961
|
-
if ('numeric' === previous.type) {
|
|
962
|
-
const previous_value = value.substring(previous.from, previous.to);
|
|
963
|
-
const current_value = value.substring(current.from, current.to);
|
|
964
|
-
if (current_value.startsWith('.')
|
|
965
|
-
&& /^\d+$/.test(previous_value)) {
|
|
966
|
-
smoosh_numerics.push(token_index);
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
for (const index of smoosh_numerics) {
|
|
971
|
-
tokens.splice(index - 1, 2, new TokenSpan(tokens[index - 1].from, tokens[index].to, 'numeric'));
|
|
972
|
-
}
|
|
973
|
-
const convert_to_negative = [];
|
|
974
|
-
if (tokens.length >= 2
|
|
975
|
-
&& 'operation' === tokens[0].type
|
|
976
|
-
&& '-' === value[tokens[0].from]
|
|
977
|
-
&& 'numeric' === tokens[1].type) {
|
|
978
|
-
convert_to_negative.push(0);
|
|
979
|
-
}
|
|
980
|
-
for (let token_index = 0; token_index < tokens.length; ++token_index) {
|
|
981
|
-
const token = tokens[token_index];
|
|
982
|
-
const next = tokens[token_index + 1];
|
|
983
|
-
const after = tokens[token_index + 2];
|
|
984
|
-
if (('nesting_open' === token.type
|
|
985
|
-
|| 'operation' === token.type)
|
|
986
|
-
&& next
|
|
987
|
-
&& after
|
|
988
|
-
&& 'operation' === next.type
|
|
989
|
-
&& '-' === value[next.from]
|
|
990
|
-
&& 'numeric' === after.type) {
|
|
991
|
-
convert_to_negative.push(token_index + 1);
|
|
992
|
-
token_index += 2;
|
|
993
|
-
continue;
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
for (const index of convert_to_negative.reverse()) {
|
|
997
|
-
tokens.splice(index, 2, new TokenSpan(tokens[index].from, tokens[index + 1].to, 'numeric'));
|
|
998
|
-
}
|
|
999
|
-
return tokens;
|
|
1000
|
-
}
|
|
1001
|
-
static parse_scan(scan) {
|
|
1002
|
-
const reduced = scan.tokens.reduce((was, is, index) => TokenScan.reduce(scan, was, is, index), default_tokenizer_state());
|
|
1003
|
-
if (undefined !== reduced.left_operand
|
|
1004
|
-
&& '' === reduced.operation
|
|
1005
|
-
&& undefined === reduced.right_operand
|
|
1006
|
-
&& 0 === reduced.outter_stack.length) {
|
|
1007
|
-
return reduced.left_operand;
|
|
1008
|
-
}
|
|
1009
|
-
throw new TokenScanParseError('Parse in unsupported state!', scan, reduced);
|
|
1010
|
-
}
|
|
1011
|
-
static reduce(scan, was, is, index) {
|
|
1012
|
-
const value = scan.toStringCalculation();
|
|
1013
|
-
if (is_nesting_open(is)) {
|
|
1014
|
-
if ('right' === was.operand_mode) {
|
|
1015
|
-
if (undefined === was.left_operand) {
|
|
1016
|
-
if (!(was.outter_stack.length > 0
|
|
1017
|
-
&& !(was.outter_stack[was.outter_stack.length - 1] instanceof TokenSpan))) {
|
|
1018
|
-
throw new TokenScanParseError(
|
|
1019
|
-
// eslint-disable-next-line max-len
|
|
1020
|
-
'Nesting opened without left operand to push into stack!', scan, was);
|
|
1021
|
-
}
|
|
1022
|
-
return was;
|
|
1023
|
-
}
|
|
1024
|
-
else if ('' === was.operation) {
|
|
1025
|
-
throw new TokenScanParseError('Nesting opened without operation to push into stack!', scan, was, is);
|
|
1026
|
-
}
|
|
1027
|
-
was.outter_stack.push({
|
|
1028
|
-
left_operand: was.left_operand,
|
|
1029
|
-
operation: was.operation,
|
|
1030
|
-
});
|
|
1031
|
-
was.left_operand = undefined;
|
|
1032
|
-
was.operation = '';
|
|
1033
|
-
was.operand_mode = 'left';
|
|
1034
|
-
}
|
|
1035
|
-
else {
|
|
1036
|
-
was.outter_stack.push(is);
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
else if (is_nesting_close(is)) {
|
|
1040
|
-
const popped = was.outter_stack.pop();
|
|
1041
|
-
if (popped instanceof TokenSpan) {
|
|
1042
|
-
if ('nesting_open' === popped.type
|
|
1043
|
-
&& '' === was.operation
|
|
1044
|
-
&& undefined !== was.left_operand
|
|
1045
|
-
&& undefined === was.right_operand) {
|
|
1046
|
-
// no-op, deliberately do nothing
|
|
1047
|
-
}
|
|
1048
|
-
else {
|
|
1049
|
-
throw new TokenScanParseError(
|
|
1050
|
-
// eslint-disable-next-line max-len
|
|
1051
|
-
'token span popping in this context not yet implemented', scan, was, is);
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
else if (undefined === popped) {
|
|
1055
|
-
if (index !== (scan.tokens.length - 1)
|
|
1056
|
-
&& ('' !== was.operation
|
|
1057
|
-
|| undefined !== was.right_operand)) {
|
|
1058
|
-
throw new TokenScanParseError('Token scan finished with incomplete parse!', scan, was, is);
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
else {
|
|
1062
|
-
if ('' === was.operation
|
|
1063
|
-
&& undefined !== was.left_operand
|
|
1064
|
-
&& undefined === was.right_operand) {
|
|
1065
|
-
was.left_operand = new IntermediaryCalculation(popped.left_operand, popped.operation, was.left_operand);
|
|
1066
|
-
was.operation = '';
|
|
1067
|
-
was.operand_mode = 'right';
|
|
1068
|
-
}
|
|
1069
|
-
else {
|
|
1070
|
-
throw new TokenScanParseError(
|
|
1071
|
-
// eslint-disable-next-line max-len
|
|
1072
|
-
'token span popping in this context not yet implemented', scan, was, is);
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
else if (is_numeric(is) || is_infinity(is)) {
|
|
1077
|
-
if ('left' === was.operand_mode) {
|
|
1078
|
-
was.left_operand = IntermediaryNumber.create(value.substring(is.from, is.to));
|
|
1079
|
-
was.operand_mode = 'right';
|
|
1080
|
-
}
|
|
1081
|
-
else {
|
|
1082
|
-
if ('' === was.operation) {
|
|
1083
|
-
throw new TokenScanParseError('Right operand detected without operation!', scan, was, is);
|
|
1084
|
-
}
|
|
1085
|
-
else if (undefined === was.left_operand) {
|
|
1086
|
-
throw new TokenScanParseError('Right operand detected without left operand!', scan, was, is);
|
|
1087
|
-
}
|
|
1088
|
-
let resolved = new IntermediaryCalculation(was.left_operand, was.operation, IntermediaryNumber.create(value.substring(is.from, is.to)));
|
|
1089
|
-
if (was.outter_stack.length > 0
|
|
1090
|
-
&& !(was.outter_stack[was.outter_stack.length - 1] instanceof TokenSpan)) {
|
|
1091
|
-
const previous = (was.outter_stack.pop());
|
|
1092
|
-
resolved = new IntermediaryCalculation(previous.left_operand, previous.operation, resolved);
|
|
1093
|
-
}
|
|
1094
|
-
was.left_operand = resolved;
|
|
1095
|
-
was.operation = '';
|
|
1096
|
-
was.right_operand = undefined;
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
else if ('operation' === is.type) {
|
|
1100
|
-
if (undefined === was.left_operand) {
|
|
1101
|
-
throw new TokenScanParseError('Operation detected without left operand!', scan, was, is);
|
|
1102
|
-
}
|
|
1103
|
-
else if ('' !== was.operation) {
|
|
1104
|
-
throw new TokenScanParseError(`Cannot set operation when operation already set to "${was.operation}"`, scan, was, is);
|
|
1105
|
-
}
|
|
1106
|
-
const maybe = value.substring(is.from, is.to);
|
|
1107
|
-
is_operation_value(maybe);
|
|
1108
|
-
was.operation = maybe;
|
|
1109
|
-
}
|
|
1110
|
-
else {
|
|
1111
|
-
throw new TokenScanParseError('not implemented', scan, was, is);
|
|
1112
|
-
}
|
|
1113
|
-
return was;
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
//#endregion
|