@elyukai/utils 0.2.8 → 0.3.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/{array → src/array}/filters.js +4 -4
  3. package/dist/{array → src/array}/reductions.js +2 -2
  4. package/dist/src/array/transformations.d.ts +27 -0
  5. package/dist/src/array/transformations.js +34 -0
  6. package/dist/{async.js → src/async.js} +2 -2
  7. package/dist/{classList.js → src/classList.js} +1 -1
  8. package/dist/{equality.js → src/equality.js} +3 -8
  9. package/dist/{function.js → src/function.js} +2 -2
  10. package/dist/{maybe.js → src/maybe.js} +2 -4
  11. package/dist/{nullable.js → src/nullable.js} +1 -1
  12. package/dist/{object.js → src/object.js} +1 -1
  13. package/dist/src/parser.d.ts +146 -0
  14. package/dist/src/parser.js +219 -0
  15. package/dist/{reader.js → src/reader.js} +10 -10
  16. package/dist/{result.js → src/result.js} +3 -3
  17. package/dist/src/state.d.ts +51 -0
  18. package/dist/src/state.js +79 -0
  19. package/dist/src/stateParser.d.ts +143 -0
  20. package/dist/src/stateParser.js +219 -0
  21. package/dist/{string → src/string}/number.js +3 -11
  22. package/dist/{string.js → src/string.js} +6 -13
  23. package/package.json +6 -6
  24. package/dist/array/generators.d.ts +0 -13
  25. package/dist/array/generators.js +0 -17
  26. /package/dist/{array → src/array}/filters.d.ts +0 -0
  27. /package/dist/{array → src/array}/fixed.d.ts +0 -0
  28. /package/dist/{array → src/array}/fixed.js +0 -0
  29. /package/dist/{array → src/array}/groups.d.ts +0 -0
  30. /package/dist/{array → src/array}/groups.js +0 -0
  31. /package/dist/{array → src/array}/modify.d.ts +0 -0
  32. /package/dist/{array → src/array}/modify.js +0 -0
  33. /package/dist/{array → src/array}/nonEmpty.d.ts +0 -0
  34. /package/dist/{array → src/array}/nonEmpty.js +0 -0
  35. /package/dist/{array → src/array}/reductions.d.ts +0 -0
  36. /package/dist/{array → src/array}/sets.d.ts +0 -0
  37. /package/dist/{array → src/array}/sets.js +0 -0
  38. /package/dist/{async.d.ts → src/async.d.ts} +0 -0
  39. /package/dist/{classList.d.ts → src/classList.d.ts} +0 -0
  40. /package/dist/{dictionary → src/dictionary}/native.d.ts +0 -0
  41. /package/dist/{dictionary → src/dictionary}/native.js +0 -0
  42. /package/dist/{dictionary.d.ts → src/dictionary.d.ts} +0 -0
  43. /package/dist/{dictionary.js → src/dictionary.js} +0 -0
  44. /package/dist/{equality.d.ts → src/equality.d.ts} +0 -0
  45. /package/dist/{function.d.ts → src/function.d.ts} +0 -0
  46. /package/dist/{lazy.d.ts → src/lazy.d.ts} +0 -0
  47. /package/dist/{lazy.js → src/lazy.js} +0 -0
  48. /package/dist/{maybe.d.ts → src/maybe.d.ts} +0 -0
  49. /package/dist/{nullable.d.ts → src/nullable.d.ts} +0 -0
  50. /package/dist/{number.d.ts → src/number.d.ts} +0 -0
  51. /package/dist/{number.js → src/number.js} +0 -0
  52. /package/dist/{object.d.ts → src/object.d.ts} +0 -0
  53. /package/dist/{ordering.d.ts → src/ordering.d.ts} +0 -0
  54. /package/dist/{ordering.js → src/ordering.js} +0 -0
  55. /package/dist/{range.d.ts → src/range.d.ts} +0 -0
  56. /package/dist/{range.js → src/range.js} +0 -0
  57. /package/dist/{reader.d.ts → src/reader.d.ts} +0 -0
  58. /package/dist/{result.d.ts → src/result.d.ts} +0 -0
  59. /package/dist/{roman.d.ts → src/roman.d.ts} +0 -0
  60. /package/dist/{roman.js → src/roman.js} +0 -0
  61. /package/dist/{string → src/string}/number.d.ts +0 -0
  62. /package/dist/{string → src/string}/regex.d.ts +0 -0
  63. /package/dist/{string → src/string}/regex.js +0 -0
  64. /package/dist/{string.d.ts → src/string.d.ts} +0 -0
  65. /package/dist/{typeSafety.d.ts → src/typeSafety.d.ts} +0 -0
  66. /package/dist/{typeSafety.js → src/typeSafety.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,32 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
4
4
 
5
+ ## [0.3.0](https://github.com/elyukai/ts-utils/compare/v0.2.9...v0.3.0) (2026-03-06)
6
+
7
+
8
+ ### ⚠ BREAKING CHANGES
9
+
10
+ * intersperse as old intercalate
11
+ * add array intercalate and rename module
12
+
13
+ ### Features
14
+
15
+ * add array intercalate and rename module ([2495638](https://github.com/elyukai/ts-utils/commit/24956389cb0fe6e867a985ab760d295cced4ba6b))
16
+ * intersperse as old intercalate ([2d39540](https://github.com/elyukai/ts-utils/commit/2d39540f502415f7db4d5e9227aaab4802e16148))
17
+
18
+ ## [0.2.9](https://github.com/elyukai/ts-utils/compare/v0.2.8...v0.2.9) (2026-03-02)
19
+
20
+
21
+ ### Features
22
+
23
+ * add simple parser combinator class ([33ead55](https://github.com/elyukai/ts-utils/commit/33ead55758fb5cb8dc20de2d53a69f1973ec3c3e))
24
+ * extend parser, add state and combine both ([dd9a3f6](https://github.com/elyukai/ts-utils/commit/dd9a3f680c2b80658aff19103ecbfa3a4d81bdaf))
25
+
26
+
27
+ ### Bug Fixes
28
+
29
+ * slow type ([e8d6fb6](https://github.com/elyukai/ts-utils/commit/e8d6fb63e79937a01b8f15441147bc2b72686e97))
30
+
5
31
  ## [0.2.8](https://github.com/elyukai/ts-utils/compare/v0.2.7...v0.2.8) (2026-02-24)
6
32
 
7
33
  ## [0.2.7](https://github.com/elyukai/ts-utils/compare/v0.2.6...v0.2.7) (2026-02-24)
@@ -11,17 +11,17 @@ export const unique = (arr) => Array.from(new Set(arr));
11
11
  /**
12
12
  * Checks if there are any duplicate elements in the array.
13
13
  */
14
- export const anySame = (arr, equalityCheck = (a, b) => a === b) => arr.some((item, index) => arr.findIndex((other) => equalityCheck(item, other)) !== index);
14
+ export const anySame = (arr, equalityCheck = (a, b) => a === b) => arr.some((item, index) => arr.findIndex(other => equalityCheck(item, other)) !== index);
15
15
  /**
16
16
  * Checks if there are any duplicate elements in the array and returns an array
17
17
  * of found duplicates where the values are the indices of these values.
18
18
  */
19
19
  export const anySameIndices = (arr, equalityCheck = (a, b) => a === b) => arr.reduce((acc, item, index) => {
20
- const firstIndex = arr.findIndex((other) => equalityCheck(item, other));
20
+ const firstIndex = arr.findIndex(other => equalityCheck(item, other));
21
21
  if (firstIndex === index) {
22
22
  return acc;
23
23
  }
24
- const accIndex = acc.findIndex((accElem) => accElem[0] === firstIndex);
24
+ const accIndex = acc.findIndex(accElem => accElem[0] === firstIndex);
25
25
  return accIndex === -1
26
26
  ? [...acc, [firstIndex, index]]
27
27
  : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- The index must exist according to the findIndex above
@@ -34,4 +34,4 @@ export const anySameIndices = (arr, equalityCheck = (a, b) => a === b) => arr.re
34
34
  * @param equalityCheck An optional function to determine if two elements are considered the same. Defaults to strict shallow equality.
35
35
  * @returns `true` if all elements in the array are the same, otherwise `false`.
36
36
  */
37
- export const allSame = (arr, equalityCheck = (a, b) => a === b) => isNotEmpty(arr) ? arr.every((item) => equalityCheck(item, arr[0])) : true;
37
+ export const allSame = (arr, equalityCheck = (a, b) => a === b) => (isNotEmpty(arr) ? arr.every(item => equalityCheck(item, arr[0])) : true);
@@ -50,7 +50,7 @@ export const countBy = (arr, fn) => arr.reduce((acc, value, index) => {
50
50
  */
51
51
  export const countByMany = (arr, fn) => arr.reduce((acc, value, index) => {
52
52
  const keys = fn(value, index);
53
- unique(keys).forEach((key) => {
53
+ unique(keys).forEach(key => {
54
54
  acc[key] = (acc[key] ?? 0) + 1;
55
55
  });
56
56
  return acc;
@@ -59,4 +59,4 @@ export const countByMany = (arr, fn) => arr.reduce((acc, value, index) => {
59
59
  * Returns `true` if the array contains at least `minCount` elements that
60
60
  * satisfy the given predicate, `false` otherwise.
61
61
  */
62
- export const someCount = (arr, predicate, minCount) => reduceWhile(arr, (acc, value) => (predicate(value) ? acc + 1 : acc), (acc) => acc >= minCount, 0) >= minCount;
62
+ export const someCount = (arr, predicate, minCount) => reduceWhile(arr, (acc, value) => (predicate(value) ? acc + 1 : acc), acc => acc >= minCount, 0) >= minCount;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Utility functions for transforming arrays.
3
+ * @module
4
+ */
5
+ /**
6
+ * Returns a new array with the given value interspersed between each element of the given array. An empty array is returned if the given array is empty.
7
+ * @param arr The array to intersperse.
8
+ * @param value The value to intersperse between each element of the array.
9
+ * @returns A new array with the given value interspersed between each element of the given array.
10
+ */
11
+ export declare const intersperse: <T>(arr: T[], value: T) => T[];
12
+ /**
13
+ * Returns a new array with the given values intercalated between each element of the given array. An empty array is returned if the given array is empty.
14
+ * @param arr The array to intercalate.
15
+ * @param values The values to intercalate between each element of the array.
16
+ * @returns A new array with the given values intercalated between each element of the given array.
17
+ */
18
+ export declare const intercalate: <T>(arr: T[][], values: T[]) => T[];
19
+ /**
20
+ * Returns the possibilities of all the combinations of nested array values.
21
+ *
22
+ * @example
23
+ *
24
+ * flatCombine([["a", "b"], ["c"]]) // [["a", "c"], ["b", "c"]]
25
+ * flatCombine([["a", "b"], ["c", "d"]]) // [["a", "c"], ["b", "c"], ["a", "d"], ["b", "d"]]
26
+ */
27
+ export declare const flatCombine: <T>(arr: T[][]) => T[][];
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Utility functions for transforming arrays.
3
+ * @module
4
+ */
5
+ import { isNotEmpty } from "./nonEmpty.js";
6
+ /**
7
+ * Returns a new array with the given value interspersed between each element of the given array. An empty array is returned if the given array is empty.
8
+ * @param arr The array to intersperse.
9
+ * @param value The value to intersperse between each element of the array.
10
+ * @returns A new array with the given value interspersed between each element of the given array.
11
+ */
12
+ export const intersperse = (arr, value) => !isNotEmpty(arr) ? [] : arr.slice(1).reduce((acc, elem) => [...acc, value, elem], [arr[0]]);
13
+ /**
14
+ * Returns a new array with the given values intercalated between each element of the given array. An empty array is returned if the given array is empty.
15
+ * @param arr The array to intercalate.
16
+ * @param values The values to intercalate between each element of the array.
17
+ * @returns A new array with the given values intercalated between each element of the given array.
18
+ */
19
+ export const intercalate = (arr, values) => !isNotEmpty(arr)
20
+ ? []
21
+ : arr.slice(1).reduce((acc, elem) => [...acc, ...values, ...elem], arr[0]);
22
+ /**
23
+ * Returns the possibilities of all the combinations of nested array values.
24
+ *
25
+ * @example
26
+ *
27
+ * flatCombine([["a", "b"], ["c"]]) // [["a", "c"], ["b", "c"]]
28
+ * flatCombine([["a", "b"], ["c", "d"]]) // [["a", "c"], ["b", "c"], ["a", "d"], ["b", "d"]]
29
+ */
30
+ export const flatCombine = (arr) => arr.length === 0
31
+ ? []
32
+ : arr.slice(1).reduce((acc, elem) => elem.flatMap(elemInner => acc.map(accElem => [...accElem, elemInner])),
33
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- it is checked before if the array is empty
34
+ arr[0].map(elem => [elem]));
@@ -12,7 +12,7 @@
12
12
  * await wait(1000); // Waits for 1 second
13
13
  * ```
14
14
  */
15
- export const wait = (delay) => new Promise((resolve) => setTimeout(resolve, delay));
15
+ export const wait = (delay) => new Promise(resolve => setTimeout(resolve, delay));
16
16
  /**
17
17
  * A simple async map to process tasks with a concurrency limit.
18
18
  * @param tasks The list of tasks to process.
@@ -36,7 +36,7 @@ export const mapAsync = (tasks, worker, concurrency) => new Promise((resolve, re
36
36
  const task = tasks[currentIndex];
37
37
  activeCount++;
38
38
  worker(task)
39
- .then((result) => {
39
+ .then(result => {
40
40
  results[currentIndex] = result;
41
41
  resolvedCount++;
42
42
  activeCount--;
@@ -7,7 +7,7 @@
7
7
  * nullable values and keys with falsey values.
8
8
  */
9
9
  export const classList = (...cls) => cls
10
- .flatMap((cl) => {
10
+ .flatMap(cl => {
11
11
  if (cl === null || cl === undefined) {
12
12
  return [];
13
13
  }
@@ -31,8 +31,7 @@ export const notEqual = (a, b) => !Object.is(a, b);
31
31
  *
32
32
  * Use {@link deepEqual} for a deep equality check.
33
33
  */
34
- export const arrayEqual = (arr1, arr2) => arr1.length === arr2.length &&
35
- arr1.every((value, index) => equal(value, arr2[index]));
34
+ export const arrayEqual = (arr1, arr2) => arr1.length === arr2.length && arr1.every((value, index) => equal(value, arr2[index]));
36
35
  /**
37
36
  * Checks two values for value equality. This is a deep equality check that
38
37
  * works for all types, including objects and arrays. For objects, it only
@@ -42,16 +41,12 @@ export const deepEqual = (a, b) => {
42
41
  if (equal(a, b)) {
43
42
  return true;
44
43
  }
45
- if (typeof a === "object" &&
46
- typeof b === "object" &&
47
- a !== null &&
48
- b !== null) {
44
+ if (typeof a === "object" && typeof b === "object" && a !== null && b !== null) {
49
45
  const keys = Object.keys(a);
50
46
  if (keys.length !== Object.keys(b).length) {
51
47
  return false;
52
48
  }
53
- return keys.every((key) => key in b &&
54
- deepEqual(a[key], b[key]));
49
+ return keys.every(key => key in b && deepEqual(a[key], b[key]));
55
50
  }
56
51
  return false;
57
52
  };
@@ -16,12 +16,12 @@ export const constant = (value) => () => value;
16
16
  * Returns a function that applies the given predicates to the given value and
17
17
  * returns `true` if all predicates return `true`.
18
18
  */
19
- export const andEvery = (...predicates) => (value) => predicates.every((predicate) => predicate(value));
19
+ export const andEvery = (...predicates) => (value) => predicates.every(predicate => predicate(value));
20
20
  /**
21
21
  * Returns a function that combines predicate functions disjunctionally.
22
22
  */
23
23
  export function orSome(...predicates) {
24
- return (value) => predicates.some((predicate) => predicate(value));
24
+ return value => predicates.some(predicate => predicate(value));
25
25
  }
26
26
  /**
27
27
  * Returns a function that applies the given predicate to the given value and
@@ -33,12 +33,10 @@ export const map = (maybe, f) => isJust(maybe) ? Just(f(maybe.value)) : Nothing;
33
33
  /**
34
34
  * Chains a result to a new result.
35
35
  */
36
- export const then = (maybe, f) => (isJust(maybe) ? f(maybe.value) : maybe);
36
+ export const then = (maybe, f) => isJust(maybe) ? f(maybe.value) : maybe;
37
37
  /**
38
38
  * Combines two maybes into one maybe. If both maybes contain a value, the
39
39
  * values are combined using the provided function. If at least one maybe
40
40
  * contains nothing, nothing is returned.
41
41
  */
42
- export const combine = (maybe1, maybe2, f) => isJust(maybe1) && isJust(maybe2)
43
- ? Just(f(maybe1.value, maybe2.value))
44
- : Nothing;
42
+ export const combine = (maybe1, maybe2, f) => (isJust(maybe1) && isJust(maybe2) ? Just(f(maybe1.value, maybe2.value)) : Nothing);
@@ -13,7 +13,7 @@ export const isNotNullish = (value) => !isNullish(value);
13
13
  /**
14
14
  * Maps a value to another value if it is not `null` or `undefined`.
15
15
  */
16
- export const mapNullable = (value, map) => (isNotNullish(value) ? map(value) : value);
16
+ export const mapNullable = (value, map) => isNotNullish(value) ? map(value) : value;
17
17
  /**
18
18
  * Maps a value to another value if it is not `null` or `undefined`, otherwise
19
19
  * returns a default value.
@@ -24,7 +24,7 @@ export const mapObject = (object, map) => {
24
24
  * Keys not present in the keys array will be placed at the end in their original order.
25
25
  */
26
26
  export const sortObjectKeysByIndex = (obj, keys) => Object.fromEntries([
27
- ...keys.flatMap((key) => obj[key] === undefined ? [] : [[key, obj[key]]]),
27
+ ...keys.flatMap(key => (obj[key] === undefined ? [] : [[key, obj[key]]])),
28
28
  ...Object.entries(obj).filter(([key]) => !keys.includes(key)),
29
29
  ]);
30
30
  /**
@@ -0,0 +1,146 @@
1
+ /**
2
+ * A simple parser combinator.
3
+ * @module
4
+ */
5
+ /**
6
+ * The type `ParserResult<T>` represents the result of parsing a string using a parser. It is an array of tuples, where each tuple contains a parsed element of type T and the remaining unparsed string. If the parser fails to parse the input according to the defined rules, it returns an empty array.
7
+ */
8
+ export type ParserResult<T> = [parsed: T, remaining: string][];
9
+ /**
10
+ * A class that serves as a parser combinator, allowing you to build complex parsers by combining simpler ones. The parser takes a string input and produces an array of tuples, where each tuple contains a parsed element of type T and the remaining unparsed string. If the parser fails to parse the input according to the defined rules, it returns an empty array.
11
+ */
12
+ export declare class Parser<T> {
13
+ #private;
14
+ /**
15
+ * Returns a parser that applies the given function to the syntax string if it is not empty.
16
+ */
17
+ constructor(fn: (syntax: string) => ParserResult<T>);
18
+ /**
19
+ * Parses a single character.
20
+ */
21
+ static item: Parser<string>;
22
+ /**
23
+ * Returns a parser that always succeeds with the given element, without consuming any input.
24
+ */
25
+ static of<T>(element: T): Parser<T>;
26
+ /**
27
+ * Transforms the result of this parser using the given function, without consuming any additional input.
28
+ */
29
+ map<U>(f: (result: T) => U): Parser<U>;
30
+ /**
31
+ * Chains this parser with another parser that depends on the result of this parser.
32
+ */
33
+ then<U>(f: (result: T) => Parser<U>): Parser<U>;
34
+ /**
35
+ * A parser that always fails.
36
+ */
37
+ static zero: Parser<never>;
38
+ /**
39
+ * Combines this parser with another parser, trying this parser first and then the other parser.
40
+ */
41
+ or(other: Parser<T>): Parser<T>;
42
+ /**
43
+ * Combines this parser with another parser, trying this parser first and then the other parser only if the first parser fails. It only returns the first successful result.
44
+ */
45
+ orFirst(other: Parser<T>): Parser<T>;
46
+ /**
47
+ * Combines this parser with another parser, trying this parser first and then the other parser only if the first parser fails. It only returns the first successful result.
48
+ *
49
+ * The suffix ‘W’ stands for ‘widening’, as this method allows you to combine parsers with different result types.
50
+ */
51
+ orFirstW<U>(other: Parser<U>): Parser<T | U>;
52
+ /**
53
+ * Returns a parser that tries this parser and returns its result if it succeeds, but if this parser fails, it returns a parser that always succeeds with `undefined` without consuming any input.
54
+ */
55
+ optional(): Parser<T | undefined>;
56
+ /**
57
+ * Returns a parser that parses a character that satisfies the given predicate function.
58
+ */
59
+ static satisfy<C extends string>(predicate: (character: string) => character is C): Parser<C>;
60
+ static satisfy(predicate: (character: string) => boolean): Parser<string>;
61
+ /**
62
+ * Returns a parser that parses a specific string.
63
+ */
64
+ static string<T extends string>(string: T): Parser<T>;
65
+ /**
66
+ * Returns a parser that parses zero or more occurrences of this parser, returning an array of the parsed results.
67
+ */
68
+ many(): Parser<T[]>;
69
+ /**
70
+ * Returns a parser that parses one or more occurrences of this parser, returning an array of the parsed results.
71
+ *
72
+ * It fails if this parser does not succeed at least once.
73
+ */
74
+ many1(): Parser<T[]>;
75
+ /**
76
+ * Returns a parser that parses zero or more occurrences of this parser, separated by another parser, returning an array of the parsed results.
77
+ */
78
+ separatedBy(separator: Parser<unknown>): Parser<T[]>;
79
+ /**
80
+ * Returns a parser that parses one or more occurrences of this parser, separated by another parser, returning an array of the parsed results.
81
+ *
82
+ * It fails if this parser does not succeed at least once.
83
+ */
84
+ separatedBy1(separator: Parser<unknown>): Parser<T[]>;
85
+ /**
86
+ * Returns a parser that parses a string that matches the given regular expression pattern.
87
+ *
88
+ * The pattern must match at the beginning of the string (patterns that are designed like this might perform better), and the parser will return the matched substring as the parsed result.
89
+ */
90
+ static regex(pattern: RegExp): Parser<string>;
91
+ /**
92
+ * A parser that parses whitepace characters.
93
+ */
94
+ static space: Parser<string>;
95
+ /**
96
+ * A parser that parses horizontal whitepace characters.
97
+ *
98
+ * This can be useful for parsing a language where newlines are significant.
99
+ */
100
+ static hspace: Parser<string>;
101
+ /**
102
+ * Throws away trailing whitespace characters before applying the given parser.
103
+ */
104
+ token(): Parser<T>;
105
+ /**
106
+ * Throws away trailing horizontal whitespace characters before applying the given parser.
107
+ *
108
+ * This can be useful for parsing a language where newlines are significant.
109
+ */
110
+ htoken(): Parser<T>;
111
+ /**
112
+ * Returns a parser that parses a specific string, ignoring trailing whitespace characters.
113
+ */
114
+ static symb<T extends string>(symbol: T): Parser<T>;
115
+ /**
116
+ * Returns a parser that parses a specific string, ignoring trailing horizontal whitespace characters.
117
+ *
118
+ * This can be useful for parsing a language where newlines are significant.
119
+ */
120
+ static hsymb<T extends string>(symbol: T): Parser<T>;
121
+ /**
122
+ * Creates a parser that parses a value using the given parser, surrounded by the given left and right parsers, and returns the parsed value.
123
+ */
124
+ between<L, R>(left: Parser<L>, right: Parser<R>): Parser<T>;
125
+ /**
126
+ * Returns a parser that applies the given parser to the input string without consuming any input, returning the result of the parser if it succeeds. If the parser fails, this lookahead parser also fails.
127
+ */
128
+ static lookahead<T>(parser: Parser<T>): Parser<T>;
129
+ /**
130
+ * Returns a parser that applies the given parser to the input string without consuming any input, returning `undefined` if it fails. If the parser succeeds, this fails instead.
131
+ */
132
+ static negativeLookahead(parser: Parser<unknown>): Parser<undefined>;
133
+ /**
134
+ * Runs the parser on the given syntax string, ignoring any leading whitespace.
135
+ *
136
+ * A complete parse is one where the remaining unparsed string is empty. However, this method does not enforce that, and it will return all possible parses, including those that do not consume the entire input.
137
+ */
138
+ apply(syntax: string): ParserResult<T>;
139
+ /**
140
+ * Runs the parser on the given syntax string.
141
+ *
142
+ * A complete parse is one where the remaining unparsed string is empty. However, this method does not enforce that, and it will return all possible parses, including those that do not consume the entire input.
143
+ */
144
+ parse(syntax: string): ParserResult<T>;
145
+ static debugLog<T>(parser: Parser<T>): Parser<T>;
146
+ }
@@ -0,0 +1,219 @@
1
+ /**
2
+ * A simple parser combinator.
3
+ * @module
4
+ */
5
+ /**
6
+ * A class that serves as a parser combinator, allowing you to build complex parsers by combining simpler ones. The parser takes a string input and produces an array of tuples, where each tuple contains a parsed element of type T and the remaining unparsed string. If the parser fails to parse the input according to the defined rules, it returns an empty array.
7
+ */
8
+ export class Parser {
9
+ /**
10
+ * The function takes a syntax string and returns an array of tuples, where each tuple contains a parsed element of type T and the remaining unparsed string. An empty array is returned if the syntax string cannot be parsed according to the defined rules.
11
+ */
12
+ #fn;
13
+ /**
14
+ * Returns a parser that applies the given function to the syntax string if it is not empty.
15
+ */
16
+ constructor(fn) {
17
+ this.#fn = fn;
18
+ }
19
+ /**
20
+ * Parses a single character.
21
+ */
22
+ static item = new Parser(syntax =>
23
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- we check for empty string before accessing the first character
24
+ syntax.length === 0 ? [] : [[syntax[0], syntax.slice(1)]]);
25
+ /**
26
+ * Returns a parser that always succeeds with the given element, without consuming any input.
27
+ */
28
+ static of(element) {
29
+ return new Parser(syntax => [[element, syntax]]);
30
+ }
31
+ /**
32
+ * Transforms the result of this parser using the given function, without consuming any additional input.
33
+ */
34
+ map(f) {
35
+ return new Parser(syntax => this.#fn(syntax).map(([result, remaining]) => [f(result), remaining]));
36
+ }
37
+ /**
38
+ * Chains this parser with another parser that depends on the result of this parser.
39
+ */
40
+ then(f) {
41
+ return new Parser(syntax => this.#fn(syntax).flatMap(([result, remaining]) => f(result).#fn(remaining)));
42
+ }
43
+ /**
44
+ * A parser that always fails.
45
+ */
46
+ static zero = new Parser(() => []);
47
+ /**
48
+ * Combines this parser with another parser, trying this parser first and then the other parser.
49
+ */
50
+ or(other) {
51
+ return new Parser(syntax => [...this.#fn(syntax), ...other.#fn(syntax)]);
52
+ }
53
+ /**
54
+ * Combines this parser with another parser, trying this parser first and then the other parser only if the first parser fails. It only returns the first successful result.
55
+ */
56
+ orFirst(other) {
57
+ return new Parser(syntax => {
58
+ const result = this.#fn(syntax);
59
+ return result.length > 0 ? result.slice(0, 1) : other.#fn(syntax).slice(0, 1);
60
+ });
61
+ }
62
+ /**
63
+ * Combines this parser with another parser, trying this parser first and then the other parser only if the first parser fails. It only returns the first successful result.
64
+ *
65
+ * The suffix ‘W’ stands for ‘widening’, as this method allows you to combine parsers with different result types.
66
+ */
67
+ orFirstW(other) {
68
+ return new Parser((syntax) => {
69
+ const result = this.#fn(syntax);
70
+ return result.length > 0 ? result.slice(0, 1) : other.#fn(syntax).slice(0, 1);
71
+ });
72
+ }
73
+ /**
74
+ * Returns a parser that tries this parser and returns its result if it succeeds, but if this parser fails, it returns a parser that always succeeds with `undefined` without consuming any input.
75
+ */
76
+ optional() {
77
+ return this.orFirstW(Parser.of(undefined));
78
+ }
79
+ static satisfy(predicate) {
80
+ return Parser.item.then(char => (predicate(char) ? Parser.of(char) : Parser.zero));
81
+ }
82
+ /**
83
+ * Returns a parser that parses a specific string.
84
+ */
85
+ static string(string) {
86
+ return new Parser(syntax => syntax.startsWith(string)
87
+ ? [[syntax.slice(0, string.length), syntax.slice(string.length)]]
88
+ : []);
89
+ }
90
+ /**
91
+ * Returns a parser that parses zero or more occurrences of this parser, returning an array of the parsed results.
92
+ */
93
+ many() {
94
+ return this.many1().orFirst(Parser.of([]));
95
+ }
96
+ /**
97
+ * Returns a parser that parses one or more occurrences of this parser, returning an array of the parsed results.
98
+ *
99
+ * It fails if this parser does not succeed at least once.
100
+ */
101
+ many1() {
102
+ return this.then(result => this.many().then(results => Parser.of([result, ...results])));
103
+ }
104
+ /**
105
+ * Returns a parser that parses zero or more occurrences of this parser, separated by another parser, returning an array of the parsed results.
106
+ */
107
+ separatedBy(separator) {
108
+ return this.separatedBy1(separator).orFirst(Parser.of([]));
109
+ }
110
+ /**
111
+ * Returns a parser that parses one or more occurrences of this parser, separated by another parser, returning an array of the parsed results.
112
+ *
113
+ * It fails if this parser does not succeed at least once.
114
+ */
115
+ separatedBy1(separator) {
116
+ return this.then(result => separator
117
+ .then(() => this)
118
+ .many()
119
+ .then(results => Parser.of([result, ...results])));
120
+ }
121
+ /**
122
+ * Returns a parser that parses a string that matches the given regular expression pattern.
123
+ *
124
+ * The pattern must match at the beginning of the string (patterns that are designed like this might perform better), and the parser will return the matched substring as the parsed result.
125
+ */
126
+ static regex(pattern) {
127
+ return new Parser(syntax => {
128
+ const result = pattern.exec(syntax);
129
+ // checks for no match and not a match at the beginning of the string at the same time
130
+ return result?.index !== 0 ? [] : [[result[0], syntax.slice(result[0].length)]];
131
+ });
132
+ }
133
+ /**
134
+ * A parser that parses whitepace characters.
135
+ */
136
+ static space = Parser.regex(/^\s*/);
137
+ /**
138
+ * A parser that parses horizontal whitepace characters.
139
+ *
140
+ * This can be useful for parsing a language where newlines are significant.
141
+ */
142
+ static hspace = Parser.regex(/^[^\S\r\n]*/);
143
+ /**
144
+ * Throws away trailing whitespace characters before applying the given parser.
145
+ */
146
+ token() {
147
+ return this.then(result => Parser.space.then(() => Parser.of(result)));
148
+ }
149
+ /**
150
+ * Throws away trailing horizontal whitespace characters before applying the given parser.
151
+ *
152
+ * This can be useful for parsing a language where newlines are significant.
153
+ */
154
+ htoken() {
155
+ return this.then(result => Parser.hspace.then(() => Parser.of(result)));
156
+ }
157
+ /**
158
+ * Returns a parser that parses a specific string, ignoring trailing whitespace characters.
159
+ */
160
+ static symb(symbol) {
161
+ return Parser.string(symbol).token();
162
+ }
163
+ /**
164
+ * Returns a parser that parses a specific string, ignoring trailing horizontal whitespace characters.
165
+ *
166
+ * This can be useful for parsing a language where newlines are significant.
167
+ */
168
+ static hsymb(symbol) {
169
+ return Parser.string(symbol).htoken();
170
+ }
171
+ /**
172
+ * Creates a parser that parses a value using the given parser, surrounded by the given left and right parsers, and returns the parsed value.
173
+ */
174
+ between(left, right) {
175
+ return left.then(() => this).then(result => right.then(() => Parser.of(result)));
176
+ }
177
+ /**
178
+ * Returns a parser that applies the given parser to the input string without consuming any input, returning the result of the parser if it succeeds. If the parser fails, this lookahead parser also fails.
179
+ */
180
+ static lookahead(parser) {
181
+ return new Parser(syntax => {
182
+ const result = parser.parse(syntax)[0];
183
+ return result !== undefined ? [[result[0], syntax]] : [];
184
+ });
185
+ }
186
+ /**
187
+ * Returns a parser that applies the given parser to the input string without consuming any input, returning `undefined` if it fails. If the parser succeeds, this fails instead.
188
+ */
189
+ static negativeLookahead(parser) {
190
+ return new Parser(syntax => {
191
+ const result = parser.parse(syntax)[0];
192
+ return result === undefined ? [[undefined, syntax]] : [];
193
+ });
194
+ }
195
+ /**
196
+ * Runs the parser on the given syntax string, ignoring any leading whitespace.
197
+ *
198
+ * A complete parse is one where the remaining unparsed string is empty. However, this method does not enforce that, and it will return all possible parses, including those that do not consume the entire input.
199
+ */
200
+ apply(syntax) {
201
+ return Parser.space.then(() => this).parse(syntax);
202
+ }
203
+ /**
204
+ * Runs the parser on the given syntax string.
205
+ *
206
+ * A complete parse is one where the remaining unparsed string is empty. However, this method does not enforce that, and it will return all possible parses, including those that do not consume the entire input.
207
+ */
208
+ parse(syntax) {
209
+ return this.#fn(syntax);
210
+ }
211
+ static debugLog(parser) {
212
+ return new Parser(syntax => {
213
+ console.log("Parsing:", syntax);
214
+ const results = parser.parse(syntax);
215
+ console.log(results);
216
+ return results;
217
+ });
218
+ }
219
+ }