@signpostmarv/intermediary-number 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './lib/IntermediaryNumber';
2
+ export * from './lib/IntermediaryNumberTypes';
3
+ export * from './lib/Numbers';
4
+ export * from './lib/NumberStrings';
package/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from './lib/IntermediaryNumber';
2
+ export * from './lib/IntermediaryNumberTypes';
3
+ export * from './lib/Numbers';
4
+ export * from './lib/NumberStrings';
package/lib/Docs.json.js CHANGED
@@ -4,4 +4,3 @@ export class NotAnAmountString extends Error {
4
4
  export function is_string(maybe) {
5
5
  return 'string' === typeof maybe;
6
6
  }
7
- //# sourceMappingURL=Docs.json.js.map
@@ -4,7 +4,43 @@ import { amount_string } from './NumberStrings';
4
4
  import type { input_types, type_property_types, value_types } from './IntermediaryNumberTypes';
5
5
  export type math_types = operand_types | input_types;
6
6
  export declare const regex_recurring_number: RegExp;
7
- export type CanDoMath_result_types = IntermediaryNumber | IntermediaryCalculation;
7
+ export type CanDoMath_result_types = IntermediaryNumber | IntermediaryCalculation | TokenScan;
8
+ export type operation_types = '+' | '-' | '*' | 'x' | '/' | '%';
9
+ export type operand_type_property_types = type_property_types | 'IntermediaryCalculation' | 'TokenScan';
10
+ export type CanConvertTypeJson = {
11
+ type: 'IntermediaryNumber';
12
+ value: string;
13
+ } | {
14
+ type: 'IntermediaryCalculation';
15
+ left: CanConvertTypeJson;
16
+ operation: operation_types;
17
+ right: CanConvertTypeJson;
18
+ } | {
19
+ type: 'TokenScan';
20
+ value: string;
21
+ };
22
+ export type CanDoMathWithDispose_operator_types = 'divide' | 'minus' | 'modulo' | 'plus' | 'times';
23
+ export type operand_types = IntermediaryNumber | IntermediaryCalculation | TokenScan;
24
+ type TokenSpan_types = 'ignore' | 'nesting_open' | 'nesting_close' | 'numeric' | 'operation';
25
+ type TokenSpan_types_part_baked = Exclude<TokenSpan_types, 'ignore'>;
26
+ type TokenScan_internals = {
27
+ parsed: IntermediaryNumber | IntermediaryCalculation | undefined;
28
+ tokens: (TokenSpan<TokenSpan_types_part_baked>[]) | undefined;
29
+ valid: boolean | undefined;
30
+ };
31
+ type TokenScan_parsing_value = Omit<TokenScan, 'is_valid' | 'parsed'>;
32
+ type TokenScan_tokenizer_operand_buffer = IntermediaryNumber | IntermediaryCalculation | undefined;
33
+ type incomplete_operation = {
34
+ left_operand: Exclude<TokenScan_tokenizer_operand_buffer, undefined>;
35
+ operation: operation_types;
36
+ };
37
+ type TokenScan_tokenizer = {
38
+ outter_stack: (incomplete_operation | TokenSpan<'nesting_open'>)[];
39
+ left_operand: TokenScan_tokenizer_operand_buffer;
40
+ right_operand: TokenScan_tokenizer_operand_buffer;
41
+ operation: '' | operation_types;
42
+ operand_mode: 'left' | 'right';
43
+ };
8
44
  interface HasType {
9
45
  get type(): operand_type_property_types;
10
46
  }
@@ -16,21 +52,12 @@ interface CanDoMath<ResultType extends CanDoMath_result_types = CanDoMath_result
16
52
  modulo(value: math_types): ResultType;
17
53
  plus(value: math_types): ResultType;
18
54
  times(value: math_types): ResultType;
19
- abs(): (IntermediaryCalculation | IntermediaryNumber);
55
+ abs(): (operand_types);
20
56
  max(first: math_types, ...remaining: math_types[]): math_types;
21
57
  }
22
58
  interface CanResolveMath<T extends CanDoMath_result_types = CanDoMath_result_types> extends CanDoMath<T, string> {
23
59
  resolve(): IntermediaryNumber;
24
60
  }
25
- export type CanConvertTypeJson = {
26
- type: 'IntermediaryNumber';
27
- value: string;
28
- } | {
29
- type: 'IntermediaryCalculation';
30
- left: CanConvertTypeJson;
31
- operation: operation_types;
32
- right: CanConvertTypeJson;
33
- };
34
61
  export interface CanConvertType extends HasType {
35
62
  toAmountString(): amount_string;
36
63
  toBigNumber(): BigNumber;
@@ -43,7 +70,6 @@ export interface CanConvertType extends HasType {
43
70
  isZero(): boolean;
44
71
  toJSON(): CanConvertTypeJson;
45
72
  }
46
- type CanDoMathWithDispose_operator_types = 'divide' | 'minus' | 'modulo' | 'plus' | 'times';
47
73
  interface CanDoMathWithDispose<ResultType extends CanDoMath_result_types = CanDoMath_result_types, ResolveString extends string = type_property_types> extends CanConvertType, CanDoMath<ResultType, ResolveString> {
48
74
  do_math_then_dispose(operator: CanDoMathWithDispose_operator_types, right_operand: math_types): ResultType;
49
75
  }
@@ -76,6 +102,7 @@ export declare class IntermediaryNumber implements CanDoMathWithDispose {
76
102
  toFraction(): Fraction;
77
103
  toJSON(): CanConvertTypeJson;
78
104
  toString(): string;
105
+ toStringCalculation(): string;
79
106
  static create(input: input_types): IntermediaryNumber;
80
107
  static create_if_valid(input: string): operand_types | NotValid;
81
108
  static fromJson(json: CanConvertTypeJson): CanDoMath_result_types;
@@ -86,9 +113,6 @@ export declare class NotValid extends Error {
86
113
  readonly value: string;
87
114
  constructor(not_valid: string, reason: unknown);
88
115
  }
89
- export type operand_types = IntermediaryNumber | IntermediaryCalculation;
90
- export type operation_types = '+' | '-' | '*' | 'x' | '/' | '%';
91
- export type operand_type_property_types = type_property_types | 'IntermediaryCalculation';
92
116
  export declare class IntermediaryCalculation implements CanResolveMathWithDispose {
93
117
  readonly left_operand: operand_types;
94
118
  readonly operation: operation_types;
@@ -118,9 +142,64 @@ export declare class IntermediaryCalculation implements CanResolveMathWithDispos
118
142
  toFraction(): Fraction;
119
143
  toJSON(): CanConvertTypeJson;
120
144
  toString(): string;
145
+ toStringCalculation(): string;
121
146
  private operand_to_IntermediaryNumber;
122
147
  static fromString(input: Exclude<string, ''>): IntermediaryNumber | IntermediaryCalculation;
123
- static maybe_reduce_operands(left: operand_types, operation: operation_types, right: operand_types): IntermediaryNumber;
148
+ static is(maybe: unknown): maybe is IntermediaryCalculation;
149
+ static maybe_reduce_operands(left: operand_types, operation: operation_types, right: operand_types): IntermediaryNumber | TokenScan;
150
+ static require_is(maybe: unknown): asserts maybe is IntermediaryCalculation;
124
151
  private static maybe_short_circuit;
125
152
  }
153
+ declare class TokenSpan<T = TokenSpan_types> {
154
+ readonly from: number;
155
+ readonly to: number;
156
+ readonly type: T;
157
+ constructor(from: number, to: number, type: T);
158
+ }
159
+ export declare class TokenScanError extends Error {
160
+ }
161
+ export declare class TokenScanParseError extends Error {
162
+ readonly current?: TokenSpan<TokenSpan_types>;
163
+ readonly scan: TokenScan_parsing_value;
164
+ readonly state?: TokenScan_tokenizer;
165
+ constructor(message: string, scan: TokenScan_parsing_value, state: TokenScan_tokenizer, current?: TokenSpan<TokenSpan_types>);
166
+ }
167
+ export declare class TokenScan implements CanResolveMathWithDispose {
168
+ private readonly internal;
169
+ readonly value: string | [TokenScan, operation_types, math_types];
170
+ private constructor();
171
+ get parsed(): Exclude<TokenScan_internals['parsed'], undefined>;
172
+ get resolve_type(): string;
173
+ get tokens(): Exclude<TokenScan_internals['tokens'], undefined>;
174
+ get type(): operand_type_property_types;
175
+ get valid(): boolean;
176
+ abs(): operand_types;
177
+ compare(value: math_types): 0 | 1 | -1;
178
+ divide(value: math_types): TokenScan;
179
+ do_math_then_dispose(operator: CanDoMathWithDispose_operator_types, right_operand: math_types): CanDoMath_result_types;
180
+ isGreaterThan(value: math_types): boolean;
181
+ isLessThan(value: math_types): boolean;
182
+ isOne(): boolean;
183
+ isZero(): boolean;
184
+ max(first: math_types, ...remaining: math_types[]): math_types;
185
+ minus(value: math_types): TokenScan;
186
+ modulo(value: math_types): TokenScan;
187
+ plus(value: math_types): TokenScan;
188
+ resolve(): IntermediaryNumber;
189
+ times(value: math_types): TokenScan;
190
+ toAmountString(): amount_string;
191
+ toBigNumber(): BigNumber;
192
+ toBigNumberOrFraction(): BigNumber | Fraction;
193
+ toFraction(): Fraction;
194
+ toJSON(): CanConvertTypeJson;
195
+ toString(): string;
196
+ toStringCalculation(): string;
197
+ static create(value: string): TokenScan;
198
+ static is(maybe: unknown): maybe is TokenScan;
199
+ static require_is(maybe: unknown): asserts maybe is TokenScan;
200
+ private static determine_tokens_from_scan;
201
+ private static massage_part_baked_tokens;
202
+ private static parse_scan;
203
+ private static reduce;
204
+ }
126
205
  export {};
@@ -3,8 +3,9 @@ import BigNumber from 'bignumber.js';
3
3
  import Fraction from 'fraction.js';
4
4
  import { is_string, } from './Docs.json';
5
5
  import { NumberStrings, } from './NumberStrings';
6
- import { TokenScan, } from './TokenScan';
7
6
  export const regex_recurring_number = /^-?(\d+\.)(\d+r|\d*\[\d+\]r?|\d*\(\d+\)r?)$/;
7
+ //#endregion
8
+ //#region utility functions
8
9
  function do_math(left_operand, operator, right_operand) {
9
10
  return IntermediaryCalculation.maybe_reduce_operands(left_operand, operator, IntermediaryNumber.reuse_or_create(right_operand));
10
11
  }
@@ -85,6 +86,34 @@ function max(first, second, ...remaining) {
85
86
  }
86
87
  return IntermediaryNumber.reuse_or_create(max);
87
88
  }
89
+ //#region TokenScan utility functions
90
+ function default_tokenizer_state() {
91
+ return {
92
+ outter_stack: [],
93
+ left_operand: undefined,
94
+ operation: '',
95
+ right_operand: undefined,
96
+ operand_mode: 'left',
97
+ };
98
+ }
99
+ function is_nesting_open(maybe) {
100
+ return 'nesting_open' === maybe.type;
101
+ }
102
+ function is_nesting_close(maybe) {
103
+ return 'nesting_close' === maybe.type;
104
+ }
105
+ function is_numeric(maybe) {
106
+ return 'numeric' === maybe.type;
107
+ }
108
+ function is_operation_value(maybe) {
109
+ if (!(maybe.length === 1
110
+ && '+-/x*%'.includes(maybe))) {
111
+ throw new TokenScanError(`Expected operation value, found "${maybe}"`);
112
+ }
113
+ }
114
+ //#endregion
115
+ //#endregion
116
+ //#region IntermediaryNumber
88
117
  export class IntermediaryNumber {
89
118
  value;
90
119
  static One = new this('1');
@@ -242,6 +271,9 @@ export class IntermediaryNumber {
242
271
  }
243
272
  return this.value.toString();
244
273
  }
274
+ toStringCalculation() {
275
+ return this.toString();
276
+ }
245
277
  static create(input) {
246
278
  if ('' === input) {
247
279
  return IntermediaryNumber.Zero;
@@ -281,7 +313,7 @@ export class IntermediaryNumber {
281
313
  return IntermediaryNumber.create(maybe);
282
314
  }
283
315
  else if (/^(\d+|\d*\.\d+)\s*[+/*x%-]\s*(\d+|\d*\.\d+)$/.test(maybe)) {
284
- return (new TokenScan(input)).parsed;
316
+ return TokenScan.create(input).parsed;
285
317
  }
286
318
  const scientific = /^(-?\d+(?:\.\d+))e([+-])(\d+)$/.exec(maybe);
287
319
  if (scientific) {
@@ -299,15 +331,21 @@ export class IntermediaryNumber {
299
331
  if ('IntermediaryNumber' === json.type) {
300
332
  return this.create(json.value);
301
333
  }
334
+ else if ('TokenScan' === json.type) {
335
+ return TokenScan.create(json.value);
336
+ }
302
337
  return new IntermediaryCalculation(this.fromJson(json.left), json.operation, this.fromJson(json.right));
303
338
  }
304
339
  static reuse_or_create(input) {
305
340
  return (((input instanceof IntermediaryNumber)
306
- || (input instanceof IntermediaryCalculation))
341
+ || (input instanceof IntermediaryCalculation)
342
+ || (input instanceof TokenScan))
307
343
  ? input
308
344
  : this.create(input));
309
345
  }
310
346
  }
347
+ //#endregion
348
+ //#region IntermediaryCalculation
311
349
  export class NotValid extends Error {
312
350
  reason;
313
351
  value;
@@ -474,8 +512,16 @@ export class IntermediaryCalculation {
474
512
  cache.set(this, value);
475
513
  return value;
476
514
  }
515
+ toStringCalculation() {
516
+ return `${(this.left_operand instanceof IntermediaryCalculation)
517
+ ? `(${this.left_operand.toStringCalculation()})`
518
+ : this.left_operand.toString()} ${this.operation} ${(this.right_operand instanceof IntermediaryCalculation)
519
+ ? `(${this.right_operand.toStringCalculation()})`
520
+ : this.right_operand.toString()}`;
521
+ }
477
522
  operand_to_IntermediaryNumber(operand) {
478
- if ((operand instanceof IntermediaryCalculation)) {
523
+ if ((operand instanceof IntermediaryCalculation)
524
+ || (operand instanceof TokenScan)) {
479
525
  return operand.resolve();
480
526
  }
481
527
  else if ('amount_string' === operand.type
@@ -487,7 +533,10 @@ export class IntermediaryCalculation {
487
533
  return operand;
488
534
  }
489
535
  static fromString(input) {
490
- return (new TokenScan(input)).parsed;
536
+ return TokenScan.create(input).parsed;
537
+ }
538
+ static is(maybe) {
539
+ return maybe instanceof this;
491
540
  }
492
541
  static maybe_reduce_operands(left, operation, right) {
493
542
  let value = this.maybe_short_circuit(left, operation, right);
@@ -499,6 +548,11 @@ export class IntermediaryCalculation {
499
548
  }
500
549
  return value;
501
550
  }
551
+ static require_is(maybe) {
552
+ if (!this.is(maybe)) {
553
+ throw new Error('Argument is not an instanceof IntermediaryCalculation');
554
+ }
555
+ }
502
556
  static maybe_short_circuit(left, operation, right) {
503
557
  let value = undefined;
504
558
  if ('+' === operation) {
@@ -526,4 +580,401 @@ export class IntermediaryCalculation {
526
580
  return value;
527
581
  }
528
582
  }
529
- //# sourceMappingURL=IntermediaryNumber.js.map
583
+ //#endregion
584
+ //#region TokenScan
585
+ class TokenSpan {
586
+ from;
587
+ to;
588
+ type;
589
+ constructor(from, to, type) {
590
+ this.from = from;
591
+ this.to = to;
592
+ this.type = type;
593
+ }
594
+ }
595
+ export class TokenScanError extends Error {
596
+ }
597
+ export class TokenScanParseError extends Error {
598
+ current;
599
+ scan;
600
+ state;
601
+ constructor(message, scan, state, current) {
602
+ super(message);
603
+ this.scan = scan;
604
+ this.state = state;
605
+ this.current = current;
606
+ }
607
+ }
608
+ const regex_numeric = (/(?:\d*\.\d*\(\d+\)r?|\d*\.\d*\[\d+\]r?|\d+(?:\.\d+r)?|\.\d+r?)/g);
609
+ export class TokenScan {
610
+ internal = {
611
+ parsed: undefined,
612
+ tokens: undefined,
613
+ valid: undefined,
614
+ };
615
+ value;
616
+ constructor(value) {
617
+ this.value = value;
618
+ }
619
+ get parsed() {
620
+ if (undefined === this.internal.parsed) {
621
+ this.internal.parsed = TokenScan.parse_scan(this);
622
+ }
623
+ return this.internal.parsed;
624
+ }
625
+ get resolve_type() {
626
+ return this.parsed.resolve_type;
627
+ }
628
+ get tokens() {
629
+ if (undefined === this.internal.tokens) {
630
+ this.internal.tokens = TokenScan.determine_tokens_from_scan(this);
631
+ }
632
+ return this.internal.tokens;
633
+ }
634
+ get type() {
635
+ return 'TokenScan';
636
+ }
637
+ get valid() {
638
+ if (undefined === this.internal.valid) {
639
+ try {
640
+ this.parsed;
641
+ this.internal.valid = true;
642
+ }
643
+ catch (err) {
644
+ this.internal.valid = false;
645
+ }
646
+ }
647
+ return this.internal.valid;
648
+ }
649
+ abs() {
650
+ return this.parsed.abs();
651
+ }
652
+ compare(value) {
653
+ return this.parsed.compare(value);
654
+ }
655
+ divide(value) {
656
+ return new TokenScan([
657
+ this,
658
+ '/',
659
+ value,
660
+ ]);
661
+ }
662
+ do_math_then_dispose(operator, right_operand) {
663
+ const result = this.parsed.do_math_then_dispose(operator, right_operand);
664
+ this.internal.parsed = undefined;
665
+ return result;
666
+ }
667
+ isGreaterThan(value) {
668
+ return this.parsed.isGreaterThan(value);
669
+ }
670
+ isLessThan(value) {
671
+ return this.parsed.isLessThan(value);
672
+ }
673
+ isOne() {
674
+ return ((is_string(this.value)
675
+ && '1' === this.value.trim())
676
+ || this.parsed.isOne());
677
+ }
678
+ isZero() {
679
+ return ((is_string(this.value)
680
+ && '0' === this.value.trim())
681
+ || this.parsed.isZero());
682
+ }
683
+ max(first, ...remaining) {
684
+ return this.parsed.max(first, ...remaining);
685
+ }
686
+ minus(value) {
687
+ return new TokenScan([
688
+ this,
689
+ '-',
690
+ value,
691
+ ]);
692
+ }
693
+ modulo(value) {
694
+ return new TokenScan([
695
+ this,
696
+ '%',
697
+ value,
698
+ ]);
699
+ }
700
+ plus(value) {
701
+ return new TokenScan([
702
+ this,
703
+ '+',
704
+ value,
705
+ ]);
706
+ }
707
+ resolve() {
708
+ const parsed = this.parsed;
709
+ return IntermediaryCalculation.is(parsed) ? parsed.resolve() : parsed;
710
+ }
711
+ times(value) {
712
+ return new TokenScan([
713
+ this,
714
+ 'x',
715
+ value,
716
+ ]);
717
+ }
718
+ toAmountString() {
719
+ return this.parsed.toAmountString();
720
+ }
721
+ toBigNumber() {
722
+ return this.parsed.toBigNumber();
723
+ }
724
+ toBigNumberOrFraction() {
725
+ return this.parsed.toBigNumberOrFraction();
726
+ }
727
+ toFraction() {
728
+ return this.parsed.toFraction();
729
+ }
730
+ toJSON() {
731
+ return {
732
+ type: 'TokenScan',
733
+ value: this.toStringCalculation(),
734
+ };
735
+ }
736
+ toString() {
737
+ return this.parsed.toString();
738
+ }
739
+ toStringCalculation() {
740
+ if (this.value instanceof Array) {
741
+ const left_operand = this.value[0];
742
+ return `${(left_operand.parsed instanceof IntermediaryNumber)
743
+ ? left_operand.toString()
744
+ : `(${left_operand.toStringCalculation()})`} ${this.value[1]} ${IntermediaryNumber.reuse_or_create(this.value[2]).toStringCalculation()}`;
745
+ }
746
+ return this.value;
747
+ }
748
+ static create(value) {
749
+ return new TokenScan(value);
750
+ }
751
+ static is(maybe) {
752
+ return maybe instanceof TokenScan;
753
+ }
754
+ static require_is(maybe) {
755
+ if (!this.is(maybe)) {
756
+ throw new Error('Argument is not an instanceof TokenScan');
757
+ }
758
+ }
759
+ static determine_tokens_from_scan(scan) {
760
+ let tokens = [];
761
+ const value = scan.toStringCalculation();
762
+ for (const entry of value.matchAll(/([\s]+)/g)) {
763
+ tokens.push(new TokenSpan(entry.index, entry.index + entry[0].length, 'ignore'));
764
+ }
765
+ for (const entry of value.matchAll(regex_numeric)) {
766
+ tokens.push(new TokenSpan(entry.index, entry.index + entry[0].length, 'numeric'));
767
+ }
768
+ for (const entry of value.matchAll(/([+/*x%-])/g)) {
769
+ tokens.push(new TokenSpan(entry.index, entry.index + entry[0].length, 'operation'));
770
+ }
771
+ for (const entry of value.matchAll(/(\()/g)) {
772
+ tokens.push(new TokenSpan(entry.index, entry.index + entry[0].length, 'nesting_open'));
773
+ }
774
+ for (const entry of value.matchAll(/(\))/g)) {
775
+ tokens.push(new TokenSpan(entry.index, entry.index + entry[0].length, 'nesting_close'));
776
+ }
777
+ tokens = tokens.sort((a, b) => {
778
+ return a.from - b.from;
779
+ });
780
+ const recursive_numerics = tokens.filter(maybe => ('numeric' === maybe.type
781
+ && /[()]/.test(value.substring(maybe.from, maybe.to))));
782
+ tokens = tokens.filter((maybe) => {
783
+ if ('nesting_open' === maybe.type
784
+ || 'nesting_close' === maybe.type) {
785
+ return !recursive_numerics.find(maybe_numeric => (maybe.from >= maybe_numeric.from
786
+ && maybe.to <= maybe_numeric.to));
787
+ }
788
+ return true;
789
+ });
790
+ if (tokens.length < 1) {
791
+ throw new TokenScanError('No tokens found!');
792
+ }
793
+ else if (0 !== tokens[0].from) {
794
+ throw new TokenScanError('First token not at index 0!');
795
+ }
796
+ else if (value.length !== tokens[tokens.length - 1].to) {
797
+ throw new TokenScanError('Last token does not end at end of string!');
798
+ }
799
+ let nesting_balance = 0;
800
+ for (let index = 0; index < tokens.length; ++index) {
801
+ const token = tokens[index];
802
+ if ('nesting_open' === token.type) {
803
+ nesting_balance += (token.to - token.from);
804
+ }
805
+ else if ('nesting_close' === token.type) {
806
+ nesting_balance -= (token.to - token.from);
807
+ }
808
+ if (index > 0
809
+ && tokens[index - 1].to !== token.from) {
810
+ console.error(tokens, index);
811
+ throw new TokenScanError(`Token expected to be found at index ${index}`);
812
+ }
813
+ }
814
+ if (0 !== nesting_balance) {
815
+ throw new TokenScanError('Imbalanced nesting in string!');
816
+ }
817
+ return this.massage_part_baked_tokens(scan, tokens.filter((maybe) => 'ignore' !== maybe.type));
818
+ }
819
+ static massage_part_baked_tokens(scan, tokens) {
820
+ const smoosh_numerics = [];
821
+ const value = scan.toStringCalculation();
822
+ for (let token_index = tokens.length - 1; token_index > 0; --token_index) {
823
+ const previous = tokens[token_index - 1];
824
+ const current = tokens[token_index];
825
+ if ('numeric' === previous.type) {
826
+ const previous_value = value.substring(previous.from, previous.to);
827
+ const current_value = value.substring(current.from, current.to);
828
+ if (current_value.startsWith('.')
829
+ && /^\d+$/.test(previous_value)) {
830
+ smoosh_numerics.push(token_index);
831
+ }
832
+ }
833
+ }
834
+ for (const index of smoosh_numerics) {
835
+ tokens.splice(index - 1, 2, new TokenSpan(tokens[index - 1].from, tokens[index].to, 'numeric'));
836
+ }
837
+ const convert_to_negative = [];
838
+ if (tokens.length >= 2
839
+ && 'operation' === tokens[0].type
840
+ && '-' === value[tokens[0].from]
841
+ && 'numeric' === tokens[1].type) {
842
+ convert_to_negative.push(0);
843
+ }
844
+ for (let token_index = 0; token_index < tokens.length; ++token_index) {
845
+ const token = tokens[token_index];
846
+ const next = tokens[token_index + 1];
847
+ const after = tokens[token_index + 2];
848
+ if (('nesting_open' === token.type
849
+ || 'operation' === token.type)
850
+ && next
851
+ && after
852
+ && 'operation' === next.type
853
+ && '-' === value[next.from]
854
+ && 'numeric' === after.type) {
855
+ convert_to_negative.push(token_index + 1);
856
+ token_index += 2;
857
+ continue;
858
+ }
859
+ }
860
+ for (const index of convert_to_negative.reverse()) {
861
+ tokens.splice(index, 2, new TokenSpan(tokens[index].from, tokens[index + 1].to, 'numeric'));
862
+ }
863
+ return tokens;
864
+ }
865
+ static parse_scan(scan) {
866
+ const reduced = scan.tokens.reduce((was, is, index) => TokenScan.reduce(scan, was, is, index), default_tokenizer_state());
867
+ if (undefined !== reduced.left_operand
868
+ && '' === reduced.operation
869
+ && undefined === reduced.right_operand
870
+ && 0 === reduced.outter_stack.length) {
871
+ return reduced.left_operand;
872
+ }
873
+ throw new TokenScanParseError('Parse in unsupported state!', scan, reduced);
874
+ }
875
+ static reduce(scan, was, is, index) {
876
+ const value = scan.toStringCalculation();
877
+ if (is_nesting_open(is)) {
878
+ if ('right' === was.operand_mode) {
879
+ if (undefined === was.left_operand) {
880
+ if (!(was.outter_stack.length > 0
881
+ && !(was.outter_stack[was.outter_stack.length - 1] instanceof TokenSpan))) {
882
+ throw new TokenScanParseError(
883
+ // eslint-disable-next-line max-len
884
+ 'Nesting opened without left operand to push into stack!', scan, was);
885
+ }
886
+ return was;
887
+ }
888
+ else if ('' === was.operation) {
889
+ throw new TokenScanParseError('Nesting opened without operation to push into stack!', scan, was, is);
890
+ }
891
+ was.outter_stack.push({
892
+ left_operand: was.left_operand,
893
+ operation: was.operation,
894
+ });
895
+ was.left_operand = undefined;
896
+ was.operation = '';
897
+ was.operand_mode = 'left';
898
+ }
899
+ else {
900
+ was.outter_stack.push(is);
901
+ }
902
+ }
903
+ else if (is_nesting_close(is)) {
904
+ const popped = was.outter_stack.pop();
905
+ if (popped instanceof TokenSpan) {
906
+ if ('nesting_open' === popped.type
907
+ && '' === was.operation
908
+ && undefined !== was.left_operand
909
+ && undefined === was.right_operand) {
910
+ // no-op, deliberately do nothing
911
+ }
912
+ else {
913
+ throw new TokenScanParseError(
914
+ // eslint-disable-next-line max-len
915
+ 'token span popping in this context not yet implemented', scan, was, is);
916
+ }
917
+ }
918
+ else if (undefined === popped) {
919
+ if (index !== (scan.tokens.length - 1)
920
+ && ('' !== was.operation
921
+ || undefined !== was.right_operand)) {
922
+ throw new TokenScanParseError('Token scan finished with incomplete parse!', scan, was, is);
923
+ }
924
+ }
925
+ else {
926
+ if ('' === was.operation
927
+ && undefined !== was.left_operand
928
+ && undefined === was.right_operand) {
929
+ was.left_operand = new IntermediaryCalculation(popped.left_operand, popped.operation, was.left_operand);
930
+ was.operation = '';
931
+ was.operand_mode = 'right';
932
+ }
933
+ else {
934
+ throw new TokenScanParseError(
935
+ // eslint-disable-next-line max-len
936
+ 'token span popping in this context not yet implemented', scan, was, is);
937
+ }
938
+ }
939
+ }
940
+ else if (is_numeric(is)) {
941
+ if ('left' === was.operand_mode) {
942
+ was.left_operand = IntermediaryNumber.create(value.substring(is.from, is.to));
943
+ was.operand_mode = 'right';
944
+ }
945
+ else {
946
+ if ('' === was.operation) {
947
+ throw new TokenScanParseError('Right operand detected without operation!', scan, was, is);
948
+ }
949
+ else if (undefined === was.left_operand) {
950
+ throw new TokenScanParseError('Right operand detected without left operand!', scan, was, is);
951
+ }
952
+ let resolved = new IntermediaryCalculation(was.left_operand, was.operation, IntermediaryNumber.create(value.substring(is.from, is.to)));
953
+ if (was.outter_stack.length > 0
954
+ && !(was.outter_stack[was.outter_stack.length - 1] instanceof TokenSpan)) {
955
+ const previous = (was.outter_stack.pop());
956
+ resolved = new IntermediaryCalculation(previous.left_operand, previous.operation, resolved);
957
+ }
958
+ was.left_operand = resolved;
959
+ was.operation = '';
960
+ was.right_operand = undefined;
961
+ }
962
+ }
963
+ else if ('operation' === is.type) {
964
+ if (undefined === was.left_operand) {
965
+ throw new TokenScanParseError('Operation detected without left operand!', scan, was, is);
966
+ }
967
+ else if ('' !== was.operation) {
968
+ throw new TokenScanParseError(`Cannot set operation when operation already set to "${was.operation}"`, scan, was, is);
969
+ }
970
+ const maybe = value.substring(is.from, is.to);
971
+ is_operation_value(maybe);
972
+ was.operation = maybe;
973
+ }
974
+ else {
975
+ throw new TokenScanParseError('not implemented', scan, was, is);
976
+ }
977
+ return was;
978
+ }
979
+ }
980
+ //#endregion
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=IntermediaryNumberTypes.js.map
@@ -51,4 +51,3 @@ export class NumberStrings {
51
51
  }
52
52
  }
53
53
  }
54
- //# sourceMappingURL=NumberStrings.js.map
package/lib/Numbers.js CHANGED
@@ -47,4 +47,3 @@ export class Numbers {
47
47
  return result;
48
48
  }
49
49
  }
50
- //# sourceMappingURL=Numbers.js.map
package/package.json CHANGED
@@ -8,6 +8,7 @@
8
8
  "url": "git+https://github.com/SignpostMarv/Intermediary-Number.git"
9
9
  },
10
10
  "funding": "https://github.com/SignpostMarv/Intermediary-Number?sponsor=1",
11
+ "exports": "./index.js",
11
12
  "devDependencies": {
12
13
  "@eslint/js": "^9.4.0",
13
14
  "@stdlib/types": "^0.3.2",
@@ -27,5 +28,5 @@
27
28
  "bignumber.js": "^9.1.2",
28
29
  "fraction.js": "^4.3.7"
29
30
  },
30
- "version": "0.1.1"
31
+ "version": "0.2.0"
31
32
  }
@@ -1,48 +0,0 @@
1
- import { IntermediaryCalculation, IntermediaryNumber, operation_types } from './IntermediaryNumber';
2
- type TokenSpan_types = 'ignore' | 'nesting_open' | 'nesting_close' | 'numeric' | 'operation';
3
- type TokenSpan_types_part_baked = Exclude<TokenSpan_types, 'ignore'>;
4
- declare class TokenSpan<T = TokenSpan_types> {
5
- readonly from: number;
6
- readonly to: number;
7
- readonly type: T;
8
- constructor(from: number, to: number, type: T);
9
- }
10
- export declare class TokenScanError extends Error {
11
- }
12
- export declare class TokenScanParseError extends Error {
13
- readonly current?: TokenSpan<TokenSpan_types>;
14
- readonly scan: TokenScan_parsing_value;
15
- readonly state?: TokenScan_tokenizer;
16
- constructor(message: string, scan: TokenScan_parsing_value, state: TokenScan_tokenizer, current?: TokenSpan<TokenSpan_types>);
17
- }
18
- type TokenScan_internals = {
19
- parsed: IntermediaryNumber | IntermediaryCalculation | undefined;
20
- tokens: (TokenSpan<TokenSpan_types_part_baked>[]) | undefined;
21
- valid: boolean | undefined;
22
- };
23
- type TokenScan_parsing_value = Omit<TokenScan, 'is_valid' | 'parsed'>;
24
- export declare class TokenScan {
25
- private readonly internal;
26
- readonly value: string;
27
- constructor(value: string);
28
- get parsed(): Exclude<TokenScan_internals['parsed'], undefined>;
29
- get tokens(): Exclude<TokenScan_internals['tokens'], undefined>;
30
- get valid(): boolean;
31
- private static determine_tokens_from_scan;
32
- private static massage_part_baked_tokens;
33
- private static parse_scan;
34
- private static reduce;
35
- }
36
- type TokenScan_tokenizer_operand_buffer = IntermediaryNumber | IntermediaryCalculation | undefined;
37
- type incomplete_operation = {
38
- left_operand: Exclude<TokenScan_tokenizer_operand_buffer, undefined>;
39
- operation: operation_types;
40
- };
41
- type TokenScan_tokenizer = {
42
- outter_stack: (incomplete_operation | TokenSpan<'nesting_open'>)[];
43
- left_operand: TokenScan_tokenizer_operand_buffer;
44
- right_operand: TokenScan_tokenizer_operand_buffer;
45
- operation: '' | operation_types;
46
- operand_mode: 'left' | 'right';
47
- };
48
- export {};
package/lib/TokenScan.js DELETED
@@ -1,302 +0,0 @@
1
- import { IntermediaryCalculation, IntermediaryNumber, } from './IntermediaryNumber';
2
- class TokenSpan {
3
- from;
4
- to;
5
- type;
6
- constructor(from, to, type) {
7
- this.from = from;
8
- this.to = to;
9
- this.type = type;
10
- }
11
- }
12
- export class TokenScanError extends Error {
13
- }
14
- export class TokenScanParseError extends Error {
15
- current;
16
- scan;
17
- state;
18
- constructor(message, scan, state, current) {
19
- super(message);
20
- this.scan = scan;
21
- this.state = state;
22
- this.current = current;
23
- }
24
- }
25
- const regex_numeric = (/(?:\d*\.\d*\(\d+\)r?|\d*\.\d*\[\d+\]r?|\d+(?:\.\d+r)?|\.\d+r?)/g);
26
- export class TokenScan {
27
- internal = {
28
- parsed: undefined,
29
- tokens: undefined,
30
- valid: undefined,
31
- };
32
- value;
33
- constructor(value) {
34
- this.value = value;
35
- }
36
- get parsed() {
37
- if (undefined === this.internal.parsed) {
38
- this.internal.parsed = TokenScan.parse_scan(this);
39
- }
40
- return this.internal.parsed;
41
- }
42
- get tokens() {
43
- if (undefined === this.internal.tokens) {
44
- this.internal.tokens = TokenScan.determine_tokens_from_scan(this);
45
- }
46
- return this.internal.tokens;
47
- }
48
- get valid() {
49
- if (undefined === this.internal.valid) {
50
- try {
51
- this.parsed;
52
- this.internal.valid = true;
53
- }
54
- catch (err) {
55
- this.internal.valid = false;
56
- }
57
- }
58
- return this.internal.valid;
59
- }
60
- static determine_tokens_from_scan(scan) {
61
- let tokens = [];
62
- for (const entry of scan.value.matchAll(/([\s]+)/g)) {
63
- tokens.push(new TokenSpan(entry.index, entry.index + entry[0].length, 'ignore'));
64
- }
65
- for (const entry of scan.value.matchAll(regex_numeric)) {
66
- tokens.push(new TokenSpan(entry.index, entry.index + entry[0].length, 'numeric'));
67
- }
68
- for (const entry of scan.value.matchAll(/([+/*x%-])/g)) {
69
- tokens.push(new TokenSpan(entry.index, entry.index + entry[0].length, 'operation'));
70
- }
71
- for (const entry of scan.value.matchAll(/(\()/g)) {
72
- tokens.push(new TokenSpan(entry.index, entry.index + entry[0].length, 'nesting_open'));
73
- }
74
- for (const entry of scan.value.matchAll(/(\))/g)) {
75
- tokens.push(new TokenSpan(entry.index, entry.index + entry[0].length, 'nesting_close'));
76
- }
77
- tokens = tokens.sort((a, b) => {
78
- return a.from - b.from;
79
- });
80
- const recursive_numerics = tokens.filter(maybe => ('numeric' === maybe.type
81
- && /[()]/.test(scan.value.substring(maybe.from, maybe.to))));
82
- tokens = tokens.filter((maybe) => {
83
- if ('nesting_open' === maybe.type
84
- || 'nesting_close' === maybe.type) {
85
- return !recursive_numerics.find(maybe_numeric => (maybe.from >= maybe_numeric.from
86
- && maybe.to <= maybe_numeric.to));
87
- }
88
- return true;
89
- });
90
- if (tokens.length < 1) {
91
- throw new TokenScanError('No tokens found!');
92
- }
93
- else if (0 !== tokens[0].from) {
94
- throw new TokenScanError('First token not at index 0!');
95
- }
96
- else if (scan.value.length !== tokens[tokens.length - 1].to) {
97
- throw new TokenScanError('Last token does not end at end of string!');
98
- }
99
- let nesting_balance = 0;
100
- for (let index = 0; index < tokens.length; ++index) {
101
- const token = tokens[index];
102
- if ('nesting_open' === token.type) {
103
- nesting_balance += (token.to - token.from);
104
- }
105
- else if ('nesting_close' === token.type) {
106
- nesting_balance -= (token.to - token.from);
107
- }
108
- if (index > 0
109
- && tokens[index - 1].to !== token.from) {
110
- console.error(tokens, index);
111
- throw new TokenScanError(`Token expected to be found at index ${index}`);
112
- }
113
- }
114
- if (0 !== nesting_balance) {
115
- throw new TokenScanError('Imbalanced nesting in string!');
116
- }
117
- return this.massage_part_baked_tokens(scan, tokens.filter((maybe) => 'ignore' !== maybe.type));
118
- }
119
- static massage_part_baked_tokens(scan, tokens) {
120
- const smoosh_numerics = [];
121
- for (let token_index = tokens.length - 1; token_index > 0; --token_index) {
122
- const previous = tokens[token_index - 1];
123
- const current = tokens[token_index];
124
- if ('numeric' === previous.type) {
125
- const previous_value = scan.value.substring(previous.from, previous.to);
126
- const current_value = scan.value.substring(current.from, current.to);
127
- if (current_value.startsWith('.')
128
- && /^\d+$/.test(previous_value)) {
129
- smoosh_numerics.push(token_index);
130
- }
131
- }
132
- }
133
- for (const index of smoosh_numerics) {
134
- tokens.splice(index - 1, 2, new TokenSpan(tokens[index - 1].from, tokens[index].to, 'numeric'));
135
- }
136
- const convert_to_negative = [];
137
- if (tokens.length >= 2
138
- && 'operation' === tokens[0].type
139
- && '-' === scan.value[tokens[0].from]
140
- && 'numeric' === tokens[1].type) {
141
- convert_to_negative.push(0);
142
- }
143
- for (let token_index = 0; token_index < tokens.length; ++token_index) {
144
- const token = tokens[token_index];
145
- const next = tokens[token_index + 1];
146
- const after = tokens[token_index + 2];
147
- if (('nesting_open' === token.type
148
- || 'operation' === token.type)
149
- && next
150
- && after
151
- && 'operation' === next.type
152
- && '-' === scan.value[next.from]
153
- && 'numeric' === after.type) {
154
- convert_to_negative.push(token_index + 1);
155
- token_index += 2;
156
- continue;
157
- }
158
- }
159
- for (const index of convert_to_negative.reverse()) {
160
- tokens.splice(index, 2, new TokenSpan(tokens[index].from, tokens[index + 1].to, 'numeric'));
161
- }
162
- return tokens;
163
- }
164
- static parse_scan(scan) {
165
- const reduced = scan.tokens.reduce((was, is, index) => TokenScan.reduce(scan, was, is, index), default_tokenizer_state());
166
- if (undefined !== reduced.left_operand
167
- && '' === reduced.operation
168
- && undefined === reduced.right_operand
169
- && 0 === reduced.outter_stack.length) {
170
- return reduced.left_operand;
171
- }
172
- throw new TokenScanParseError('Parse in unsupported state!', scan, reduced);
173
- }
174
- static reduce(scan, was, is, index) {
175
- if (is_nesting_open(is)) {
176
- if ('right' === was.operand_mode) {
177
- if (undefined === was.left_operand) {
178
- if (!(was.outter_stack.length > 0
179
- && !(was.outter_stack[was.outter_stack.length - 1] instanceof TokenSpan))) {
180
- throw new TokenScanParseError(
181
- // eslint-disable-next-line max-len
182
- 'Nesting opened without left operand to push into stack!', scan, was);
183
- }
184
- return was;
185
- }
186
- else if ('' === was.operation) {
187
- throw new TokenScanParseError('Nesting opened without operation to push into stack!', scan, was, is);
188
- }
189
- was.outter_stack.push({
190
- left_operand: was.left_operand,
191
- operation: was.operation,
192
- });
193
- was.left_operand = undefined;
194
- was.operation = '';
195
- was.operand_mode = 'left';
196
- }
197
- else {
198
- was.outter_stack.push(is);
199
- }
200
- }
201
- else if (is_nesting_close(is)) {
202
- const popped = was.outter_stack.pop();
203
- if (popped instanceof TokenSpan) {
204
- if ('nesting_open' === popped.type
205
- && '' === was.operation
206
- && undefined !== was.left_operand
207
- && undefined === was.right_operand) {
208
- // no-op, deliberately do nothing
209
- }
210
- else {
211
- throw new TokenScanParseError(
212
- // eslint-disable-next-line max-len
213
- 'token span popping in this context not yet implemented', scan, was, is);
214
- }
215
- }
216
- else if (undefined === popped) {
217
- if (index !== (scan.tokens.length - 1)
218
- && ('' !== was.operation
219
- || undefined !== was.right_operand)) {
220
- throw new TokenScanParseError('Token scan finished with incomplete parse!', scan, was, is);
221
- }
222
- }
223
- else {
224
- if ('' === was.operation
225
- && undefined !== was.left_operand
226
- && undefined === was.right_operand) {
227
- was.left_operand = new IntermediaryCalculation(popped.left_operand, popped.operation, was.left_operand);
228
- was.operation = '';
229
- was.operand_mode = 'right';
230
- }
231
- else {
232
- throw new TokenScanParseError(
233
- // eslint-disable-next-line max-len
234
- 'token span popping in this context not yet implemented', scan, was, is);
235
- }
236
- }
237
- }
238
- else if (is_numeric(is)) {
239
- if ('left' === was.operand_mode) {
240
- was.left_operand = IntermediaryNumber.create(scan.value.substring(is.from, is.to));
241
- was.operand_mode = 'right';
242
- }
243
- else {
244
- if ('' === was.operation) {
245
- throw new TokenScanParseError('Right operand detected without operation!', scan, was, is);
246
- }
247
- else if (undefined === was.left_operand) {
248
- throw new TokenScanParseError('Right operand detected without left operand!', scan, was, is);
249
- }
250
- let resolved = new IntermediaryCalculation(was.left_operand, was.operation, IntermediaryNumber.create(scan.value.substring(is.from, is.to)));
251
- if (was.outter_stack.length > 0
252
- && !(was.outter_stack[was.outter_stack.length - 1] instanceof TokenSpan)) {
253
- const previous = (was.outter_stack.pop());
254
- resolved = new IntermediaryCalculation(previous.left_operand, previous.operation, resolved);
255
- }
256
- was.left_operand = resolved;
257
- was.operation = '';
258
- was.right_operand = undefined;
259
- }
260
- }
261
- else if ('operation' === is.type) {
262
- if (undefined === was.left_operand) {
263
- throw new TokenScanParseError('Operation detected without left operand!', scan, was, is);
264
- }
265
- else if ('' !== was.operation) {
266
- throw new TokenScanParseError(`Cannot set operation when operation already set to "${was.operation}"`, scan, was, is);
267
- }
268
- const maybe = scan.value.substring(is.from, is.to);
269
- is_operation_value(maybe);
270
- was.operation = maybe;
271
- }
272
- else {
273
- throw new TokenScanParseError('not implemented', scan, was, is);
274
- }
275
- return was;
276
- }
277
- }
278
- function default_tokenizer_state() {
279
- return {
280
- outter_stack: [],
281
- left_operand: undefined,
282
- operation: '',
283
- right_operand: undefined,
284
- operand_mode: 'left',
285
- };
286
- }
287
- function is_nesting_open(maybe) {
288
- return 'nesting_open' === maybe.type;
289
- }
290
- function is_nesting_close(maybe) {
291
- return 'nesting_close' === maybe.type;
292
- }
293
- function is_numeric(maybe) {
294
- return 'numeric' === maybe.type;
295
- }
296
- function is_operation_value(maybe) {
297
- if (!(maybe.length === 1
298
- && '+-/x*%'.includes(maybe))) {
299
- throw new TokenScanError(`Expected operation value, found "${maybe}"`);
300
- }
301
- }
302
- //# sourceMappingURL=TokenScan.js.map