• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

satisfactory-dev / Intermediary-Number / 22593198569

02 Mar 2026 07:56PM UTC coverage: 96.731% (+1.4%) from 95.286%
22593198569

push

github

SignpostMarv
drop c8

467 of 488 branches covered (95.7%)

Branch coverage included in aggregate %.

2462 of 2540 relevant lines covered (96.93%)

1024.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.05
/lib/IntermediaryNumber.ts
1
import assert from 'assert';
5✔
2
import BigNumber from 'bignumber.js';
5✔
3
import Fraction from 'fraction.js';
5✔
4

5✔
5
import {
5✔
6
        is_string,
5✔
7
} from './Docs.json.ts';
5✔
8

5✔
9
import type {
5✔
10
        amount_string,
5✔
11
} from './NumberStrings.ts';
5✔
12
import {
5✔
13
        NumberStrings,
5✔
14
} from './NumberStrings.ts';
5✔
15

5✔
16
import type {
5✔
17
        input_types,
5✔
18
        type_property_types,
5✔
19
        value_types,
5✔
20
} from './IntermediaryNumberTypes';
5✔
21

5✔
22
// #region Types
5✔
23

5✔
24
export type math_types = (
5✔
25
        | operand_types
5✔
26
        | input_types
5✔
27
);
5✔
28

5✔
29
export const regex_recurring_number = (
5✔
30
        /^-?(\d+\.)(\d+r|\d*\[\d+\]r?|\d*\(\d+\)r?)$/
5✔
31
);
5✔
32

5✔
33
export type CanDoMath_result_types = (
5✔
34
        | IntermediaryNumber
5✔
35
        | IntermediaryCalculation
5✔
36
        | TokenScan
5✔
37
);
5✔
38

5✔
39
export type operation_types = (
5✔
40
        | '+'
5✔
41
        | '-'
5✔
42
        | '*'
5✔
43
        | 'x'
5✔
44
        | '/'
5✔
45
        | '%'
5✔
46
);
5✔
47

5✔
48
export type operand_type_property_types = (
5✔
49
        | type_property_types
5✔
50
        | 'IntermediaryCalculation'
5✔
51
        | 'TokenScan'
5✔
52
);
5✔
53

5✔
54
export type CanConvertTypeJson = (
5✔
55
        | {
5✔
56
                type: 'IntermediaryNumber',
5✔
57
                value: string,
5✔
58
        }
5✔
59
        | {
5✔
60
                type: 'IntermediaryCalculation',
5✔
61
                left: CanConvertTypeJson,
5✔
62
                operation: operation_types,
5✔
63
                right: CanConvertTypeJson,
5✔
64
        }
5✔
65
        | {
5✔
66
                type: 'TokenScan',
5✔
67
                value: string,
5✔
68
        }
5✔
69
);
5✔
70

5✔
71
export type CanDoMathWithDispose_operator_types = (
5✔
72
        | 'divide'
5✔
73
        | 'minus'
5✔
74
        | 'modulo'
5✔
75
        | 'plus'
5✔
76
        | 'times'
5✔
77
);
5✔
78

5✔
79
export type operand_types = (
5✔
80
        | IntermediaryNumber
5✔
81
        | IntermediaryCalculation
5✔
82
        | TokenScan
5✔
83
);
5✔
84

5✔
85
// #region TokenScan types
5✔
86

5✔
87
type TokenSpan_types = (
5✔
88
        | 'ignore'
5✔
89
        | 'nesting_open'
5✔
90
        | 'nesting_close'
5✔
91
        | 'numeric'
5✔
92
        | 'operation'
5✔
93
        | 'Infinity'
5✔
94
);
5✔
95

5✔
96
type TokenSpan_types_part_baked = Exclude<
5✔
97
        TokenSpan_types,
5✔
98
        | 'ignore'
5✔
99
>;
5✔
100

5✔
101
type TokenScan_internals = {
5✔
102
        parsed: IntermediaryNumber|IntermediaryCalculation|undefined,
5✔
103
        tokens: (TokenSpan<TokenSpan_types_part_baked>[])|undefined,
5✔
104
        valid: boolean|undefined,
5✔
105
};
5✔
106

5✔
107
type TokenScan_parsing_tokens = Omit<TokenScan, 'is_valid'|'tokens'|'parsed'>;
5✔
108
type TokenScan_parsing_value = Omit<TokenScan, 'is_valid'|'parsed'>;
5✔
109

5✔
110
type TokenScan_tokenizer_operand_buffer = (
5✔
111
        | IntermediaryNumber
5✔
112
        | IntermediaryCalculation
5✔
113
        | undefined
5✔
114
);
5✔
115

5✔
116
type incomplete_operation = {
5✔
117
        left_operand: Exclude<
5✔
118
                TokenScan_tokenizer_operand_buffer,
5✔
119
                undefined
5✔
120
        >,
5✔
121
        operation: operation_types,
5✔
122
};
5✔
123

5✔
124
type TokenScan_tokenizer = {
5✔
125
        outter_stack: (
5✔
126
                | incomplete_operation
5✔
127
                | TokenSpan<'nesting_open'>
5✔
128
        )[],
5✔
129
        left_operand: TokenScan_tokenizer_operand_buffer,
5✔
130
        right_operand: TokenScan_tokenizer_operand_buffer,
5✔
131
        operation: ''|operation_types,
5✔
132
        operand_mode: 'left'|'right',
5✔
133
};
5✔
134

5✔
135
// #endregion
5✔
136

5✔
137
// #endregion
5✔
138

5✔
139
// #region interfaces
5✔
140

5✔
141
interface HasType
5✔
142
{
5✔
143
        get type(): operand_type_property_types;
5✔
144
}
5✔
145

5✔
146
interface CanDoMath<
5✔
147
        ResultType extends CanDoMath_result_types = CanDoMath_result_types,
5✔
148
        ResolveString extends string = type_property_types,
5✔
149
> extends HasType {
5✔
150
        get resolve_type(): ResolveString;
5✔
151

5✔
152
        compare(
5✔
153
                value: math_types
5✔
154
        ): -1|0|1;
5✔
155

5✔
156
        divide(
5✔
157
                value: math_types
5✔
158
        ): ResultType;
5✔
159

5✔
160
        minus(
5✔
161
                value: math_types
5✔
162
        ): ResultType;
5✔
163

5✔
164
        modulo(
5✔
165
                value: math_types
5✔
166
        ): ResultType;
5✔
167

5✔
168
        plus(
5✔
169
                value: math_types
5✔
170
        ): ResultType;
5✔
171

5✔
172
        times(
5✔
173
                value: math_types
5✔
174
        ): ResultType;
5✔
175

5✔
176
        abs(): (
5✔
177
                | operand_types
5✔
178
        );
5✔
179

5✔
180
        max(
5✔
181
                first: math_types,
5✔
182
                ...remaining: math_types[]
5✔
183
        ): operand_types;
5✔
184

5✔
185
        min(
5✔
186
                first: math_types,
5✔
187
                ...remaining: math_types[]
5✔
188
        ): operand_types;
5✔
189
}
5✔
190

5✔
191
interface CanResolveMath<
5✔
192
        T extends CanDoMath_result_types = CanDoMath_result_types,
5✔
193
> extends CanDoMath<
5✔
194
                T,
5✔
195
                string
5✔
196
        > {
5✔
197
        resolve(): IntermediaryNumber;
5✔
198
}
5✔
199

5✔
200
export interface CanConvertType extends HasType
5✔
201
{
5✔
202
        toAmountString(): amount_string;
5✔
203

5✔
204
        toBigNumber(): BigNumber;
5✔
205

5✔
206
        toBigNumberOrFraction(): BigNumber|Fraction;
5✔
207

5✔
208
        toFraction(): Fraction;
5✔
209

5✔
210
        toString(): string;
5✔
211

5✔
212
        isLessThan(value: math_types): boolean;
5✔
213

5✔
214
        isGreaterThan(value: math_types): boolean;
5✔
215

5✔
216
        isOne(): boolean;
5✔
217

5✔
218
        isZero(): boolean;
5✔
219

5✔
220
        toJSON(): CanConvertTypeJson;
5✔
221
}
5✔
222

5✔
223
interface CanDoMathWithDispose<
5✔
224
        ResultType extends CanDoMath_result_types = CanDoMath_result_types,
5✔
225
        ResolveString extends string = type_property_types,
5✔
226
> extends CanConvertType, CanDoMath<
5✔
227
                ResultType,
5✔
228
                ResolveString
5✔
229
        > {
5✔
230
        do_math_then_dispose(
5✔
231
                operator: CanDoMathWithDispose_operator_types,
5✔
232
                right_operand: math_types
5✔
233
        ): ResultType;
5✔
234
}
5✔
235

5✔
236
interface CanResolveMathWithDispose<
5✔
237
        T extends CanDoMath_result_types = CanDoMath_result_types,
5✔
238
> extends
5✔
239
        CanResolveMath<T>,
5✔
240
        CanDoMathWithDispose<T, string>
5✔
241
{
5✔
242
}
5✔
243

5✔
244
// #endregion
5✔
245

5✔
246
// #region utility functions
5✔
247

5✔
248
function do_math(
30✔
249
        left_operand: IntermediaryNumber|IntermediaryCalculation,
30✔
250
        operator: operation_types,
30✔
251
        right_operand: math_types,
30✔
252
): operand_types {
30✔
253
        return IntermediaryCalculation.maybe_reduce_operands(
30✔
254
                left_operand,
30✔
255
                operator,
30✔
256
                IntermediaryNumber.reuse_or_create(right_operand),
30✔
257
        );
30✔
258
}
30✔
259

5✔
260
function abs(
8✔
261
        value:
8✔
262
                | operand_types,
8✔
263
): operand_types {
8✔
264
        if (value.isZero()) {
8✔
265
                return value;
3✔
266
        }
3✔
267

4✔
268
        return (value.isLessThan(0)
4✔
269
                ? IntermediaryNumber.Zero.minus(
8✔
270
                        value,
2✔
271
                )
2✔
272
                : value
8✔
273
        );
8✔
274
}
8✔
275

5✔
276
function compare(
5,501✔
277
        value: math_types,
5,501✔
278
        to: CanConvertType,
5,501✔
279
): 0|1|-1 {
5,501✔
280
        if (
5,501✔
281
                value instanceof IntermediaryNumberInfinity
5,501✔
282
                && to instanceof IntermediaryNumberInfinity
5,501!
283
        ) {
5,501!
284
                return 0;
×
285
        } else if (value instanceof IntermediaryNumberInfinity) {
5,501!
286
                return 1;
×
287
        } else if (to instanceof IntermediaryNumberInfinity) {
5,501!
288
                return -1;
×
289
        }
×
290

5,501✔
291
        const comparable = IntermediaryNumber.reuse_or_create(
5,501✔
292
                value,
5,501✔
293
        ).toBigNumberOrFraction();
5,501✔
294

5,501✔
295
        let result: number|null;
5,501✔
296

5,501✔
297
        if (comparable instanceof BigNumber) {
5,501✔
298
                result = to.toBigNumber().comparedTo(comparable);
393✔
299
        } else {
5,501✔
300
                result = to.toFraction().compare(comparable);
4✔
301
        }
4✔
302

5,501✔
303
        assert.strictEqual(
5,501✔
304
                (
5,501✔
305
                        -1 === result
5,501✔
306
                        || 0 === result
5,501✔
307
                        || 1 === result
5,501✔
308
                ),
5,501✔
309
                true,
5,501✔
310
                `Expecting -1, 0, or 1, receieved ${JSON.stringify(result)}`,
5,501✔
311
        );
5,501✔
312

5,501✔
313
        return result as -1|0|1;
5,501✔
314
}
5,501✔
315

5✔
316
const conversion_cache = new class {
5✔
317
        private toAmountString_cache: undefined|WeakMap<
5✔
318
                CanConvertType,
5✔
319
                amount_string
5✔
320
        >;
5✔
321

5✔
322
        private toBigNumber_cache: WeakMap<CanConvertType, BigNumber>|undefined;
5✔
323

5✔
324
        private toFraction_cache: WeakMap<CanConvertType, Fraction>|undefined;
5✔
325

5✔
326
        private toString_cache: WeakMap<CanConvertType, string>|undefined;
5✔
327

5✔
328
        get AmountString(): WeakMap<CanConvertType, amount_string> {
5✔
329
                if (!this.toAmountString_cache) {
3✔
330
                        this.toAmountString_cache = new WeakMap();
1✔
331
                }
1✔
332

3✔
333
                return this.toAmountString_cache;
3✔
334
        }
3✔
335

5✔
336
        get BigNumber(): WeakMap<CanConvertType, BigNumber> {
5✔
337
                if (!this.toBigNumber_cache) {
7,078✔
338
                        this.toBigNumber_cache = new WeakMap();
3✔
339
                }
3✔
340

7,078✔
341
                return this.toBigNumber_cache;
7,078✔
342
        }
7,078✔
343

5✔
344
        get Fraction(): WeakMap<CanConvertType, Fraction> {
5✔
345
                if (!this.toFraction_cache) {
1,378✔
346
                        this.toFraction_cache = new WeakMap();
4✔
347
                }
4✔
348

1,378✔
349
                return this.toFraction_cache;
1,378✔
350
        }
1,378✔
351

5✔
352
        get String(): WeakMap<CanConvertType, string> {
5✔
353
                if (!this.toString_cache) {
626✔
354
                        this.toString_cache = new WeakMap();
3✔
355
                }
3✔
356

626✔
357
                return this.toString_cache;
626✔
358
        }
626✔
359

5✔
360
        dispose(of: CanConvertType) {
5✔
361
                for (const cache of [
3✔
362
                        this.toAmountString_cache,
3✔
363
                        this.toBigNumber_cache,
3✔
364
                        this.toFraction_cache,
3✔
365
                        this.toString_cache,
3✔
366
                ]) {
3✔
367
                        if (cache) {
12✔
368
                                cache.delete(of);
9✔
369
                        }
9✔
370
                }
12✔
371
        }
3✔
372
}();
5✔
373

5✔
374
export function dispose(value: operand_types) {
5✔
375
        conversion_cache.dispose(value);
3✔
376
}
3✔
377

5✔
378
function max(
9✔
379
        first: math_types,
9✔
380
        second: math_types,
9✔
381
        ...remaining: math_types[]
9✔
382
): operand_types {
9✔
383
        let max = IntermediaryNumber.reuse_or_create(first);
9✔
384

9✔
385
        for (const entry of [second, ...remaining]) {
9✔
386
                const maybe = IntermediaryNumber.reuse_or_create(entry);
30✔
387
                if (-1 === max.compare(maybe)) {
30✔
388
                        max = maybe;
15✔
389
                }
15✔
390
        }
30✔
391

9✔
392
        return IntermediaryNumber.reuse_or_create(max);
9✔
393
}
9✔
394

5✔
395
function min(
6✔
396
        first: math_types,
6✔
397
        second: math_types,
6✔
398
        ...remaining: math_types[]
6✔
399
): operand_types {
6✔
400
        let min = IntermediaryNumber.reuse_or_create(first);
6✔
401

6✔
402
        for (const entry of [second, ...remaining]) {
6✔
403
                const maybe = IntermediaryNumber.reuse_or_create(entry);
18✔
404
                if (-1 === maybe.compare(min)) {
18✔
405
                        min = maybe;
3✔
406
                }
3✔
407
        }
18✔
408

6✔
409
        return IntermediaryNumber.reuse_or_create(min);
6✔
410
}
6✔
411

5✔
412
// #region TokenScan utility functions
5✔
413

5✔
414
function default_tokenizer_state(): TokenScan_tokenizer {
936✔
415
        return {
936✔
416
                outter_stack: [],
936✔
417
                left_operand: undefined,
936✔
418
                operation: '',
936✔
419
                right_operand: undefined,
936✔
420
                operand_mode: 'left',
936✔
421
        };
936✔
422
}
936✔
423

5✔
424
function is_nesting_open(
8,994✔
425
        maybe: TokenSpan<TokenSpan_types>,
8,994✔
426
): maybe is TokenSpan<'nesting_open'> {
8,994✔
427
        return 'nesting_open' === maybe.type;
8,994✔
428
}
8,994✔
429

5✔
430
function is_nesting_close(
6,479✔
431
        maybe: TokenSpan<TokenSpan_types>,
6,479✔
432
): maybe is TokenSpan<'nesting_close'> {
6,479✔
433
        return 'nesting_close' === maybe.type;
6,479✔
434
}
6,479✔
435

5✔
436
function is_numeric(
3,964✔
437
        maybe: TokenSpan<TokenSpan_types>,
3,964✔
438
): maybe is TokenSpan<'numeric'> {
3,964✔
439
        return 'numeric' === maybe.type;
3,964✔
440
}
3,964✔
441

5✔
442
function is_infinity(
1,742✔
443
        maybe: TokenSpan<TokenSpan_types>,
1,742✔
444
): maybe is TokenSpan<'Infinity'> {
1,742✔
445
        return 'Infinity' === maybe.type;
1,742✔
446
}
1,742✔
447

5✔
448
export function is_operation_value(
5✔
449
        maybe: string,
1,525✔
450
): asserts maybe is operation_types {
1,525✔
451
        if (
1,525✔
452
                !(
1,525✔
453
                        maybe.length === 1
1,525✔
454
                        && '+-/x*%'.includes(maybe)
1,525✔
455
                )
1,525✔
456
        ) {
1,525✔
457
                throw new TokenScanError(
1✔
458
                        `Expected operation value, found "${maybe}"`,
1✔
459
                );
1✔
460
        }
1✔
461
}
1,525✔
462

5✔
463
// #endregion
5✔
464

5✔
465
// #endregion
5✔
466

5✔
467
// #region IntermediaryNumber
5✔
468

5✔
469
export class IntermediaryNumber implements CanDoMathWithDispose {
5✔
470
        private readonly value: value_types;
5✔
471

5✔
472
        static readonly One = new this('1');
5✔
473

5✔
474
        static readonly Zero = new this('0');
5✔
475

5✔
476
        protected constructor(value: value_types) {
5✔
477
                this.value = value;
12,370✔
478
        }
12,370✔
479

5✔
480
        get resolve_type(): type_property_types {
5✔
481
                return this.type;
224✔
482
        }
224✔
483

5✔
484
        get type(): type_property_types {
5✔
485
                if (this.value instanceof BigNumber) {
15,791✔
486
                        return 'BigNumber';
8,161✔
487
                } else if (this.value instanceof Fraction) {
15,791✔
488
                        return 'Fraction';
2,562✔
489
                } else if (NumberStrings.is_amount_string(this.value)) {
7,629✔
490
                        return 'amount_string';
4,995✔
491
                }
4,995✔
492

26✔
493
                return 'numeric_string';
26✔
494
        }
15,791✔
495

5✔
496
        abs() {
5✔
497
                return abs(this);
5✔
498
        }
5✔
499

5✔
500
        compare(value: math_types): 0 | 1 | -1 {
5✔
501
                return compare(value, this);
4,440✔
502
        }
4,440✔
503

5✔
504
        divide(value: math_types) {
5✔
505
                return do_math(this, '/', value);
7✔
506
        }
7✔
507

5✔
508
        do_math_then_dispose(
5✔
509
                operator: CanDoMathWithDispose_operator_types,
3✔
510
                right_operand: math_types,
3✔
511
        ): CanDoMath_result_types {
3✔
512
                const result = this[operator](right_operand);
3✔
513

3✔
514
                if (result !== this) {
3✔
515
                        dispose(this);
1✔
516
                }
1✔
517

3✔
518
                return result;
3✔
519
        }
3✔
520

5✔
521
        isGreaterThan(value: math_types): boolean {
5✔
522
                return 1 === this.compare(value);
4✔
523
        }
4✔
524

5✔
525
        isLessThan(value: math_types): boolean {
5✔
526
                return -1 === this.compare(value);
4✔
527
        }
4✔
528

5✔
529
        isOne(): boolean {
5✔
530
                return 0 === this.compare(1);
1,620✔
531
        }
1,620✔
532

5✔
533
        isZero(): boolean {
5✔
534
                return 0 === this.compare(0);
2,772✔
535
        }
2,772✔
536

5✔
537
        max(
5✔
538
                first: math_types,
4✔
539
                ...remaining: math_types[]
4✔
540
        ): operand_types {
4✔
541
                return max(this, first, ...remaining);
4✔
542
        }
4✔
543

5✔
544
        min(
5✔
545
                first: math_types,
2✔
546
                ...remaining: math_types[]
2✔
547
        ): operand_types {
2✔
548
                return min(this, first, ...remaining);
2✔
549
        }
2✔
550

5✔
551
        minus(value: math_types) {
5✔
552
                return do_math(this, '-', value);
6✔
553
        }
6✔
554

5✔
555
        modulo(value: math_types) {
5✔
556
                return do_math(this, '%', value);
1✔
557
        }
1✔
558

5✔
559
        plus(value: math_types) {
5✔
560
                if (this.isZero()) {
5✔
561
                        return IntermediaryNumber.reuse_or_create(value);
2✔
562
                }
2✔
563

3✔
564
                return do_math(this, '+', value);
3✔
565
        }
5✔
566

5✔
567
        times(value: math_types) {
5✔
568
                return do_math(this, 'x', value);
4✔
569
        }
4✔
570

5✔
571
        toAmountString(): amount_string {
5✔
572
                if (NumberStrings.is_amount_string(this.value)) {
4✔
573
                        return this.value;
1✔
574
                }
1✔
575

2✔
576
                return NumberStrings.round_off(this.toBigNumberOrFraction());
2✔
577
        }
4✔
578

5✔
579
        toBigNumber(): BigNumber {
5✔
580
                if (this.value instanceof BigNumber) {
15,217✔
581
                        return this.value;
8,309✔
582
                } else if (this.value instanceof Fraction) {
15,217✔
583
                        return BigNumber(this.value.valueOf());
877✔
584
                }
877✔
585

6,014✔
586
                const cache = conversion_cache.BigNumber;
6,014✔
587

6,014✔
588
                if (cache.has(this)) {
15,217✔
589
                        return cache.get(this) as BigNumber;
4,441✔
590
                }
4,441✔
591

1,573✔
592
                const value = BigNumber(this.value);
1,573✔
593
                cache.set(this, value);
1,573✔
594

1,573✔
595
                return value;
1,573✔
596
        }
15,217✔
597

5✔
598
        toBigNumberOrFraction(): BigNumber | Fraction {
5✔
599
                return ('Fraction' === this.type)
11,450✔
600
                        ? this.toFraction()
11,450✔
601
                        : this.toBigNumber();
11,450✔
602
        }
11,450✔
603

5✔
604
        toFraction(): Fraction {
5✔
605
                if (this.value instanceof Fraction) {
2,635✔
606
                        return this.value;
1,260✔
607
                }
1,260✔
608

1,375✔
609
                const cache = conversion_cache.Fraction;
1,375✔
610

1,375✔
611
                if (cache.has(this)) {
2,635✔
612
                        return cache.get(this) as Fraction;
104✔
613
                }
104✔
614

1,271✔
615
                const value = new Fraction(this.toString());
1,271✔
616
                cache.set(this, value);
1,271✔
617

1,271✔
618
                return value;
1,271✔
619
        }
2,635✔
620

5✔
621
        toJSON(): CanConvertTypeJson {
5✔
622
                if (this.isOne()) {
67✔
623
                        return {
17✔
624
                                type: 'IntermediaryNumber',
17✔
625
                                value: '1',
17✔
626
                        };
17✔
627
                } else if (this.isZero()) {
67✔
628
                        return {
3✔
629
                                type: 'IntermediaryNumber',
3✔
630
                                value: '0',
3✔
631
                        };
3✔
632
                }
3✔
633

45✔
634
                if (this.value instanceof Fraction) {
67✔
635
                        const [left, right] = this.value.toFraction().split('/');
38✔
636

38✔
637
                        if (undefined === right) {
38✔
638
                                return {
5✔
639
                                        type: 'IntermediaryNumber',
5✔
640
                                        value: left,
5✔
641
                                };
5✔
642
                        }
5✔
643

33✔
644
                        return {
33✔
645
                                type: 'IntermediaryCalculation',
33✔
646
                                left: {
33✔
647
                                        type: 'IntermediaryNumber',
33✔
648
                                        value: left,
33✔
649
                                },
33✔
650
                                operation: '/',
33✔
651
                                right: {
33✔
652
                                        type: 'IntermediaryNumber',
33✔
653
                                        value: right,
33✔
654
                                },
33✔
655
                        };
33✔
656
                } else if (this.value instanceof BigNumber) {
67✔
657
                        return {
6✔
658
                                type: 'IntermediaryNumber',
6✔
659
                                value: this.value.toFixed(),
6✔
660
                        };
6✔
661
                }
6✔
662

1✔
663
                return {
1✔
664
                        type: 'IntermediaryNumber',
1✔
665
                        value: this.value,
1✔
666
                };
1✔
667
        }
67✔
668

5✔
669
        toString() {
5✔
670
                if (this.value instanceof BigNumber) {
2,231✔
671
                        return this.value.toFixed();
1,189✔
672
                }
1,189✔
673

1,041✔
674
                return this.value.toString();
1,041✔
675
        }
2,231✔
676

5✔
677
        toStringCalculation() {
5✔
678
                return this.toString();
45✔
679
        }
45✔
680

5✔
681
        static create(
5✔
682
                input: input_types,
12,353✔
683
        ): IntermediaryNumber {
12,353✔
684
                if ('' === input) {
12,353✔
685
                        return IntermediaryNumber.Zero;
2✔
686
                }
2✔
687

738✔
688
                if (input instanceof Fraction) {
12,353✔
689
                        return new this(input.simplify(1 / (2 ** 52)));
1,468✔
690
                }
1,468✔
691
                if (
26✔
692
                        input instanceof BigNumber
26✔
693
                        || NumberStrings.is_numeric_string(input)
26✔
694
                ) {
12,353✔
695
                        return new this(input);
4,541✔
696
                } else if ('number' === typeof input) {
24✔
697
                        return new this(BigNumber(input));
5,513✔
698
                } else if (is_string(input) && regex_recurring_number.test(input)) {
6,293✔
699
                        let only_last_digit_recurring = false;
529✔
700
                        if (/^\d*\.\d+r$/.test(input)) {
529✔
701
                                only_last_digit_recurring = true;
100✔
702
                        }
100✔
703

529✔
704
                        if (input.endsWith('r')) {
529✔
705
                                input = input.substring(0, input.length - 1);
204✔
706
                        }
204✔
707

529✔
708
                        if (only_last_digit_recurring) {
529✔
709
                                input = input.replace(/(\d)$/, '($1)');
100✔
710
                        } else if (input.includes('[')) {
529✔
711
                                input = input.replace(/\[(\d+)\]/, '($1)');
104✔
712
                        }
104✔
713

529✔
714
                        return new this(new Fraction(input));
529✔
715
                } else if ('Infinity' === input) {
754✔
716
                        return new IntermediaryNumberInfinity(new BigNumber('Infinity'));
225✔
717
                }
225✔
718

1✔
719
                throw new Error('Unsupported argument specified!');
1✔
720
        }
12,353✔
721

5✔
722
        static create_if_valid(
5✔
723
                input: string,
15✔
724
        ): operand_types|NotValid {
15✔
725
                const maybe = input.trim();
15✔
726

15✔
727
                if (
15✔
728
                        NumberStrings.is_amount_string(maybe)
15✔
729
                        || NumberStrings.is_numeric_string(maybe)
15✔
730
                ) {
15✔
731
                        return IntermediaryNumber.create(maybe);
1✔
732
                } else if (
15✔
733
                        /^(\d+|\d*\.\d+)\s*[+/*x%-]\s*(\d+|\d*\.\d+)$/.test(maybe)
14✔
734
                ) {
14✔
735
                        return TokenScan.create(input).parsed;
1✔
736
                }
1✔
737

13✔
738
                const scientific = /^(-?\d+(?:\.\d+))e([+-])(\d+)$/.exec(maybe);
13✔
739

13✔
740
                if (scientific) {
15✔
741
                        const calc = new IntermediaryCalculation(
1✔
742
                                IntermediaryNumber.Zero,
1✔
743
                                scientific[2] as '+'|'-',
1✔
744
                                IntermediaryNumber.create(scientific[3]),
1✔
745
                        ).toBigNumber();
1✔
746

1✔
747
                        return IntermediaryNumber.create(scientific[1]).times(
1✔
748
                                (new BigNumber(10)).pow(calc),
1✔
749
                        );
1✔
750
                }
1✔
751

12✔
752
                try {
12✔
753
                        return IntermediaryCalculation.fromString(maybe);
12✔
754
                } catch (err) {
15✔
755
                        return new NotValid(maybe, err);
2✔
756
                }
2✔
757
        }
15✔
758

5✔
759
        static fromJson(json: CanConvertTypeJson): CanDoMath_result_types {
5✔
760
                if ('IntermediaryNumber' === json.type) {
8✔
761
                        return this.create(json.value);
3✔
762
                } else if ('TokenScan' === json.type) {
8✔
763
                        return TokenScan.create(json.value);
1✔
764
                }
1✔
765

1✔
766
                return new IntermediaryCalculation(
1✔
767
                        this.fromJson(json.left),
1✔
768
                        json.operation,
1✔
769
                        this.fromJson(json.right),
1✔
770
                );
1✔
771
        }
8✔
772

5✔
773
        static reuse_or_create(
5✔
774
                input:
5,716✔
775
                        | operand_types
5,716✔
776
                        | input_types,
5,716✔
777
        ): operand_types {
5,716✔
778
                return (
5,716✔
779
                        (
5,716✔
780
                                (input instanceof IntermediaryNumber)
5,716✔
781
                                || (input instanceof IntermediaryCalculation)
5,716✔
782
                                || (input instanceof TokenScan)
5,716✔
783
                        )
5,716✔
784
                                ? input
5,716✔
785
                                : this.create(input)
5,716✔
786
                );
5,716✔
787
        }
5,716✔
788
}
5✔
789

5✔
790
// #endregion
5✔
791

5✔
792
// #region IntermediaryNumberInfinity
5✔
793

5✔
794
export class IntermediaryNumberInfinity extends IntermediaryNumber {
5✔
795
        static readonly One = new this(new BigNumber('Infinity'));
5✔
796

5✔
797
        static readonly Zero = new this(new BigNumber('Infinity'));
5✔
798

5✔
799
        isOne(): boolean {
5✔
800
                return false;
21✔
801
        }
21✔
802

5✔
803
        isZero(): boolean {
5✔
804
                return false;
1✔
805
        }
1✔
806

5✔
807
        toFraction(): Fraction {
5✔
808
                throw new Error('Cannot convert infinity to Fraction');
×
809
        }
×
810

5✔
811
        toString(): string {
5✔
812
                return 'Infinity';
24✔
813
        }
24✔
814

5✔
815
        toStringCalculation(): string {
5✔
816
                return 'Infinity';
×
817
        }
×
818
}
5✔
819

5✔
820
// #endregion
5✔
821

5✔
822
// #region IntermediaryCalculation
5✔
823

5✔
824
export class NotValid extends Error {
5✔
825
        readonly reason: unknown;
5✔
826

2✔
827
        readonly value: string;
5✔
828

5✔
829
        constructor(not_valid: string, reason: unknown) {
5✔
830
                super('Value given was not valid!');
2✔
831

2✔
832
                this.value = not_valid;
2✔
833
                this.reason = reason;
2✔
834
        }
2✔
835
}
5✔
836

5✔
837
const BigNumber_operation_map: {
5✔
838
        [key in Exclude<
5✔
839
                operation_types,
5✔
840
                '/'
5✔
841
        >]: ((a: BigNumber, b: BigNumber) => BigNumber)
5✔
842
} = {
5✔
843
        '+': (a, b) => a.plus(b),
5✔
844
        '-': (a, b) => a.minus(b),
5✔
845
        x: (a, b) => a.times(b),
5✔
846
        '*': (a, b) => a.times(b),
5✔
847
        '%': (a, b) => a.modulo(b),
5✔
848
};
5✔
849

5✔
850
const Fraction_operation_map: {
5✔
851
        [key in operation_types]: ((a: Fraction, b: Fraction) => Fraction)
5✔
852
} = {
5✔
853
        '+': (a, b) => a.add(b),
5✔
854
        '-': (a, b) => a.sub(b),
5✔
855
        x: (a, b) => a.mul(b),
5✔
856
        '*': (a, b) => a.mul(b),
5✔
857
        '/': (a, b) => a.div(b),
5✔
858
        '%': (a, b) => a.mod(b),
5✔
859
};
5✔
860

5✔
861
export class IntermediaryCalculation implements CanResolveMathWithDispose {
5✔
862
        readonly left_operand: operand_types;
5✔
863

1,560✔
864
        readonly operation: operation_types;
1,560✔
865

1,560✔
866
        readonly right_operand: operand_types;
5✔
867

5✔
868
        constructor(
5✔
869
                left: operand_types,
1,560✔
870
                operation: operation_types,
1,560✔
871
                right: operand_types,
1,560✔
872
        ) {
1,560✔
873
                this.left_operand = left;
1,560✔
874
                this.operation = operation;
1,560✔
875
                this.right_operand = right;
1,560✔
876
        }
1,560✔
877

5✔
878
        get has_infinity(): boolean {
5✔
879
                return (
1,337✔
880
                        this.left_operand instanceof IntermediaryNumberInfinity
1,337✔
881
                        || this.right_operand instanceof IntermediaryNumberInfinity
1,337✔
882
                );
1,337✔
883
        }
1,337✔
884

5✔
885
        get left_type(): operand_type_property_types {
5✔
886
                if (this.left_operand instanceof IntermediaryCalculation) {
600✔
887
                        return 'IntermediaryCalculation';
412✔
888
                }
412✔
889

188✔
890
                return this.left_operand.type;
188✔
891
        }
600✔
892

5✔
893
        get resolve_type(): string {
5✔
894
                return `${this.left_type} ${this.operation} ${this.right_type}`;
600✔
895
        }
600✔
896

5✔
897
        get right_type(): operand_type_property_types {
5✔
898
                if (this.right_operand instanceof IntermediaryCalculation) {
600✔
899
                        return 'IntermediaryCalculation';
104✔
900
                }
104✔
901

496✔
902
                return this.right_operand.type;
496✔
903
        }
600✔
904

5✔
905
        get type(): operand_type_property_types {
5✔
906
                return 'IntermediaryCalculation';
3✔
907
        }
3✔
908

5✔
909
        abs() {
5✔
910
                return abs(this);
3✔
911
        }
3✔
912

5✔
913
        compare(value: math_types): 0 | 1 | -1 {
5✔
914
                return compare(value, this);
1,061✔
915
        }
1,061✔
916

5✔
917
        divide(value: math_types) {
5✔
918
                return do_math(this, '/', value);
4✔
919
        }
4✔
920

5✔
921
        do_math_then_dispose(
5✔
922
                operator: CanDoMathWithDispose_operator_types,
2✔
923
                right_operand: math_types,
2✔
924
        ): CanDoMath_result_types {
2✔
925
                const result = this[operator](right_operand);
2✔
926

2✔
927
                if (result !== this) {
2✔
928
                        dispose(this);
2✔
929
                }
2✔
930

2✔
931
                return result;
2✔
932
        }
2✔
933

5✔
934
        isGreaterThan(value: math_types): boolean {
5✔
935
                return 1 === this.compare(value);
2✔
936
        }
2✔
937

5✔
938
        isLessThan(value: math_types): boolean {
5✔
939
                return -1 === this.compare(value);
2✔
940
        }
2✔
941

5✔
942
        isOne(): boolean {
5✔
943
                return 0 === this.compare(1);
323✔
944
        }
323✔
945

5✔
946
        isZero(): boolean {
5✔
947
                return 0 === this.compare(0);
724✔
948
        }
724✔
949

5✔
950
        max(
5✔
951
                first: math_types,
5✔
952
                ...remaining: math_types[]
5✔
953
        ): operand_types {
5✔
954
                return max(this, first, ...remaining);
5✔
955
        }
5✔
956

5✔
957
        min(
5✔
958
                first: math_types,
4✔
959
                ...remaining: math_types[]
4✔
960
        ): operand_types {
4✔
961
                return min(this, first, ...remaining);
4✔
962
        }
4✔
963

5✔
964
        minus(value: math_types) {
5✔
965
                return do_math(this, '-', value);
1✔
966
        }
1✔
967

5✔
968
        modulo(value: math_types) {
5✔
969
                return do_math(this, '%', value);
1✔
970
        }
1✔
971

5✔
972
        plus(value: math_types) {
5✔
973
                if (this.isZero()) {
3✔
974
                        return IntermediaryNumber.reuse_or_create(value);
1✔
975
                }
1✔
976

2✔
977
                return do_math(this, '+', value);
2✔
978
        }
3✔
979

5✔
980
        resolve(): IntermediaryNumber {
5✔
981
                const reduced = IntermediaryCalculation.maybe_short_circuit(
2,324✔
982
                        this.left_operand,
2,324✔
983
                        this.operation,
2,324✔
984
                        this.right_operand,
2,324✔
985
                );
2,324✔
986

2,324✔
987
                if (reduced instanceof IntermediaryNumber) {
2,324✔
988
                        return reduced;
375✔
989
                }
375✔
990

1,933✔
991
                const left_operand = this.operand_to_IntermediaryNumber(
1,933✔
992
                        this.left_operand,
1,933✔
993
                );
1,933✔
994
                const right_operand = this.operand_to_IntermediaryNumber(
1,933✔
995
                        this.right_operand,
1,933✔
996
                );
1,933✔
997
                const left = left_operand.toBigNumberOrFraction();
1,933✔
998
                const right = right_operand.toBigNumberOrFraction();
1,933✔
999

1,933✔
1000
                if (
1,933✔
1001
                        '/' === this.operation
1,933✔
1002
                        || left instanceof Fraction
2,324✔
1003
                        || right instanceof Fraction
2,324✔
1004
                ) {
2,324✔
1005
                        if (left_operand instanceof IntermediaryNumberInfinity) {
1,154!
1006
                                return left_operand;
×
1007
                        } else if (
1,154✔
1008
                                right_operand instanceof IntermediaryNumberInfinity
1,154✔
1009
                        ) {
1,154✔
1010
                                return right_operand;
1✔
1011
                        }
1✔
1012

7✔
1013
                        return IntermediaryNumber.create(
7✔
1014
                                Fraction_operation_map[this.operation](
7✔
1015
                                        (
7✔
1016
                                                (left instanceof BigNumber)
7✔
1017
                                                        ? left_operand.toFraction()
1,154✔
1018
                                                        : left
1,154✔
1019
                                        ),
1,154✔
1020
                                        (
1,154✔
1021
                                                (right instanceof BigNumber)
1,154✔
1022
                                                        ? right_operand.toFraction()
1,154✔
1023
                                                        : right
1,154✔
1024
                                        ),
1,154✔
1025
                                ),
1,154✔
1026
                        );
1,154✔
1027
                }
1,154✔
1028

791✔
1029
                return IntermediaryNumber.create(
791✔
1030
                        BigNumber_operation_map[this.operation](
791✔
1031
                                left,
791✔
1032
                                right,
791✔
1033
                        ),
791✔
1034
                );
791✔
1035
        }
2,324✔
1036

5✔
1037
        times(value: math_types) {
5✔
1038
                return do_math(this, 'x', value);
1✔
1039
        }
1✔
1040

5✔
1041
        toAmountString(): amount_string {
5✔
1042
                const cache = conversion_cache.AmountString;
3✔
1043

3✔
1044
                if (cache.has(this)) {
3✔
1045
                        return cache.get(this) as amount_string;
1✔
1046
                }
1✔
1047

1✔
1048
                const value = this.resolve().toAmountString();
1✔
1049
                cache.set(this, value);
1✔
1050

1✔
1051
                return value;
1✔
1052
        }
3✔
1053

5✔
1054
        toBigNumber(): BigNumber {
5✔
1055
                const cache = conversion_cache.BigNumber;
1,063✔
1056

1,063✔
1057
                if (cache.has(this)) {
1,063✔
1058
                        return cache.get(this) as BigNumber;
475✔
1059
                }
475✔
1060

586✔
1061
                const value = this.resolve().toBigNumber();
586✔
1062
                cache.set(this, value);
586✔
1063

586✔
1064
                return value;
586✔
1065
        }
1,063✔
1066

5✔
1067
        toBigNumberOrFraction(): BigNumber | Fraction {
5✔
1068
                return this.resolve().toBigNumberOrFraction();
11✔
1069
        }
11✔
1070

5✔
1071
        toFraction(): Fraction {
5✔
1072
                const cache = conversion_cache.Fraction;
3✔
1073

3✔
1074
                if (cache.has(this)) {
3✔
1075
                        return cache.get(this) as Fraction;
1✔
1076
                }
1✔
1077

1✔
1078
                const value = this.resolve().toFraction();
1✔
1079
                cache.set(this, value);
1✔
1080

1✔
1081
                return value;
1✔
1082
        }
3✔
1083

5✔
1084
        toJSON(): CanConvertTypeJson {
5✔
1085
                const left = (
10✔
1086
                        this.left_operand instanceof IntermediaryCalculation
10✔
1087
                        && this.left_operand.has_infinity
10!
1088
                )
10✔
1089
                        ? this.left_operand
10!
1090
                        : this.operand_to_IntermediaryNumber(
10✔
1091
                                this.left_operand,
10✔
1092
                        );
10✔
1093

10✔
1094
                const right = (
10✔
1095
                        this.right_operand instanceof IntermediaryCalculation
10✔
1096
                        && this.right_operand.has_infinity
10✔
1097
                )
10✔
1098
                        ? this.right_operand
10!
1099
                        : this.operand_to_IntermediaryNumber(
10✔
1100
                                this.right_operand,
10✔
1101
                        );
10✔
1102

10✔
1103
                const maybe = IntermediaryCalculation.maybe_short_circuit(
10✔
1104
                        left,
10✔
1105
                        this.operation,
10✔
1106
                        right,
10✔
1107
                );
10✔
1108

10✔
1109
                if (maybe) {
10✔
1110
                        return maybe.toJSON();
6✔
1111
                }
6✔
1112

4✔
1113
                return {
4✔
1114
                        type: 'IntermediaryCalculation',
4✔
1115
                        left: left.toJSON(),
4✔
1116
                        operation: this.operation,
4✔
1117
                        right: right.toJSON(),
4✔
1118
                };
4✔
1119
        }
10✔
1120

5✔
1121
        toString(): string {
5✔
1122
                const cache = conversion_cache.String;
626✔
1123

626✔
1124
                if (cache.has(this)) {
626✔
1125
                        return cache.get(this) as string;
2✔
1126
                }
2✔
1127

8✔
1128
                const value = this.resolve().toString();
8✔
1129
                cache.set(this, value);
8✔
1130

8✔
1131
                return value;
8✔
1132
        }
626✔
1133

5✔
1134
        toStringCalculation(): string {
5✔
1135
                return `${
6✔
1136
                        (this.left_operand instanceof IntermediaryCalculation)
6✔
1137
                                ? `(${this.left_operand.toStringCalculation()})`
6✔
1138
                                : this.left_operand.toString()
6✔
1139
                } ${
6✔
1140
                        this.operation
6✔
1141
                } ${
6✔
1142
                        (this.right_operand instanceof IntermediaryCalculation)
6✔
1143
                                ? `(${this.right_operand.toStringCalculation()})`
6✔
1144
                                : this.right_operand.toString()
6✔
1145
                }`;
6✔
1146
        }
6✔
1147

5✔
1148
        private operand_to_IntermediaryNumber(
5✔
1149
                operand: operand_types,
3,918✔
1150
        ): IntermediaryNumber {
3,918✔
1151
                if (
3,918✔
1152
                        (operand instanceof IntermediaryCalculation)
3,918✔
1153
                        || (operand instanceof TokenScan)
3,918✔
1154
                ) {
3,918✔
1155
                        return operand.resolve();
1,060✔
1156
                } else if (
3,918✔
1157
                        'amount_string' === operand.type
2,850✔
1158
                        || 'numeric_string' === operand.type
2,850✔
1159
                ) {
2,850✔
1160
                        return IntermediaryNumber.create(
2,342✔
1161
                                '/' === this.operation
2,342✔
1162
                                        ? operand.toFraction()
2,342✔
1163
                                        : operand.toBigNumberOrFraction(),
2,342✔
1164
                        );
2,342✔
1165
                }
2,342✔
1166

508✔
1167
                return operand;
508✔
1168
        }
3,918✔
1169

5✔
1170
        static fromString(
5✔
1171
                input: Exclude<string, ''>,
52✔
1172
        ): IntermediaryNumber|IntermediaryCalculation {
52✔
1173
                return TokenScan.create(input).parsed;
52✔
1174
        }
52✔
1175

5✔
1176
        static is(maybe: unknown): maybe is IntermediaryCalculation {
5✔
1177
                return maybe instanceof this;
16✔
1178
        }
16✔
1179

5✔
1180
        static maybe_reduce_operands(
5✔
1181
                left: operand_types,
30✔
1182
                operation: operation_types,
30✔
1183
                right: operand_types,
30✔
1184
        ) {
30✔
1185
                let value: operand_types|undefined = this.maybe_short_circuit(
30✔
1186
                        left,
30✔
1187
                        operation,
30✔
1188
                        right,
30✔
1189
                );
30✔
1190

30✔
1191
                if (undefined === value) {
30✔
1192
                        value = new IntermediaryCalculation(left, operation, right);
19✔
1193
                }
19✔
1194

30✔
1195
                if (value instanceof IntermediaryCalculation) {
30✔
1196
                        return value.resolve();
21✔
1197
                }
21✔
1198

3✔
1199
                return value;
3✔
1200
        }
30✔
1201

5✔
1202
        static require_is(
5✔
1203
                maybe: unknown,
6✔
1204
        ): asserts maybe is IntermediaryCalculation {
6✔
1205
                if (!this.is(maybe)) {
6✔
1206
                        throw new Error(
1✔
1207
                                'Argument is not an instanceof IntermediaryCalculation',
1✔
1208
                        );
1✔
1209
                }
1✔
1210
        }
6✔
1211

5✔
1212
        private static maybe_short_circuit(
5✔
1213
                left: operand_types,
2,364✔
1214
                operation: operation_types,
2,364✔
1215
                right: operand_types,
2,364✔
1216
        ): operand_types|undefined {
2,364✔
1217
                let value: operand_types|undefined = undefined;
2,364✔
1218

2,364✔
1219
                if (
2,364✔
1220
                        left instanceof IntermediaryNumberInfinity
2,364✔
1221
                        && right instanceof IntermediaryNumberInfinity
2,364✔
1222
                ) {
2,364✔
1223
                        if (
40✔
1224
                                '+' === operation
40✔
1225
                                || '*' === operation
40✔
1226
                        ) {
40✔
1227
                                // infinity plus or multiplied by infintiy is infinity
40✔
1228
                                return left;
40✔
1229
                        } else if (
40✔
1230
                                '-' === operation
40✔
1231
                        ) {
40✔
1232
                                return IntermediaryNumber.Zero;
40✔
1233
                        } else if (
40✔
1234
                                '/' === operation
40✔
1235
                        ) {
40✔
1236
                                return IntermediaryNumber.One;
40✔
1237
                        }
40✔
1238
                }
40✔
1239

2,190✔
1240
                if (
2,190✔
1241
                        left instanceof IntermediaryCalculation
2,190✔
1242
                        && left.has_infinity
2,364✔
1243
                        && (
2,364✔
1244
                                (
60✔
1245
                                        '+' === left.operation
60✔
1246
                                        && '-' === operation
60✔
1247
                                )
60✔
1248
                                || (
60✔
1249
                                        '-' === left.operation
40✔
1250
                                        && '+' === operation
40✔
1251
                                )
40✔
1252
                        )
60✔
1253
                        && (
2,364✔
1254
                                right instanceof IntermediaryNumberInfinity
40✔
1255
                        )
40✔
1256
                        && (
2,364✔
1257
                                !(left.left_operand instanceof IntermediaryNumberInfinity)
40✔
1258
                                || !(left.right_operand instanceof IntermediaryNumberInfinity)
40✔
1259
                        )
40✔
1260
                ) {
2,364✔
1261
                        return (
40✔
1262
                                left.left_operand instanceof IntermediaryNumberInfinity
40✔
1263
                                        ? left.right_operand
40✔
1264
                                        : left.left_operand
40✔
1265
                        );
40✔
1266
                } else if (
2,364✔
1267
                        right instanceof IntermediaryCalculation
2,150✔
1268
                        && right.has_infinity
2,150✔
1269
                        && (
2,150✔
1270
                                (
20✔
1271
                                        '+' === right.operation
20✔
1272
                                        && '-' === operation
20✔
1273
                                )
20✔
1274
                                || (
20✔
1275
                                        '-' === right.operation
20✔
1276
                                        && '+' === operation
20✔
1277
                                )
20✔
1278
                        )
20✔
1279
                        && (
2,150!
1280
                                left instanceof IntermediaryNumberInfinity
×
1281
                        )
×
1282
                        && (
2,150!
1283
                                !(right.left_operand instanceof IntermediaryNumberInfinity)
×
1284
                                || !(right.right_operand instanceof IntermediaryNumberInfinity)
×
1285
                        )
×
1286
                ) {
2,150!
1287
                        return (
×
1288
                                right.left_operand instanceof IntermediaryNumberInfinity
×
1289
                                        ? right.right_operand
×
1290
                                        : right.left_operand
×
1291
                        );
×
1292
                } else if (
2,150✔
1293
                        '/' === operation
2,150✔
1294
                        && !right.isOne()
2,150✔
1295
                        && left instanceof IntermediaryCalculation
2,150✔
1296
                        && left.has_infinity
2,150✔
1297
                        && !(left.left_operand instanceof IntermediaryNumberInfinity)
2,150✔
1298
                        && '*x'.includes(left.operation)
2,150✔
1299
                        && right instanceof IntermediaryNumberInfinity
2,150✔
1300
                ) {
2,150✔
1301
                        return left.left_operand;
20✔
1302
                }
20✔
1303

2,130✔
1304
                if ('+' === operation) {
2,364✔
1305
                        if (left.isZero()) {
883✔
1306
                                value = right;
4✔
1307
                        } else if (right.isZero()) {
883✔
1308
                                value = left;
4✔
1309
                        }
4✔
1310
                } else if ('-' === operation && right.isZero()) {
2,364✔
1311
                        value = left;
125✔
1312
                } else if ('*x'.includes(operation)) {
1,373✔
1313
                        if (left.isZero() || right.isOne()) {
1,242✔
1314
                                value = left;
163✔
1315
                        } else if (right.isZero() || left.isOne()) {
1,242✔
1316
                                value = right;
89✔
1317
                        }
89✔
1318
                } else if ('/' === operation && right.isOne()) {
1,242✔
1319
                        value = left;
2✔
1320
                }
2✔
1321

2,130✔
1322
                return value;
2,130✔
1323
        }
2,364✔
1324
}
5✔
1325

5✔
1326
// #endregion
5✔
1327

5✔
1328
// #region TokenScan
5✔
1329

5✔
1330
class TokenSpan<T = TokenSpan_types> {
5✔
1331
        readonly from: number;
5✔
1332

13,281✔
1333
        readonly to: number;
13,281✔
1334

13,281✔
1335
        readonly type: T;
5✔
1336

5✔
1337
        constructor(from: number, to: number, type: T) {
5✔
1338
                this.from = from;
13,281✔
1339
                this.to = to;
13,281✔
1340
                this.type = type;
13,281✔
1341
        }
13,281✔
1342
}
5✔
1343

5✔
1344
export class TokenScanError extends Error {
5✔
1345
}
5✔
1346

5✔
1347
export class TokenScanParseError extends Error {
5✔
1348
        readonly current?: TokenSpan<TokenSpan_types>;
5✔
1349

8✔
1350
        readonly scan: TokenScan_parsing_value;
8✔
1351

8✔
1352
        readonly state?: TokenScan_tokenizer;
5✔
1353

5✔
1354
        constructor(
5✔
1355
                message: string,
8✔
1356
                scan: TokenScan_parsing_value,
8✔
1357
                state: TokenScan_tokenizer,
8✔
1358
                current?: TokenSpan<TokenSpan_types>,
8✔
1359
        ) {
8✔
1360
                super(message);
8✔
1361

8✔
1362
                this.scan = scan;
8✔
1363
                this.state = state;
8✔
1364
                this.current = current;
8✔
1365
        }
8✔
1366
}
5✔
1367

5✔
1368
const regex_numeric = (
5✔
1369
        /(?:\d*\.\d*\(\d+\)r?|\d*\.\d*\[\d+\]r?|\d+(?:\.\d+r)?|\.\d+r?)/g
5✔
1370
);
5✔
1371

5✔
1372
export class TokenScan implements CanResolveMathWithDispose {
5✔
1373
        private readonly internal: TokenScan_internals = {
5✔
1374
                parsed: undefined,
936✔
1375
                tokens: undefined,
936✔
1376
                valid: undefined,
936✔
1377
        };
936✔
1378

936✔
1379
        readonly value: string|[TokenScan, operation_types, math_types];
5✔
1380

5✔
1381
        private constructor(
5✔
1382
                value: | string|[TokenScan, operation_types, math_types]) {
936✔
1383
                this.value = value;
936✔
1384
        }
936✔
1385

5✔
1386
        get parsed(): Exclude<TokenScan_internals['parsed'], undefined> {
5✔
1387
                return this.#parse();
1,015✔
1388
        }
1,015✔
1389

5✔
1390
        get resolve_type(): string {
5✔
1391
                return this.parsed.resolve_type;
×
1392
        }
×
1393

5✔
1394
        get tokens(): Exclude<TokenScan_internals['tokens'], undefined> {
5✔
1395
                if (undefined === this.internal.tokens) {
1,077✔
1396
                        this.internal.tokens = TokenScan.determine_tokens_from_scan(this);
883✔
1397
                }
883✔
1398

1,068✔
1399
                return this.internal.tokens;
1,068✔
1400
        }
1,077✔
1401

5✔
1402
        get type(): operand_type_property_types {
5✔
1403
                return 'TokenScan';
×
1404
        }
×
1405

5✔
1406
        get valid(): boolean {
5✔
1407
                if (undefined === this.internal.valid) {
869✔
1408
                        try {
869✔
1409
                                this.#parse();
869✔
1410
                                this.internal.valid = true;
869✔
1411
                        } catch {
869✔
1412
                                this.internal.valid = false;
5✔
1413
                        }
5✔
1414
                }
869✔
1415

869✔
1416
                return this.internal.valid;
869✔
1417
        }
869✔
1418

5✔
1419
        #parse(): Exclude<TokenScan_internals['parsed'], undefined> {
5✔
1420
                if (undefined === this.internal.parsed) {
1,884✔
1421
                        this.internal.parsed = TokenScan.parse_scan(this);
883✔
1422
                }
883✔
1423

1,868✔
1424
                return this.internal.parsed;
1,868✔
1425
        }
1,884✔
1426

5✔
1427
        abs() {
5✔
1428
                return this.parsed.abs();
1✔
1429
        }
1✔
1430

5✔
1431
        compare(value: math_types): 0 | 1 | -1 {
5✔
1432
                return this.parsed.compare(value);
1✔
1433
        }
1✔
1434

5✔
1435
        divide(value: math_types): TokenScan {
5✔
1436
                if (IntermediaryNumber.reuse_or_create(value).isOne()) {
2✔
1437
                        return this;
1✔
1438
                }
1✔
1439

1✔
1440
                return new TokenScan([
1✔
1441
                        this,
1✔
1442
                        '/',
1✔
1443
                        value,
1✔
1444
                ]);
1✔
1445
        }
2✔
1446

5✔
1447
        do_math_then_dispose(
5✔
1448
                operator: CanDoMathWithDispose_operator_types,
1✔
1449
                right_operand: math_types,
1✔
1450
        ): CanDoMath_result_types {
1✔
1451
                const result = this.parsed.do_math_then_dispose(
1✔
1452
                        operator,
1✔
1453
                        right_operand,
1✔
1454
                );
1✔
1455
                this.internal.parsed = undefined;
1✔
1456

1✔
1457
                return result;
1✔
1458
        }
1✔
1459

5✔
1460
        isGreaterThan(value: math_types): boolean {
5✔
1461
                return this.parsed.isGreaterThan(value);
1✔
1462
        }
1✔
1463

5✔
1464
        isLessThan(value: math_types): boolean {
5✔
1465
                return this.parsed.isLessThan(value);
1✔
1466
        }
1✔
1467

5✔
1468
        isOne(): boolean {
5✔
1469
                return (
2✔
1470
                        (
2✔
1471
                                is_string(this.value)
2✔
1472
                                && '1' === this.value.trim()
2✔
1473
                        )
2✔
1474
                        || this.parsed.isOne()
2✔
1475
                );
2✔
1476
        }
2✔
1477

5✔
1478
        isZero(): boolean {
5✔
1479
                return (
3✔
1480
                        (
3✔
1481
                                is_string(this.value)
3✔
1482
                                && '0' === this.value.trim()
3✔
1483
                        )
3✔
1484
                        || this.parsed.isZero()
3✔
1485
                );
3✔
1486
        }
3✔
1487

5✔
1488
        max(first: math_types, ...remaining: math_types[]): operand_types {
5✔
1489
                return this.parsed.max(first, ...remaining);
3✔
1490
        }
3✔
1491

5✔
1492
        min(first: math_types, ...remaining: math_types[]): operand_types {
5✔
1493
                return this.parsed.min(first, ...remaining);
2✔
1494
        }
2✔
1495

5✔
1496
        minus(value: math_types): TokenScan {
5✔
1497
                if (IntermediaryNumber.reuse_or_create(value).isZero()) {
5✔
1498
                        return this;
1✔
1499
                }
1✔
1500

4✔
1501
                return new TokenScan([
4✔
1502
                        this,
4✔
1503
                        '-',
4✔
1504
                        value,
4✔
1505
                ]);
4✔
1506
        }
5✔
1507

5✔
1508
        modulo(value: math_types): TokenScan {
5✔
1509
                return new TokenScan([
1✔
1510
                        this,
1✔
1511
                        '%',
1✔
1512
                        value,
1✔
1513
                ]);
1✔
1514
        }
1✔
1515

5✔
1516
        plus(value: math_types): TokenScan {
5✔
1517
                if (IntermediaryNumber.reuse_or_create(value).isZero()) {
2✔
1518
                        return this;
1✔
1519
                }
1✔
1520

1✔
1521
                return new TokenScan([
1✔
1522
                        this,
1✔
1523
                        '+',
1✔
1524
                        value,
1✔
1525
                ]);
1✔
1526
        }
2✔
1527

5✔
1528
        resolve(): IntermediaryNumber {
5✔
1529
                const parsed = this.parsed;
10✔
1530

10✔
1531
                return IntermediaryCalculation.is(parsed) ? parsed.resolve() : parsed;
10✔
1532
        }
10✔
1533

5✔
1534
        times(value: math_types): TokenScan {
5✔
1535
                if (IntermediaryNumber.reuse_or_create(value).isOne()) {
2✔
1536
                        return this;
1✔
1537
                }
1✔
1538

1✔
1539
                return new TokenScan([
1✔
1540
                        this,
1✔
1541
                        'x',
1✔
1542
                        value,
1✔
1543
                ]);
1✔
1544
        }
2✔
1545

5✔
1546
        toAmountString(): amount_string {
5✔
1547
                return this.parsed.toAmountString();
1✔
1548
        }
1✔
1549

5✔
1550
        toBigNumber(): BigNumber {
5✔
1551
                return this.parsed.toBigNumber();
1✔
1552
        }
1✔
1553

5✔
1554
        toBigNumberOrFraction(): BigNumber | Fraction {
5✔
1555
                return this.parsed.toBigNumberOrFraction();
1✔
1556
        }
1✔
1557

5✔
1558
        toFraction(): Fraction {
5✔
1559
                return this.parsed.toFraction();
1✔
1560
        }
1✔
1561

5✔
1562
        toJSON(): CanConvertTypeJson {
5✔
1563
                return {
13✔
1564
                        type: 'TokenScan',
13✔
1565
                        value: this.toStringCalculation(),
13✔
1566
                };
13✔
1567
        }
13✔
1568

5✔
1569
        toString(): string {
5✔
1570
                return this.parsed.toString();
36✔
1571
        }
36✔
1572

5✔
1573
        toStringCalculation(): string {
5✔
1574
                if (this.value instanceof Array) {
10,950✔
1575
                        const left_operand = this.value[0];
70✔
1576
                        const right_operand = IntermediaryNumber.reuse_or_create(
70✔
1577
                                this.value[2],
70✔
1578
                        );
70✔
1579

70✔
1580
                        return `${
70✔
1581
                                (left_operand.parsed instanceof IntermediaryNumber)
70✔
1582
                                        ? left_operand.toString()
70✔
1583
                                        : `(${
70✔
1584
                                                left_operand.toStringCalculation()
43✔
1585
                                        })`
43✔
1586
                        } ${
70✔
1587
                                this.value[1]
70✔
1588
                        } ${
70✔
1589
                                (right_operand instanceof IntermediaryNumber)
70✔
1590
                                        ? right_operand.toStringCalculation()
70✔
1591
                                        : `(${right_operand.toStringCalculation()})`
70✔
1592
                        }`;
70✔
1593
                }
70✔
1594

10,649✔
1595
                return this.value;
10,649✔
1596
        }
10,950✔
1597

5✔
1598
        static create(value: string): TokenScan {
5✔
1599
                return new TokenScan(value);
928✔
1600
        }
928✔
1601

5✔
1602
        static is(maybe: unknown): maybe is TokenScan {
5✔
1603
                return maybe instanceof TokenScan;
7✔
1604
        }
7✔
1605

5✔
1606
        static require_is(maybe: unknown): asserts maybe is TokenScan {
5✔
1607
                if (!this.is(maybe)) {
7✔
1608
                        throw new Error(
1✔
1609
                                'Argument is not an instanceof TokenScan',
1✔
1610
                        );
1✔
1611
                }
1✔
1612
        }
7✔
1613

5✔
1614
        private static determine_tokens_from_scan(
5✔
1615
                scan: TokenScan_parsing_tokens,
936✔
1616
        ): Exclude<TokenScan_internals['tokens'], undefined> {
936✔
1617
                let tokens: TokenSpan<TokenSpan_types>[] = [];
936✔
1618

936✔
1619
                const value = scan.toStringCalculation();
936✔
1620

936✔
1621
                for (const entry of value.matchAll(/([\s]+)/g)) {
936✔
1622
                        tokens.push(new TokenSpan(
3,523✔
1623
                                entry.index,
3,523✔
1624
                                entry.index + entry[0].length,
3,523✔
1625
                                'ignore',
3,523✔
1626
                        ));
3,523✔
1627
                }
3,523✔
1628

936✔
1629
                for (const entry of value.matchAll(regex_numeric)) {
936✔
1630
                        tokens.push(new TokenSpan(
2,273✔
1631
                                entry.index,
2,273✔
1632
                                entry.index + entry[0].length,
2,273✔
1633
                                'numeric',
2,273✔
1634
                        ));
2,273✔
1635
                }
2,273✔
1636

936✔
1637
                for (const entry of value.matchAll(/([+/*x%-])/g)) {
936✔
1638
                        tokens.push(new TokenSpan(
1,531✔
1639
                                entry.index,
1,531✔
1640
                                entry.index + entry[0].length,
1,531✔
1641
                                'operation',
1,531✔
1642
                        ));
1,531✔
1643
                }
1,531✔
1644

936✔
1645
                for (const entry of value.matchAll(/(\()/g)) {
936✔
1646
                        tokens.push(new TokenSpan(
2,832✔
1647
                                entry.index,
2,832✔
1648
                                entry.index + entry[0].length,
2,832✔
1649
                                'nesting_open',
2,832✔
1650
                        ));
2,832✔
1651
                }
2,832✔
1652

936✔
1653
                for (const entry of value.matchAll(/(\))/g)) {
936✔
1654
                        tokens.push(new TokenSpan(
2,832✔
1655
                                entry.index,
2,832✔
1656
                                entry.index + entry[0].length,
2,832✔
1657
                                'nesting_close',
2,832✔
1658
                        ));
2,832✔
1659
                }
2,832✔
1660

936✔
1661
                for (const entry of value.matchAll(/(\bInfinity\b)/g)) {
936✔
1662
                        tokens.push(new TokenSpan(
224✔
1663
                                entry.index,
224✔
1664
                                entry.index + entry[0].length,
224✔
1665
                                'Infinity',
224✔
1666
                        ));
224✔
1667
                }
224✔
1668

936✔
1669
                tokens = tokens.sort((a, b) => {
936✔
1670
                        return a.from - b.from;
36,933✔
1671
                });
936✔
1672

936✔
1673
                const recursive_numerics = tokens.filter(
936✔
1674
                        (maybe) => (
936✔
1675
                                'numeric' === maybe.type
13,215✔
1676
                                && /[()]/.test(value.substring(maybe.from, maybe.to))
13,215✔
1677
                        ),
936✔
1678
                );
936✔
1679

936✔
1680
                tokens = tokens.filter(
936✔
1681
                        (maybe) => {
936✔
1682
                                if (
13,215✔
1683
                                        'nesting_open' === maybe.type
13,215✔
1684
                                        || 'nesting_close' === maybe.type
13,215✔
1685
                                ) {
13,215✔
1686
                                        return !recursive_numerics.find(
5,664✔
1687
                                                (maybe_numeric) => (
5,664✔
1688
                                                        maybe.from >= maybe_numeric.from
2,474✔
1689
                                                        && maybe.to <= maybe_numeric.to
2,474✔
1690
                                                ),
5,664✔
1691
                                        );
5,664✔
1692
                                }
5,664✔
1693

7,551✔
1694
                                return true;
7,551✔
1695
                        },
936✔
1696
                );
936✔
1697

936✔
1698
                if (tokens.length < 1) {
936✔
1699
                        throw new TokenScanError('No tokens found!');
4✔
1700
                } else if (0 !== tokens[0].from) {
936!
1701
                        throw new TokenScanError('First token not at index 0!');
×
1702
                } else if (value.length !== tokens[tokens.length - 1].to) {
928!
1703
                        throw new TokenScanError(
×
1704
                                'Last token does not end at end of string!',
×
1705
                        );
×
1706
                }
✔
1707

928✔
1708
                let nesting_balance = 0;
928✔
1709

928✔
1710
                for (let index = 0; index < tokens.length; ++index) {
936✔
1711
                        const token = tokens[index];
12,573✔
1712
                        if ('nesting_open' === token.type) {
12,573✔
1713
                                nesting_balance += (token.to - token.from);
2,511✔
1714
                        } else if ('nesting_close' === token.type) {
12,573✔
1715
                                nesting_balance -= (token.to - token.from);
2,511✔
1716
                        }
2,511✔
1717

12,573✔
1718
                        if (
12,573✔
1719
                                index > 0
12,573✔
1720
                                && tokens[index - 1].to !== token.from
12,573✔
1721
                        ) {
12,573!
1722
                                console.error(tokens, index);
×
1723
                                throw new TokenScanError(
×
1724
                                        `Token expected to be found at index ${index}`,
×
1725
                                );
×
1726
                        }
×
1727
                }
12,573✔
1728

928✔
1729
                if (0 !== nesting_balance) {
936!
1730
                        throw new TokenScanError(
×
1731
                                'Imbalanced nesting in string!',
×
1732
                        );
×
1733
                }
✔
1734

928✔
1735
                return this.massage_part_baked_tokens(
928✔
1736
                        scan,
928✔
1737
                        tokens.filter(
928✔
1738
                                (maybe): maybe is TokenSpan<
928✔
1739
                                        TokenSpan_types_part_baked
12,573✔
1740
                                > => 'ignore' !== maybe.type,
928✔
1741
                        ),
928✔
1742
                );
928✔
1743
        }
936✔
1744

5✔
1745
        private static massage_part_baked_tokens(
5✔
1746
                scan: TokenScan_parsing_tokens,
932✔
1747
                tokens: Exclude<TokenScan_internals['tokens'], undefined>,
932✔
1748
        ): Exclude<TokenScan_internals['tokens'], undefined> {
932✔
1749
                const smoosh_numerics: number[] = [];
932✔
1750

932✔
1751
                const value = scan.toStringCalculation();
932✔
1752

932✔
1753
                for (
932✔
1754
                        let token_index = tokens.length - 1; token_index > 0; --token_index
932✔
1755
                ) {
932✔
1756
                        const previous = tokens[token_index - 1];
8,067✔
1757
                        const current = tokens[token_index];
8,067✔
1758

8,067✔
1759
                        if ('numeric' === previous.type) {
8,067✔
1760
                                const previous_value = value.substring(
1,865✔
1761
                                        previous.from,
1,865✔
1762
                                        previous.to,
1,865✔
1763
                                );
1,865✔
1764
                                const current_value = value.substring(
1,865✔
1765
                                        current.from,
1,865✔
1766
                                        current.to,
1,865✔
1767
                                );
1,865✔
1768

1,865✔
1769
                                if (
1,865✔
1770
                                        current_value.startsWith('.')
1,865✔
1771
                                        && /^\d+$/.test(previous_value)
1,865✔
1772
                                ) {
1,865✔
1773
                                        smoosh_numerics.push(token_index);
52✔
1774
                                }
52✔
1775
                        }
1,865✔
1776
                }
8,067✔
1777

932✔
1778
                for (const index of smoosh_numerics) {
932✔
1779
                        tokens.splice(
52✔
1780
                                index - 1,
52✔
1781
                                2,
52✔
1782
                                new TokenSpan(
52✔
1783
                                        tokens[index - 1].from,
52✔
1784
                                        tokens[index].to,
52✔
1785
                                        'numeric',
52✔
1786
                                ),
52✔
1787
                        );
52✔
1788
                }
52✔
1789

932✔
1790
                const convert_to_negative: number[] = [];
932✔
1791

932✔
1792
                if (
932✔
1793
                        tokens.length >= 2
932✔
1794
                        && 'operation' === tokens[0].type
932✔
1795
                        && '-' === value[tokens[0].from]
932✔
1796
                        && 'numeric' === tokens[1].type
932✔
1797
                ) {
932✔
1798
                        convert_to_negative.push(0);
12✔
1799
                }
12✔
1800

932✔
1801
                for (
932✔
1802
                        let token_index = 0; token_index < tokens.length; ++token_index
932✔
1803
                ) {
932✔
1804
                        const token = tokens[token_index];
8,994✔
1805
                        const next = tokens[token_index + 1];
8,994✔
1806
                        const after = tokens[token_index + 2];
8,994✔
1807

8,994✔
1808
                        if (
8,994✔
1809
                                (
8,994✔
1810
                                        'nesting_open' === token.type
8,994✔
1811
                                        || 'operation' === token.type
8,994✔
1812
                                )
8,994✔
1813
                                && next
8,994✔
1814
                                && after
8,994✔
1815
                                && 'operation' === next.type
8,994✔
1816
                                && '-' === value[next.from]
8,994✔
1817
                                && 'numeric' === after.type
8,994✔
1818
                        ) {
8,994✔
1819
                                convert_to_negative.push(token_index + 1);
2✔
1820
                                token_index += 2;
2✔
1821
                                continue;
2✔
1822
                        }
2✔
1823
                }
8,994✔
1824

932✔
1825
                for (const index of convert_to_negative.reverse()) {
932✔
1826
                        tokens.splice(
14✔
1827
                                index,
14✔
1828
                                2,
14✔
1829
                                new TokenSpan(
14✔
1830
                                        tokens[index].from,
14✔
1831
                                        tokens[index + 1].to,
14✔
1832
                                        'numeric',
14✔
1833
                                ),
14✔
1834
                        );
14✔
1835
                }
14✔
1836

932✔
1837
                return tokens;
932✔
1838
        }
932✔
1839

5✔
1840
        private static parse_scan(
5✔
1841
                scan: TokenScan_parsing_value,
940✔
1842
        ): IntermediaryNumber|IntermediaryCalculation {
940✔
1843
                const reduced = scan.tokens.reduce(
940✔
1844
                        (
940✔
1845
                                was: TokenScan_tokenizer,
8,994✔
1846
                                is: TokenSpan<TokenSpan_types_part_baked>,
8,994✔
1847
                                index: number,
8,994✔
1848
                        ) => TokenScan.reduce(
8,994✔
1849
                                scan,
8,994✔
1850
                                was,
8,994✔
1851
                                is,
8,994✔
1852
                                index,
8,994✔
1853
                        ),
940✔
1854
                        default_tokenizer_state(),
940✔
1855
                );
940✔
1856

940✔
1857
                if (
940✔
1858
                        undefined !== reduced.left_operand
940✔
1859
                        && '' === reduced.operation
940✔
1860
                        && undefined === reduced.right_operand
940✔
1861
                        && 0 === reduced.outter_stack.length
940✔
1862
                ) {
940✔
1863
                        return reduced.left_operand;
924✔
1864
                }
924!
1865

×
1866
                throw new TokenScanParseError(
×
1867
                        'Parse in unsupported state!',
×
1868
                        scan,
×
1869
                        reduced,
×
1870
                );
×
1871
        }
940✔
1872

5✔
1873
        private static reduce(
5✔
1874
                scan: TokenScan_parsing_value,
8,994✔
1875
                was: TokenScan_tokenizer,
8,994✔
1876
                is: TokenSpan<TokenSpan_types_part_baked>,
8,994✔
1877
                index: number,
8,994✔
1878
        ): TokenScan_tokenizer {
8,994✔
1879
                const value = scan.toStringCalculation();
8,994✔
1880

8,994✔
1881
                if (is_nesting_open(is)) {
8,994✔
1882
                        if ('right' === was.operand_mode) {
2,515✔
1883
                                if (undefined === was.left_operand) {
227✔
1884
                                        if (
227✔
1885
                                                !(
227✔
1886
                                                        was.outter_stack.length > 0
227✔
1887
                                                        && !(
227✔
1888
                                                                was.outter_stack[
227✔
1889
                                                                        was.outter_stack.length - 1
227✔
1890
                                                                ] instanceof TokenSpan
227✔
1891
                                                        )
227✔
1892
                                                )
227✔
1893
                                        ) {
227✔
1894
                                                throw new TokenScanParseError(
227✔
1895
                                                        // eslint-disable-next-line @stylistic/max-len
227✔
1896
                                                        'Nesting opened without left operand to push into stack!',
227✔
1897
                                                        scan,
227✔
1898
                                                        was,
227✔
1899
                                                );
227✔
1900
                                        }
227✔
1901

227✔
1902
                                        return was;
227✔
1903
                                } else if ('' === was.operation) {
227✔
1904
                                        throw new TokenScanParseError(
227✔
1905
                                                'Nesting opened without operation to push into stack!',
227✔
1906
                                                scan,
227✔
1907
                                                was,
227✔
1908
                                                is,
227✔
1909
                                        );
227✔
1910
                                }
227✔
1911

227✔
1912
                                was.outter_stack.push({
227✔
1913
                                        left_operand: was.left_operand,
227✔
1914
                                        operation: was.operation,
227✔
1915
                                });
227✔
1916
                                was.left_operand = undefined;
227✔
1917
                                was.operation = '';
227✔
1918
                                was.operand_mode = 'left';
227✔
1919
                        } else {
2,515✔
1920
                                was.outter_stack.push(is);
2,287✔
1921
                        }
2,287✔
1922
                } else if (is_nesting_close(is)) {
8,994✔
1923
                        const popped = was.outter_stack.pop();
2,515✔
1924

2,515✔
1925
                        if (popped instanceof TokenSpan) {
2,515✔
1926
                                if (
2,287✔
1927
                                        'nesting_open' === popped.type
2,287✔
1928
                                        && '' === was.operation
2,287✔
1929
                                        && undefined !== was.left_operand
2,287✔
1930
                                        && undefined === was.right_operand
2,287✔
1931
                                ) {
2,287✔
1932
                                        // no-op, deliberately do nothing
2,281✔
1933
                                } else {
2,287✔
1934
                                        throw new TokenScanParseError(
6✔
1935
                                                // eslint-disable-next-line @stylistic/max-len
6✔
1936
                                                'token span popping in this context not yet implemented',
6✔
1937
                                                scan,
6✔
1938
                                                was,
6✔
1939
                                                is,
6✔
1940
                                        );
6✔
1941
                                }
6✔
1942
                        } else if (undefined === popped) {
2,515✔
1943
                                if (
136✔
1944
                                        index !== (scan.tokens.length - 1)
136✔
1945
                                        && (
136✔
1946
                                                '' !== was.operation
13✔
1947
                                                || undefined !== was.right_operand
13✔
1948
                                        )
13✔
1949
                                ) {
136✔
1950
                                        throw new TokenScanParseError(
136✔
1951
                                                'Token scan finished with incomplete parse!',
136✔
1952
                                                scan,
136✔
1953
                                                was,
136✔
1954
                                                is,
136✔
1955
                                        );
136✔
1956
                                }
136✔
1957
                        } else {
227✔
1958
                                if (
91✔
1959
                                        '' === was.operation
91✔
1960
                                        && undefined !== was.left_operand
91✔
1961
                                        && undefined === was.right_operand
91✔
1962
                                ) {
91✔
1963
                                        was.left_operand = new IntermediaryCalculation(
89✔
1964
                                                popped.left_operand,
89✔
1965
                                                popped.operation,
89✔
1966
                                                was.left_operand,
89✔
1967
                                        );
89✔
1968
                                        was.operation = '';
89✔
1969
                                        was.operand_mode = 'right';
89✔
1970
                                } else {
91✔
1971
                                        throw new TokenScanParseError(
2✔
1972
                                                // eslint-disable-next-line @stylistic/max-len
2✔
1973
                                                'token span popping in this context not yet implemented',
2✔
1974
                                                scan,
2✔
1975
                                                was,
2✔
1976
                                                is,
2✔
1977
                                        );
2✔
1978
                                }
2✔
1979
                        }
91✔
1980
                } else if (is_numeric(is) || is_infinity(is)) {
6,378✔
1981
                        if ('left' === was.operand_mode) {
2,446✔
1982
                                was.left_operand = IntermediaryNumber.create(
1,151✔
1983
                                        value.substring(
1,151✔
1984
                                                is.from,
1,151✔
1985
                                                is.to,
1,151✔
1986
                                        ),
1,151✔
1987
                                );
1,151✔
1988
                                was.operand_mode = 'right';
1,151✔
1989
                        } else {
5✔
1990
                                if ('' === was.operation) {
1,285!
1991
                                        throw new TokenScanParseError(
×
1992
                                                'Right operand detected without operation!',
×
1993
                                                scan,
×
1994
                                                was,
×
1995
                                                is,
×
1996
                                        );
×
1997
                                } else if (undefined === was.left_operand) {
1,285!
1998
                                        throw new TokenScanParseError(
×
1999
                                                'Right operand detected without left operand!',
×
2000
                                                scan,
×
2001
                                                was,
×
2002
                                                is,
×
2003
                                        );
×
2004
                                }
×
2005

1,285✔
2006
                                let resolved = new IntermediaryCalculation(
1,285✔
2007
                                        was.left_operand,
1,285✔
2008
                                        was.operation,
1,285✔
2009
                                        IntermediaryNumber.create(value.substring(
1,285✔
2010
                                                is.from,
1,285✔
2011
                                                is.to,
1,285✔
2012
                                        )),
1,285✔
2013
                                );
1,285✔
2014

1,285✔
2015
                                if (
1,285✔
2016
                                        was.outter_stack.length > 0
1,285✔
2017
                                        && !(
1,285✔
2018
                                                was.outter_stack[
1,285✔
2019
                                                        was.outter_stack.length - 1
1,285✔
2020
                                                ] instanceof TokenSpan
1,285✔
2021
                                        )
1,285✔
2022
                                ) {
1,285✔
2023
                                        const previous = (
1,285✔
2024
                                                was.outter_stack.pop()
1,285✔
2025
                                        ) as incomplete_operation;
1,285✔
2026

1,285✔
2027
                                        resolved = new IntermediaryCalculation(
1,285✔
2028
                                                previous.left_operand,
1,285✔
2029
                                                previous.operation,
1,285✔
2030
                                                resolved,
1,285✔
2031
                                        );
1,285✔
2032
                                }
1,285✔
2033

1,285✔
2034
                                was.left_operand = resolved;
1,285✔
2035
                                was.operation = '';
1,285✔
2036
                                was.right_operand = undefined;
1,285✔
2037
                        }
1,285✔
2038
                } else if ('operation' === is.type) {
3,863✔
2039
                        if (undefined === was.left_operand) {
1,518!
2040
                                throw new TokenScanParseError(
×
2041
                                        'Operation detected without left operand!',
×
2042
                                        scan,
×
2043
                                        was,
×
2044
                                        is,
×
2045
                                );
×
2046
                        } else if ('' !== was.operation) {
1,518!
2047
                                throw new TokenScanParseError(
×
2048
                                        `Cannot set operation when operation already set to "${
×
2049
                                                was.operation
×
2050
                                        }"`,
×
2051
                                        scan,
×
2052
                                        was,
×
2053
                                        is,
×
2054
                                );
×
2055
                        }
×
2056
                        const maybe = value.substring(is.from, is.to);
1,518✔
2057
                        is_operation_value(maybe);
1,518✔
2058

1,518✔
2059
                        was.operation = maybe;
1,518✔
2060
                } else {
1,518!
2061
                        throw new TokenScanParseError(
×
2062
                                'not implemented',
×
2063
                                scan,
×
2064
                                was,
×
2065
                                is,
×
2066
                        );
×
2067
                }
✔
2068

8,867✔
2069
                return was;
8,867✔
2070
        }
8,994✔
2071
}
5✔
2072

5✔
2073
// #endregion
5✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc