@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.
@@ -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