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