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

satisfactory-dev / Intermediary-Number / 20411837041

21 Dec 2025 03:19PM UTC coverage: 95.286% (-0.03%) from 95.313%
20411837041

push

github

SignpostMarv
running prettier

467 of 492 branches covered (94.92%)

Branch coverage included in aggregate %.

2262 of 2372 relevant lines covered (95.36%)

1094.49 hits per line

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

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

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

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

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

6✔
22
// #region Types
6✔
23

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

6✔
135
// #endregion
6✔
136

6✔
137
// #endregion
6✔
138

6✔
139
// #region interfaces
6✔
140

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

6✔
244
// #endregion
6✔
245

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

6✔
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

6✔
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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

6✔
344
        get Fraction(): WeakMap<CanConvertType, Fraction> {
6✔
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

6✔
352
        get String(): WeakMap<CanConvertType, string> {
6✔
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

6✔
360
        dispose(of: CanConvertType) {
6✔
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
}();
6✔
373

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

6✔
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

6✔
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

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

6✔
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

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

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

6✔
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

6✔
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

6✔
448
export function is_operation_value(
6✔
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

6✔
463
// #endregion
6✔
464

6✔
465
// #endregion
6✔
466

6✔
467
// #region IntermediaryNumber
6✔
468

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

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

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

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

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

6✔
484
        get type(): type_property_types {
6✔
485
                if (this.value instanceof BigNumber) {
15,806✔
486
                        return 'BigNumber';
8,170✔
487
                } else if (this.value instanceof Fraction) {
15,806✔
488
                        return 'Fraction';
2,580✔
489
                } else if (NumberStrings.is_amount_string(this.value)) {
7,636✔
490
                        return 'amount_string';
5,029✔
491
                }
5,029✔
492

27✔
493
                return 'numeric_string';
27✔
494
        }
15,806✔
495

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

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

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

6✔
508
        do_math_then_dispose(
6✔
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

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

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

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

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

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

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

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

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

6✔
559
        plus(value: math_types) {
6✔
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

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

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

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

6✔
579
        toBigNumber(): BigNumber {
6✔
580
                if (this.value instanceof BigNumber) {
15,232✔
581
                        return this.value;
8,319✔
582
                } else if (this.value instanceof Fraction) {
15,232✔
583
                        return BigNumber(this.value.valueOf());
892✔
584
                }
892✔
585

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

6,021✔
588
                if (cache.has(this)) {
15,198✔
589
                        return cache.get(this) as BigNumber;
4,447✔
590
                }
4,447✔
591

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

1,574✔
595
                return value;
1,574✔
596
        }
15,232✔
597

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

6✔
604
        toFraction(): Fraction {
6✔
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

6✔
621
        toJSON(): CanConvertTypeJson {
6✔
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 {
5✔
629
                                type: 'IntermediaryNumber',
5✔
630
                                value: '0',
5✔
631
                        };
5✔
632
                }
5✔
633

45✔
634
                if (this.value instanceof Fraction) {
64✔
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) {
64✔
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

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

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

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

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

12,360✔
688
                if (input instanceof Fraction) {
12,362✔
689
                        return new this(input.simplify(1 / (2 ** 52)));
1,467✔
690
                }
1,467✔
691
                if (
10,893✔
692
                        input instanceof BigNumber
10,893✔
693
                        || NumberStrings.is_numeric_string(input)
12,352✔
694
                ) {
12,362✔
695
                        return new this(input);
4,571✔
696
                } else if ('number' === typeof input) {
12,322✔
697
                        return new this(BigNumber(input));
5,568✔
698
                } else if (is_string(input) && regex_recurring_number.test(input)) {
6,322✔
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'));
224✔
717
                }
224✔
718

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

6✔
722
        static create_if_valid(
6✔
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

6✔
759
        static fromJson(json: CanConvertTypeJson): CanDoMath_result_types {
6✔
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);
4✔
764
                }
4✔
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

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

6✔
790
// #endregion
6✔
791

6✔
792
// #region IntermediaryNumberInfinity
6✔
793

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

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

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

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

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

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

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

6✔
820
// #endregion
6✔
821

6✔
822
// #region IntermediaryCalculation
6✔
823

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

2✔
827
        readonly value: string;
6✔
828

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

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

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

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

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

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

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

6✔
868
        constructor(
6✔
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

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

6✔
885
        get left_type(): operand_type_property_types {
6✔
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

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

6✔
897
        get right_type(): operand_type_property_types {
6✔
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

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

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

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

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

6✔
921
        do_math_then_dispose(
6✔
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

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

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

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

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

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

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

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

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

6✔
972
        plus(value: math_types) {
6✔
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

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

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

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

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

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

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

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

6✔
1041
        toAmountString(): amount_string {
6✔
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

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

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

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

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

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

588✔
1064
                return value;
588✔
1065
        }
1,066✔
1066

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

6✔
1071
        toFraction(): Fraction {
6✔
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

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

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

6✔
1084
        toJSON(): CanConvertTypeJson {
6✔
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

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

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

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

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

6✔
1134
        toStringCalculation(): string {
6✔
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

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

516✔
1167
                return operand;
516✔
1168
        }
3,922✔
1169

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

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

6✔
1180
        static maybe_reduce_operands(
6✔
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);
25✔
1193
                }
25✔
1194

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

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

6✔
1202
        static require_is(
6✔
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

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

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

2,326✔
1240
                if (
2,326✔
1241
                        left instanceof IntermediaryCalculation
2,326✔
1242
                        && left.has_infinity
2,366✔
1243
                        && (
2,366✔
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,366✔
1254
                                right instanceof IntermediaryNumberInfinity
40✔
1255
                        )
40✔
1256
                        && (
2,366✔
1257
                                !(left.left_operand instanceof IntermediaryNumberInfinity)
40✔
1258
                                || !(left.right_operand instanceof IntermediaryNumberInfinity)
40✔
1259
                        )
40✔
1260
                ) {
2,366✔
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,366✔
1267
                        right instanceof IntermediaryCalculation
2,286✔
1268
                        && right.has_infinity
2,286✔
1269
                        && (
2,286✔
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,286!
1280
                                left instanceof IntermediaryNumberInfinity
×
1281
                        )
×
1282
                        && (
2,286!
1283
                                !(right.left_operand instanceof IntermediaryNumberInfinity)
×
1284
                                || !(right.right_operand instanceof IntermediaryNumberInfinity)
×
1285
                        )
×
1286
                ) {
2,286!
1287
                        return (
×
1288
                                right.left_operand instanceof IntermediaryNumberInfinity
×
1289
                                        ? right.right_operand
×
1290
                                        : right.left_operand
×
1291
                        );
×
1292
                } else if (
2,286✔
1293
                        '/' === operation
2,286✔
1294
                        && !right.isOne()
2,286✔
1295
                        && left instanceof IntermediaryCalculation
2,286✔
1296
                        && left.has_infinity
2,286✔
1297
                        && !(left.left_operand instanceof IntermediaryNumberInfinity)
2,286✔
1298
                        && '*x'.includes(left.operation)
2,286✔
1299
                        && right instanceof IntermediaryNumberInfinity
2,286✔
1300
                ) {
2,286✔
1301
                        return left.left_operand;
20✔
1302
                }
20✔
1303

2,266✔
1304
                if ('+' === operation) {
2,366✔
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,366✔
1311
                        value = left;
125✔
1312
                } else if ('*x'.includes(operation)) {
1,383✔
1313
                        if (left.isZero() || right.isOne()) {
774✔
1314
                                value = left;
163✔
1315
                        } else if (right.isZero() || left.isOne()) {
774✔
1316
                                value = right;
89✔
1317
                        }
89✔
1318
                } else if ('/' === operation && right.isOne()) {
1,258✔
1319
                        value = left;
2✔
1320
                }
2✔
1321

2,266✔
1322
                return value;
2,266✔
1323
        }
2,366✔
1324
}
6✔
1325

6✔
1326
// #endregion
6✔
1327

6✔
1328
// #region TokenScan
6✔
1329

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

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

13,857✔
1335
        readonly type: T;
6✔
1336

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

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

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

8✔
1350
        readonly scan: TokenScan_parsing_value;
8✔
1351

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

6✔
1354
        constructor(
6✔
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
}
6✔
1367

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

6✔
1372
export class TokenScan implements CanResolveMathWithDispose {
6✔
1373
        private readonly internal: TokenScan_internals = {
6✔
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];
6✔
1380

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

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

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

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

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

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

6✔
1406
        get valid(): boolean {
6✔
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

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

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

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

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

6✔
1435
        divide(value: math_types): TokenScan {
6✔
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

6✔
1447
        do_math_then_dispose(
6✔
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

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

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

6✔
1468
        isOne(): boolean {
6✔
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

6✔
1478
        isZero(): boolean {
6✔
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

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

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

6✔
1496
        minus(value: math_types): TokenScan {
6✔
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

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

6✔
1516
        plus(value: math_types): TokenScan {
6✔
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

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

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

6✔
1534
        times(value: math_types): TokenScan {
6✔
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

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

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

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

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

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

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

6✔
1573
        toStringCalculation(): string {
6✔
1574
                if (this.value instanceof Array) {
11,526✔
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

11,456✔
1595
                return this.value;
11,456✔
1596
        }
11,526✔
1597

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

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

6✔
1606
        static require_is(maybe: unknown): asserts maybe is TokenScan {
6✔
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

6✔
1614
        private static determine_tokens_from_scan(
6✔
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(
3,120✔
1647
                                entry.index,
3,120✔
1648
                                entry.index + entry[0].length,
3,120✔
1649
                                'nesting_open',
3,120✔
1650
                        ));
3,120✔
1651
                }
3,120✔
1652

936✔
1653
                for (const entry of value.matchAll(/(\))/g)) {
936✔
1654
                        tokens.push(new TokenSpan(
3,120✔
1655
                                entry.index,
3,120✔
1656
                                entry.index + entry[0].length,
3,120✔
1657
                                'nesting_close',
3,120✔
1658
                        ));
3,120✔
1659
                }
3,120✔
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;
39,727✔
1671
                });
936✔
1672

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

936✔
1680
                tokens = tokens.filter(
936✔
1681
                        (maybe) => {
936✔
1682
                                if (
13,791✔
1683
                                        'nesting_open' === maybe.type
13,791✔
1684
                                        || 'nesting_close' === maybe.type
13,791✔
1685
                                ) {
13,791✔
1686
                                        return !recursive_numerics.find(
6,240✔
1687
                                                (maybe_numeric) => (
6,240✔
1688
                                                        maybe.from >= maybe_numeric.from
2,642✔
1689
                                                        && maybe.to <= maybe_numeric.to
2,642✔
1690
                                                ),
6,240✔
1691
                                        );
6,240✔
1692
                                }
6,240✔
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) {
932!
1703
                        throw new TokenScanError(
×
1704
                                'Last token does not end at end of string!',
×
1705
                        );
×
1706
                }
×
1707

932✔
1708
                let nesting_balance = 0;
932✔
1709

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

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

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

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

6✔
1745
        private static massage_part_baked_tokens(
6✔
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,694✔
1757
                        const current = tokens[token_index];
8,694✔
1758

8,694✔
1759
                        if ('numeric' === previous.type) {
8,694✔
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,694✔
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];
9,570✔
1805
                        const next = tokens[token_index + 1];
9,570✔
1806
                        const after = tokens[token_index + 2];
9,570✔
1807

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

6✔
1840
        private static parse_scan(
6✔
1841
                scan: TokenScan_parsing_value,
940✔
1842
        ): IntermediaryNumber|IntermediaryCalculation {
940✔
1843
                const reduced = scan.tokens.reduce(
940✔
1844
                        (
940✔
1845
                                was: TokenScan_tokenizer,
9,570✔
1846
                                is: TokenSpan<TokenSpan_types_part_baked>,
9,570✔
1847
                                index: number,
9,570✔
1848
                        ) => TokenScan.reduce(
9,570✔
1849
                                scan,
9,570✔
1850
                                was,
9,570✔
1851
                                is,
9,570✔
1852
                                index,
9,570✔
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;
928✔
1864
                }
928✔
1865

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

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

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

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

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

2,803✔
1925
                        if (popped instanceof TokenSpan) {
2,803✔
1926
                                if (
2,575✔
1927
                                        'nesting_open' === popped.type
2,575✔
1928
                                        && '' === was.operation
2,575✔
1929
                                        && undefined !== was.left_operand
2,575✔
1930
                                        && undefined === was.right_operand
2,575✔
1931
                                ) {
2,575✔
1932
                                        // no-op, deliberately do nothing
2,569✔
1933
                                } else {
2,575✔
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,803✔
1943
                                if (
137✔
1944
                                        index !== (scan.tokens.length - 1)
137✔
1945
                                        && (
137✔
1946
                                                '' !== was.operation
13✔
1947
                                                || undefined !== was.right_operand
13✔
1948
                                        )
13✔
1949
                                ) {
137!
1950
                                        throw new TokenScanParseError(
×
1951
                                                'Token scan finished with incomplete parse!',
×
1952
                                                scan,
×
1953
                                                was,
×
1954
                                                is,
×
1955
                                        );
×
1956
                                }
×
1957
                        } else {
228✔
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,767✔
1981
                        if ('left' === was.operand_mode) {
2,446✔
1982
                                was.left_operand = IntermediaryNumber.create(
1,156✔
1983
                                        value.substring(
1,156✔
1984
                                                is.from,
1,156✔
1985
                                                is.to,
1,156✔
1986
                                        ),
1,156✔
1987
                                );
1,156✔
1988
                                was.operand_mode = 'right';
1,156✔
1989
                        } else {
2,441✔
1990
                                if ('' === was.operation) {
1,290!
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,290!
1998
                                        throw new TokenScanParseError(
×
1999
                                                'Right operand detected without left operand!',
×
2000
                                                scan,
×
2001
                                                was,
×
2002
                                                is,
×
2003
                                        );
×
2004
                                }
×
2005

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

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

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

1,290✔
2034
                                was.left_operand = resolved;
1,290✔
2035
                                was.operation = '';
1,290✔
2036
                                was.right_operand = undefined;
1,290✔
2037
                        }
1,290✔
2038
                } else if ('operation' === is.type) {
3,964✔
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

9,562✔
2069
                return was;
9,562✔
2070
        }
9,570✔
2071
}
6✔
2072

6✔
2073
// #endregion
6✔
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