@futpib/parser 1.0.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 (228) hide show
  1. package/.editorconfig +12 -0
  2. package/.gitattributes +2 -0
  3. package/.github/workflows/main.yml +29 -0
  4. package/.yarn/releases/yarn-4.5.3.cjs +934 -0
  5. package/.yarnrc.yml +7 -0
  6. package/build/allSettledStream.d.ts +17 -0
  7. package/build/allSettledStream.js +21 -0
  8. package/build/allSettledStream.test.d.ts +1 -0
  9. package/build/allSettledStream.test.js +56 -0
  10. package/build/apk.d.ts +13 -0
  11. package/build/apk.js +1 -0
  12. package/build/apkParser.d.ts +3 -0
  13. package/build/apkParser.js +135 -0
  14. package/build/apkParser.test.d.ts +1 -0
  15. package/build/apkParser.test.js +22 -0
  16. package/build/arbitrarilySlicedAsyncInterable.d.ts +2 -0
  17. package/build/arbitrarilySlicedAsyncInterable.js +46 -0
  18. package/build/arbitrarilySlicedAsyncInterator.d.ts +2 -0
  19. package/build/arbitrarilySlicedAsyncInterator.js +17 -0
  20. package/build/arbitraryDosDate.d.ts +2 -0
  21. package/build/arbitraryDosDate.js +8 -0
  22. package/build/arbitraryDosDateTime.d.ts +2 -0
  23. package/build/arbitraryDosDateTime.js +9 -0
  24. package/build/arbitraryDosPermissions.d.ts +1 -0
  25. package/build/arbitraryDosPermissions.js +1 -0
  26. package/build/arbitraryFileSystemEntry.d.ts +14 -0
  27. package/build/arbitraryFileSystemEntry.js +12 -0
  28. package/build/arbitraryZip.d.ts +6 -0
  29. package/build/arbitraryZip.js +88 -0
  30. package/build/arbitraryZipEntry.d.ts +3 -0
  31. package/build/arbitraryZipEntry.js +26 -0
  32. package/build/arbitraryZipPermissions.d.ts +8 -0
  33. package/build/arbitraryZipPermissions.js +16 -0
  34. package/build/arbitraryZipStream.d.ts +5 -0
  35. package/build/arbitraryZipStream.js +50 -0
  36. package/build/arrayParser.d.ts +2 -0
  37. package/build/arrayParser.js +30 -0
  38. package/build/arrayParser.test.d.ts +1 -0
  39. package/build/arrayParser.test.js +9 -0
  40. package/build/bsonParser.d.ts +3 -0
  41. package/build/bsonParser.js +70 -0
  42. package/build/bsonParser.test.d.ts +1 -0
  43. package/build/bsonParser.test.js +24 -0
  44. package/build/createDisjunctionParser.d.ts +2 -0
  45. package/build/createDisjunctionParser.js +47 -0
  46. package/build/createExactParser.d.ts +2 -0
  47. package/build/createExactParser.js +12 -0
  48. package/build/createSequentialUnionParser.d.ts +2 -0
  49. package/build/createSequentialUnionParser.js +69 -0
  50. package/build/debugLogParser.d.ts +2 -0
  51. package/build/debugLogParser.js +23 -0
  52. package/build/disjunctionParser.d.ts +2 -0
  53. package/build/disjunctionParser.js +35 -0
  54. package/build/elementParser.d.ts +3 -0
  55. package/build/elementParser.js +1 -0
  56. package/build/endOfInputParser.d.ts +4 -0
  57. package/build/endOfInputParser.js +4 -0
  58. package/build/exactElementParser.d.ts +3 -0
  59. package/build/exactElementParser.js +6 -0
  60. package/build/exactSequenceParser.d.ts +2 -0
  61. package/build/exactSequenceParser.js +15 -0
  62. package/build/fixedLengthChunkParser.d.ts +2 -0
  63. package/build/fixedLengthChunkParser.js +12 -0
  64. package/build/fixedLengthParser.d.ts +2 -0
  65. package/build/fixedLengthParser.js +12 -0
  66. package/build/fixedLengthSequenceParser.d.ts +2 -0
  67. package/build/fixedLengthSequenceParser.js +16 -0
  68. package/build/index.d.ts +18 -0
  69. package/build/index.js +17 -0
  70. package/build/index.test.d.ts +1 -0
  71. package/build/index.test.js +2 -0
  72. package/build/inputChunkBuffer.d.ts +15 -0
  73. package/build/inputChunkBuffer.js +40 -0
  74. package/build/inputChunkBuffer.test.d.ts +1 -0
  75. package/build/inputChunkBuffer.test.js +34 -0
  76. package/build/inputCompanion.d.ts +18 -0
  77. package/build/inputCompanion.js +28 -0
  78. package/build/inputReader.d.ts +22 -0
  79. package/build/inputReader.js +105 -0
  80. package/build/inputReader.test.d.ts +1 -0
  81. package/build/inputReader.test.js +174 -0
  82. package/build/invariantDefined.d.ts +1 -0
  83. package/build/invariantDefined.js +5 -0
  84. package/build/invariantIdentity.d.ts +3 -0
  85. package/build/invariantIdentity.js +5 -0
  86. package/build/jsonParser.d.ts +3 -0
  87. package/build/jsonParser.js +133 -0
  88. package/build/jsonParser.test.d.ts +1 -0
  89. package/build/jsonParser.test.js +17 -0
  90. package/build/jsonParser2.d.ts +3 -0
  91. package/build/jsonParser2.js +52 -0
  92. package/build/jsonParser2.test.d.ts +1 -0
  93. package/build/jsonParser2.test.js +22 -0
  94. package/build/listParser.d.ts +2 -0
  95. package/build/listParser.js +34 -0
  96. package/build/negativeLookahead.d.ts +2 -0
  97. package/build/negativeLookahead.js +20 -0
  98. package/build/optionalParser.d.ts +2 -0
  99. package/build/optionalParser.js +23 -0
  100. package/build/parser.d.ts +10 -0
  101. package/build/parser.js +53 -0
  102. package/build/parser.test.d.ts +1 -0
  103. package/build/parser.test.js +126 -0
  104. package/build/parserAccessorParser.d.ts +2 -0
  105. package/build/parserAccessorParser.js +1 -0
  106. package/build/parserCompose.d.ts +3 -0
  107. package/build/parserCompose.js +7 -0
  108. package/build/parserContext.d.ts +48 -0
  109. package/build/parserContext.js +193 -0
  110. package/build/parserContext.test.d.ts +1 -0
  111. package/build/parserContext.test.js +115 -0
  112. package/build/parserCreatorCompose.d.ts +3 -0
  113. package/build/parserCreatorCompose.js +10 -0
  114. package/build/parserError.d.ts +43 -0
  115. package/build/parserError.js +61 -0
  116. package/build/parserImplementationInvariant.d.ts +2 -0
  117. package/build/parserImplementationInvariant.js +5 -0
  118. package/build/parserImplementationInvariantInvariant.d.ts +3 -0
  119. package/build/parserImplementationInvariantInvariant.js +15 -0
  120. package/build/parserInvariant.d.ts +4 -0
  121. package/build/parserInvariant.js +11 -0
  122. package/build/parserParsingInvariant.d.ts +1 -0
  123. package/build/parserParsingInvariant.js +1 -0
  124. package/build/promiseCompose.d.ts +1 -0
  125. package/build/promiseCompose.js +3 -0
  126. package/build/promiseFish.d.ts +1 -0
  127. package/build/promiseFish.js +3 -0
  128. package/build/sequence.d.ts +1 -0
  129. package/build/sequence.js +1 -0
  130. package/build/sequenceBuffer.d.ts +15 -0
  131. package/build/sequenceBuffer.js +40 -0
  132. package/build/sequenceBuffer.test.d.ts +1 -0
  133. package/build/sequenceBuffer.test.js +34 -0
  134. package/build/sequenceParser.d.ts +3 -0
  135. package/build/sequenceParser.js +10 -0
  136. package/build/skipParser.d.ts +2 -0
  137. package/build/skipParser.js +8 -0
  138. package/build/sliceBoundedParser.d.ts +2 -0
  139. package/build/sliceBoundedParser.js +24 -0
  140. package/build/sliceBoundedParser.test.d.ts +1 -0
  141. package/build/sliceBoundedParser.test.js +24 -0
  142. package/build/terminatedArrayParser.d.ts +2 -0
  143. package/build/terminatedArrayParser.js +27 -0
  144. package/build/terminatedSequenceParser.d.ts +2 -0
  145. package/build/terminatedSequenceParser.js +24 -0
  146. package/build/tupleParser.d.ts +11 -0
  147. package/build/tupleParser.js +17 -0
  148. package/build/unionParser.d.ts +2 -0
  149. package/build/unionParser.js +68 -0
  150. package/build/zip.d.ts +31 -0
  151. package/build/zip.js +1 -0
  152. package/build/zipEntry.d.ts +28 -0
  153. package/build/zipEntry.js +1 -0
  154. package/build/zipFile.d.ts +32 -0
  155. package/build/zipFile.js +1 -0
  156. package/build/zipFileEntry.d.ts +6 -0
  157. package/build/zipFileEntry.js +1 -0
  158. package/build/zipParser.d.ts +70 -0
  159. package/build/zipParser.js +255 -0
  160. package/build/zipParser.test.d.ts +1 -0
  161. package/build/zipParser.test.js +22 -0
  162. package/package.json +52 -0
  163. package/readme.md +17 -0
  164. package/renovate.json +6 -0
  165. package/src/allSettledStream.test.ts +59 -0
  166. package/src/allSettledStream.test.ts.md +52 -0
  167. package/src/allSettledStream.test.ts.snap +0 -0
  168. package/src/allSettledStream.ts +44 -0
  169. package/src/apk.ts +16 -0
  170. package/src/apkParser.test.ts +30 -0
  171. package/src/apkParser.test.ts.md +268 -0
  172. package/src/apkParser.test.ts.snap +0 -0
  173. package/src/apkParser.ts +327 -0
  174. package/src/arbitrarilySlicedAsyncInterable.ts +73 -0
  175. package/src/arbitrarilySlicedAsyncInterator.ts +25 -0
  176. package/src/arbitraryDosDateTime.ts +10 -0
  177. package/src/arbitraryFileSystemEntry.ts +36 -0
  178. package/src/arbitraryZip.ts +132 -0
  179. package/src/arbitraryZipPermissions.ts +25 -0
  180. package/src/arbitraryZipStream.ts +60 -0
  181. package/src/arrayParser.test.ts +11 -0
  182. package/src/arrayParser.ts +35 -0
  183. package/src/bsonParser.test.ts +37 -0
  184. package/src/bsonParser.ts +131 -0
  185. package/src/debugLogParser.ts +51 -0
  186. package/src/disjunctionParser.ts +55 -0
  187. package/src/elementParser.ts +4 -0
  188. package/src/endOfInputParser.ts +11 -0
  189. package/src/exactElementParser.ts +20 -0
  190. package/src/exactSequenceParser.ts +27 -0
  191. package/src/fixedLengthSequenceParser.ts +24 -0
  192. package/src/index.test.ts +3 -0
  193. package/src/index.ts +76 -0
  194. package/src/inputCompanion.ts +43 -0
  195. package/src/inputReader.test.ts +238 -0
  196. package/src/inputReader.ts +159 -0
  197. package/src/invariantDefined.ts +6 -0
  198. package/src/invariantIdentity.ts +8 -0
  199. package/src/jsonParser.test.ts +26 -0
  200. package/src/jsonParser.ts +225 -0
  201. package/src/listParser.ts +52 -0
  202. package/src/negativeLookahead.ts +26 -0
  203. package/src/optionalParser.ts +27 -0
  204. package/src/parser.test.ts +176 -0
  205. package/src/parser.test.ts.md +68 -0
  206. package/src/parser.test.ts.snap +0 -0
  207. package/src/parser.ts +103 -0
  208. package/src/parserAccessorParser.ts +3 -0
  209. package/src/parserContext.test.ts +154 -0
  210. package/src/parserContext.ts +316 -0
  211. package/src/parserCreatorCompose.ts +23 -0
  212. package/src/parserError.ts +85 -0
  213. package/src/parserImplementationInvariant.ts +6 -0
  214. package/src/parserInvariant.ts +26 -0
  215. package/src/promiseCompose.ts +7 -0
  216. package/src/sequence.ts +10 -0
  217. package/src/sequenceBuffer.test.ts +50 -0
  218. package/src/sequenceBuffer.ts +58 -0
  219. package/src/skipParser.ts +12 -0
  220. package/src/sliceBoundedParser.test.ts +28 -0
  221. package/src/sliceBoundedParser.ts +30 -0
  222. package/src/terminatedArrayParser.ts +40 -0
  223. package/src/tupleParser.ts +33 -0
  224. package/src/unionParser.ts +104 -0
  225. package/src/zip.ts +48 -0
  226. package/src/zipParser.test.ts +31 -0
  227. package/src/zipParser.ts +501 -0
  228. package/tsconfig.json +109 -0
@@ -0,0 +1,36 @@
1
+ import * as fc from 'fast-check';
2
+
3
+ type FileSystemDirectory = {
4
+ type: 'directory';
5
+ name: string;
6
+ entries: FileSystemEntry[];
7
+ };
8
+
9
+ type FileSystemFile = {
10
+ type: 'file';
11
+ name: string;
12
+ content: Uint8Array;
13
+ };
14
+
15
+ export type FileSystemEntry =
16
+ | FileSystemFile
17
+ | FileSystemDirectory
18
+ ;
19
+
20
+ export const arbitraryFileSystemEntry = fc.letrec((tie) => ({
21
+ entry: fc.oneof<[
22
+ fc.Arbitrary<FileSystemFile>,
23
+ fc.Arbitrary<FileSystemDirectory>,
24
+ ]>(
25
+ fc.record<FileSystemFile>({
26
+ type: fc.constant('file'),
27
+ name: fc.string(),
28
+ content: fc.uint8Array(),
29
+ }),
30
+ fc.record<FileSystemDirectory>({
31
+ type: fc.constant('directory'),
32
+ name: fc.string(),
33
+ entries: fc.array(tie('entry') as fc.Arbitrary<FileSystemEntry>),
34
+ }),
35
+ ),
36
+ })).entry;
@@ -0,0 +1,132 @@
1
+ import * as fc from 'fast-check';
2
+ import { Zip, ZipDirectoryEntry, ZipEntry, ZipFileEntry } from './zip.js';
3
+ import { arbitraryDosDateTime } from './arbitraryDosDateTime.js';
4
+ import { createArbitraryZipPermissions } from './arbitraryZipPermissions.js';
5
+ import invariant from 'invariant';
6
+
7
+ const DOS_DIRECTORY_FLAG = 0b00010000;
8
+
9
+ const arbitraryPath = fc.string({ minLength: 1 }).filter(path => {
10
+ const pathSegments = path.split('/');
11
+ const pathHasEmptySegments = pathSegments.some(segment => segment.length === 0);
12
+
13
+ if (pathHasEmptySegments) {
14
+ return false;
15
+ }
16
+
17
+ return true;
18
+ });
19
+
20
+ const createArbitraryZipEntry = (platform: 'unix' | 'dos') => fc.oneof<[
21
+ fc.Arbitrary<ZipFileEntry>,
22
+ fc.Arbitrary<ZipDirectoryEntry>,
23
+ ]>(
24
+ fc.record<ZipFileEntry>({
25
+ type: fc.constant('file'),
26
+ path: arbitraryPath,
27
+ date: arbitraryDosDateTime,
28
+ comment: fc.string(),
29
+ permissions: createArbitraryZipPermissions(platform),
30
+ compression: fc.oneof(
31
+ fc.constant('store' as const),
32
+ fc.constant('deflate' as const),
33
+ ),
34
+ content: fc.uint8Array(),
35
+ }).filter(zipFileEntry => {
36
+ if (
37
+ zipFileEntry.content.length === 0
38
+ && zipFileEntry.compression === 'deflate'
39
+ ) {
40
+ return false;
41
+ }
42
+
43
+ if (
44
+ zipFileEntry.permissions.type === 'dos'
45
+ && zipFileEntry.permissions.dosPermissions & DOS_DIRECTORY_FLAG
46
+ ) {
47
+ return false;
48
+ }
49
+
50
+ return true;
51
+ }),
52
+ fc.record<ZipDirectoryEntry>({
53
+ type: fc.constant('directory'),
54
+ path: arbitraryPath,
55
+ date: arbitraryDosDateTime,
56
+ comment: fc.string(),
57
+ permissions: createArbitraryZipPermissions(platform),
58
+ }).filter(zipDirectoryEntry => {
59
+ if (
60
+ zipDirectoryEntry.permissions.type === 'dos'
61
+ && !(zipDirectoryEntry.permissions.dosPermissions & DOS_DIRECTORY_FLAG)
62
+ ) {
63
+ return false;
64
+ }
65
+
66
+ return true;
67
+ }),
68
+ );
69
+
70
+ const createArbitraryZipEntries = (platform: 'unix' | 'dos') => (
71
+ fc
72
+ .array(createArbitraryZipEntry(platform))
73
+ .map(entries => {
74
+ const seenPaths = new Set<string>();
75
+ const seenDirectoryPaths = new Set<string>();
76
+
77
+ const normalizedEntries: typeof entries = [];
78
+
79
+ for (const entry of entries) {
80
+ if (seenPaths.has(entry.path)) {
81
+ continue;
82
+ }
83
+
84
+ seenPaths.add(entry.path);
85
+
86
+ if (entry.type === 'directory') {
87
+ seenDirectoryPaths.add(entry.path);
88
+
89
+ continue;
90
+ }
91
+
92
+ invariant(entry.type === 'file', 'Unexpected entry type %s.', entry.type);
93
+
94
+ const directories = entry.path.split('/').slice(0, -1);
95
+
96
+ for (const depth of Array.from({ length: directories.length }).map((_, index) => index)) {
97
+ const directoryPath = directories.slice(0, depth + 1).join('/');
98
+
99
+ if (directoryPath.length === 0) {
100
+ continue;
101
+ }
102
+
103
+ if (!seenDirectoryPaths.has(directoryPath)) {
104
+ normalizedEntries.push({
105
+ type: 'directory',
106
+ path: directoryPath,
107
+ date: entry.date,
108
+ comment: '',
109
+ permissions: entry.permissions.type === 'dos' ? {
110
+ type: 'dos',
111
+ dosPermissions: entry.permissions.dosPermissions | DOS_DIRECTORY_FLAG,
112
+ } : entry.permissions,
113
+ });
114
+
115
+ seenDirectoryPaths.add(directoryPath);
116
+ }
117
+ }
118
+
119
+ normalizedEntries.push(entry);
120
+ }
121
+
122
+ return normalizedEntries;
123
+ })
124
+ );
125
+
126
+ export const arbitraryZip = fc.oneof(
127
+ fc.constant('unix' as const),
128
+ fc.constant('dos' as const),
129
+ ).chain(platform => fc.record<Zip>({
130
+ comment: fc.string(),
131
+ entries: createArbitraryZipEntries(platform),
132
+ }));
@@ -0,0 +1,25 @@
1
+ import * as fc from 'fast-check';
2
+ import { ZipDosPermissions, ZipUnixPermissions } from './zip.js';
3
+
4
+ const arbitraryZipUnixPermissions = fc.record<ZipUnixPermissions>({
5
+ type: fc.constant('unix'),
6
+ unixPermissions: (
7
+ fc.nat({ max: 0b0000000111111111 })
8
+ .filter((unixPermissions) => unixPermissions > 0)
9
+ ),
10
+ });
11
+
12
+ const arbitraryZipDosPermissions = fc.record<ZipDosPermissions>({
13
+ type: fc.constant('dos'),
14
+ dosPermissions: (
15
+ fc.nat({ max: 0b00111111 })
16
+ ),
17
+ });
18
+
19
+ export const createArbitraryZipPermissions = (type: 'unix' | 'dos') => {
20
+ if (type === 'unix') {
21
+ return arbitraryZipUnixPermissions;
22
+ }
23
+
24
+ return arbitraryZipDosPermissions;
25
+ };
@@ -0,0 +1,60 @@
1
+ import JSZip from 'jszip';
2
+ import { ZipEntry } from './zip.js';
3
+ import { arbitraryZip } from './arbitraryZip.js';
4
+
5
+ function addZipEntryToZip(zip: JSZip, zipEntry: ZipEntry) {
6
+ const options = {
7
+ comment: zipEntry.comment,
8
+ date: zipEntry.date,
9
+ unixPermissions: zipEntry.permissions.type === 'unix' ? zipEntry.permissions.unixPermissions : undefined,
10
+ dosPermissions: zipEntry.permissions.type === 'dos' ? zipEntry.permissions.dosPermissions : undefined,
11
+ };
12
+
13
+ if (zipEntry.type === 'file') {
14
+ zip.file<'uint8array'>(zipEntry.path, zipEntry.content, {
15
+ compression: zipEntry.compression.toUpperCase() as any,
16
+ compressionOptions: undefined,
17
+ binary: true,
18
+ ...options,
19
+ });
20
+ } else {
21
+ zip.file(zipEntry.path, null, {
22
+ dir: true,
23
+ ...options,
24
+ });
25
+ }
26
+ }
27
+
28
+ export const arbitraryZipStream = arbitraryZip.map(zip => {
29
+ const jsZip = new JSZip();
30
+
31
+ for (const zipEntry of zip.entries) {
32
+ addZipEntryToZip(jsZip, zipEntry);
33
+ }
34
+
35
+ const zipInternalStream = jsZip.generateInternalStream({
36
+ type: 'uint8array',
37
+ comment: zip.comment,
38
+ platform: zip.entries.at(0)?.permissions.type.toUpperCase() as any,
39
+ });
40
+
41
+ const zipStream = new ReadableStream<Uint8Array>({
42
+ start(controller) {
43
+ zipInternalStream.on('data', (chunk: Uint8Array) => {
44
+ controller.enqueue(chunk);
45
+ });
46
+
47
+ zipInternalStream.on('end', () => {
48
+ controller.close();
49
+ });
50
+
51
+ zipInternalStream.on('error', (error: Error) => {
52
+ controller.error(error);
53
+ });
54
+
55
+ zipInternalStream.resume();
56
+ },
57
+ });
58
+
59
+ return [ zip, zipStream ] as const;
60
+ });
@@ -0,0 +1,11 @@
1
+ import test from 'ava';
2
+ import { createArrayParser } from './arrayParser.js';
3
+ import { Parser, runParser } from './parser.js';
4
+ import { stringInputCompanion } from './inputCompanion.js';
5
+
6
+ test('does not loop forever with a child parser that does not consume anything', async t => {
7
+ const parser: Parser<undefined[], string> = createArrayParser(async () => undefined);
8
+ const result = await runParser(parser, 'foo', stringInputCompanion);
9
+
10
+ t.deepEqual(result, []);
11
+ });
@@ -0,0 +1,35 @@
1
+ import { getParserName, Parser, setParserName } from "./parser.js";
2
+ import { ParserParsingFailedError } from "./parserError.js";
3
+
4
+ export const createArrayParser = <ElementOutput, Sequence>(
5
+ elementParser: Parser<ElementOutput, Sequence>,
6
+ ): Parser<ElementOutput[], Sequence> => {
7
+ const arrayParser: Parser<ElementOutput[], Sequence> = async parserContext => {
8
+ const elements: ElementOutput[] = [];
9
+
10
+ while (true) {
11
+ const elementParserContext = parserContext.lookahead();
12
+ const initialPosition = elementParserContext.position;
13
+ try {
14
+ const element = await elementParser(elementParserContext);
15
+ if (elementParserContext.position === initialPosition) {
16
+ return elements;
17
+ }
18
+ elements.push(element);
19
+ elementParserContext.unlookahead();
20
+ } catch (error) {
21
+ if (error instanceof ParserParsingFailedError) {
22
+ return elements;
23
+ }
24
+
25
+ throw error;
26
+ } finally {
27
+ elementParserContext.dispose();
28
+ }
29
+ }
30
+ };
31
+
32
+ setParserName(arrayParser, getParserName(elementParser, 'anonymousArrayChild') + '*');
33
+
34
+ return arrayParser;
35
+ };
@@ -0,0 +1,37 @@
1
+ import { testProp, fc } from '@fast-check/ava';
2
+ import { BSON } from 'bson';
3
+ import { bsonDocumentParser } from './bsonParser.js';
4
+ import { arbitrarilySlicedAsyncIterator } from './arbitrarilySlicedAsyncInterator.js';
5
+ import { runParser } from './parser.js';
6
+ import { uint8ArrayInputCompanion } from './inputCompanion.js';
7
+
8
+ testProp(
9
+ 'bson',
10
+ [
11
+ arbitrarilySlicedAsyncIterator(
12
+ fc
13
+ .json()
14
+ .filter(jsonString => {
15
+ // BSON.serialize does not support non-object top-level values
16
+ const jsonValue = JSON.parse(jsonString);
17
+
18
+ return (
19
+ jsonValue !== null
20
+ && typeof jsonValue === 'object'
21
+ && !Array.isArray(jsonValue)
22
+ );
23
+ })
24
+ .map(jsonString => BSON.serialize(JSON.parse(jsonString))),
25
+ ),
26
+ ],
27
+ async (t, [ bsonUint8Array, bsonUint8ArrayChunkIterator ]) => {
28
+ const expected = BSON.deserialize(bsonUint8Array);
29
+
30
+ const actual = await runParser(bsonDocumentParser, bsonUint8ArrayChunkIterator, uint8ArrayInputCompanion);
31
+
32
+ t.deepEqual(actual, expected);
33
+ },
34
+ {
35
+ verbose: true,
36
+ },
37
+ );
@@ -0,0 +1,131 @@
1
+ import { type JsonObject, type JsonValue } from 'type-fest';
2
+ import { getParserName, setParserName, type Parser } from './parser.js';
3
+ import { invariantDefined } from './invariantDefined.js';
4
+ import { createFixedLengthSequenceParser } from './fixedLengthSequenceParser.js';
5
+ import { promiseCompose } from './promiseCompose.js';
6
+ import { createTupleParser } from './tupleParser.js';
7
+ import { createSkipParser } from './skipParser.js';
8
+ import { createParserAccessorParser } from './parserAccessorParser.js';
9
+ import { createTerminatedArrayParser } from './terminatedArrayParser.js';
10
+ import { createElementParser } from './elementParser.js';
11
+ import { createExactElementParser } from './exactElementParser.js';
12
+ import { createUnionParser } from './unionParser.js';
13
+ import { parserCreatorCompose } from './parserCreatorCompose.js';
14
+
15
+ const createFixedLengthBufferParser = (length: number): Parser<Buffer, Uint8Array> => promiseCompose(createFixedLengthSequenceParser<Uint8Array>(length), sequence => Buffer.from(sequence));
16
+
17
+ const buffer1Parser = createFixedLengthBufferParser(1);
18
+ const buffer4Parser = createFixedLengthBufferParser(4);
19
+ const buffer8Parser = createFixedLengthBufferParser(8);
20
+
21
+ const elementParser: Parser<number, Uint8Array> = createElementParser();
22
+
23
+ const nullByteParser: Parser<number, Uint8Array> = createExactElementParser(0);
24
+
25
+ const cstringParser: Parser<string, Uint8Array> = promiseCompose(
26
+ createTerminatedArrayParser(
27
+ parserCreatorCompose(
28
+ () => elementParser,
29
+ (byte: number) => async parserContext => parserContext.invariant(byte, 'Expected non-null byte'),
30
+ )(),
31
+ nullByteParser,
32
+ ),
33
+ ([sequence]) => Buffer.from(sequence).toString('utf8'),
34
+ );
35
+
36
+ const doubleParser: Parser<number, Uint8Array> = promiseCompose(buffer8Parser, buffer => buffer.readDoubleLE(0));
37
+ const int32Parser: Parser<number, Uint8Array> = promiseCompose(buffer4Parser, buffer => buffer.readInt32LE(0));
38
+ const uint32Parser: Parser<number, Uint8Array> = promiseCompose(buffer4Parser, buffer => buffer.readUInt32LE(0));
39
+
40
+ const createFixedLengthStringParser = (length: number): Parser<string, Uint8Array> => promiseCompose(
41
+ createFixedLengthBufferParser(length),
42
+ buffer => buffer.toString('utf8'),
43
+ );
44
+
45
+ const createFixedLengthNullTerminatedStringParser = (lengthWihoutNullTerminator: number): Parser<string, Uint8Array> => promiseCompose(
46
+ createTupleParser([
47
+ createFixedLengthStringParser(lengthWihoutNullTerminator),
48
+ nullByteParser,
49
+ ]),
50
+ ([string]) => string,
51
+ );
52
+
53
+ const bsonStringParser: Parser<string, Uint8Array> = async parserContext => {
54
+ const stringSize = await uint32Parser(parserContext);
55
+
56
+ return createFixedLengthNullTerminatedStringParser(stringSize - 1)(parserContext);
57
+ };
58
+
59
+ const bsonArrayParser = promiseCompose(
60
+ createTupleParser([
61
+ createSkipParser(4),
62
+ createParserAccessorParser(() => bsonElementListParser),
63
+ ]),
64
+ ([ _, elements ]) => elements.map(([ _, value ]) => value),
65
+ );
66
+
67
+ const bsonBooleanParser: Parser<boolean, Uint8Array> = async parserContext => {
68
+ const booleanValue = invariantDefined(await parserContext.read(0), 'Unexpected end of input');
69
+
70
+ return booleanValue === 1;
71
+ };
72
+
73
+ const int8Parser: Parser<number, Uint8Array> = promiseCompose(buffer1Parser, buffer => buffer.readInt8(0));
74
+
75
+ const createExactInt8Parser = (value: number): Parser<number, Uint8Array> => parserCreatorCompose(
76
+ () => int8Parser,
77
+ actualValue => async parserContext => {
78
+ parserContext.invariant(actualValue === value, 'Expected %s, got %s', value, actualValue);
79
+
80
+ return actualValue;
81
+ },
82
+ )();
83
+
84
+ const createBsonElementParser = <ValueOutput>(
85
+ elementType: number,
86
+ valueParser: Parser<ValueOutput, Uint8Array>,
87
+ ): Parser<[ string, ValueOutput ], Uint8Array> => setParserName(
88
+ promiseCompose(
89
+ createTupleParser([
90
+ createExactInt8Parser(elementType),
91
+ cstringParser,
92
+ valueParser,
93
+ ]),
94
+ ([ _, elementName, elementValue ]) => [ elementName, elementValue ],
95
+ ),
96
+ `bsonElementParser(${elementType}, ${getParserName(valueParser)})`,
97
+ );
98
+
99
+ const bsonDoubleElementParser = createBsonElementParser(1, doubleParser);
100
+ const bsonStringElementParser = createBsonElementParser(2, bsonStringParser);
101
+ const bsonDocumentElementParser = createBsonElementParser(3, createParserAccessorParser(() => bsonDocumentParser));
102
+ const bsonArrayElementParser = createBsonElementParser(4, bsonArrayParser);
103
+ const bsonBooleanElementParser = createBsonElementParser(8, bsonBooleanParser);
104
+ const bsonNullElementParser = createBsonElementParser(10, async () => null);
105
+ const bsonInt32ElementParser = createBsonElementParser(16, int32Parser);
106
+
107
+ const bsonElementParser: Parser<[ string, JsonValue ], Uint8Array> = createUnionParser([
108
+ bsonDoubleElementParser,
109
+ bsonStringElementParser,
110
+ bsonDocumentElementParser,
111
+ bsonArrayElementParser,
112
+ bsonBooleanElementParser,
113
+ bsonNullElementParser,
114
+ bsonInt32ElementParser,
115
+ ]);
116
+
117
+ const bsonElementListParser: Parser<Array<[ string, JsonValue ]>, Uint8Array> = promiseCompose(
118
+ createTerminatedArrayParser(
119
+ bsonElementParser,
120
+ nullByteParser,
121
+ ),
122
+ ([elements]) => elements,
123
+ );
124
+
125
+ export const bsonDocumentParser: Parser<JsonObject, Uint8Array> = promiseCompose(
126
+ createTupleParser([
127
+ createSkipParser(4),
128
+ bsonElementListParser,
129
+ ]),
130
+ ([ _, elements ]) => Object.fromEntries(elements),
131
+ );
@@ -0,0 +1,51 @@
1
+ import { getParserName, Parser, setParserName } from "./parser.js";
2
+
3
+ export const createDebugLogParser = <Output, Sequence>(
4
+ childParser: Parser<Output, Sequence>,
5
+ ): Parser<Output, Sequence> => {
6
+ let idCounter = 0;
7
+
8
+ const debugLogParser: typeof childParser = async parserContext => {
9
+ const id = idCounter++;
10
+ const initialPosition = parserContext.position;
11
+
12
+ console.debug(
13
+ '%s %s: started (position: %s)',
14
+ getParserName(childParser),
15
+ id,
16
+ initialPosition,
17
+ );
18
+
19
+ try {
20
+ const result = await childParser(parserContext);
21
+
22
+ console.debug(
23
+ '%s %s: finished (position: %s, consumed: %s): %o',
24
+ getParserName(childParser),
25
+ id,
26
+ parserContext.position,
27
+ parserContext.position - initialPosition,
28
+ result,
29
+ );
30
+
31
+ return result;
32
+ } catch (error) {
33
+ console.debug(
34
+ '%s %s: failed (position: %s, consumed: %s): %o',
35
+ getParserName(childParser),
36
+ id,
37
+ parserContext.position,
38
+ parserContext.position - initialPosition,
39
+ error,
40
+ );
41
+
42
+ throw error;
43
+ }
44
+ };
45
+
46
+ return setParserName(debugLogParser, [
47
+ 'debugLog(',
48
+ getParserName(childParser),
49
+ ')',
50
+ ].join(''));
51
+ };
@@ -0,0 +1,55 @@
1
+ import { getParserName, setParserName, type Parser } from './parser.js';
2
+ import { ParserParsingFailedError } from './parserError.js';
3
+ import { parserImplementationInvariant } from './parserImplementationInvariant.js';
4
+
5
+ export const createDisjunctionParser = <
6
+ Output,
7
+ Sequence,
8
+ >(
9
+ childParsers: Array<Parser<any, Sequence, any>>,
10
+ ): Parser<Output, Sequence, unknown> => {
11
+ parserImplementationInvariant(childParsers.length > 0, 'Disjunction parser must have at least one child parser.');
12
+
13
+ const disjunctionParser: Parser<Output, Sequence, unknown> = async parserContext => {
14
+ const parserParsingFailedErrors: ParserParsingFailedError[] = [];
15
+
16
+ for (const childParser of childParsers) {
17
+ const childParserContext = parserContext.lookahead({
18
+ debugName: getParserName(childParser, 'anonymousDisjunctionChild'),
19
+ });
20
+
21
+ const [ childParserResult ] = await Promise.allSettled([ childParser(childParserContext) ]);
22
+
23
+ if (childParserResult.status === 'fulfilled') {
24
+ const successfulParserOutput = childParserResult.value;
25
+
26
+ childParserContext.unlookahead();
27
+ childParserContext.dispose();
28
+
29
+ return successfulParserOutput;
30
+ }
31
+
32
+ const error = childParserResult.reason;
33
+
34
+ if (error instanceof ParserParsingFailedError) {
35
+ parserParsingFailedErrors.push(error);
36
+ } else {
37
+ throw error;
38
+ }
39
+ }
40
+
41
+ parserContext.invariantJoin(
42
+ false,
43
+ parserParsingFailedErrors,
44
+ 'No disjunction child parser succeeded.',
45
+ );
46
+ };
47
+
48
+ const name = [
49
+ '(',
50
+ ...childParsers.map(childParser => getParserName(childParser, 'anonymousDiscjunctionChild')).join('|'),
51
+ ')',
52
+ ].join('');
53
+
54
+ return setParserName(disjunctionParser, name);
55
+ };
@@ -0,0 +1,4 @@
1
+ import { Parser } from "./parser.js";
2
+ import { DeriveSequenceElement } from "./sequence.js";
3
+
4
+ export const createElementParser = <Sequence, Element = DeriveSequenceElement<Sequence>>(): Parser<Element, Sequence, Element> => parserContext => parserContext.read(0);
@@ -0,0 +1,11 @@
1
+ import { Parser } from "./parser.js";
2
+ import { DeriveSequenceElement } from "./sequence.js";
3
+
4
+ export const createEndOfInputParser = <Sequence, Element = DeriveSequenceElement<Sequence>>(): Parser<void, Sequence, Element> => async parserContext => {
5
+ parserContext.invariant(
6
+ await parserContext.peek(0) === undefined,
7
+ 'Expected end of input.'
8
+ );
9
+ }
10
+
11
+ export const endOfInputParser = createEndOfInputParser<any, any>();
@@ -0,0 +1,20 @@
1
+ import { type Parser } from './parser.js';
2
+ import { DeriveSequenceElement } from './sequence.js';
3
+
4
+ export const createExactElementParser = <
5
+ Sequence,
6
+ Element = DeriveSequenceElement<Sequence>,
7
+ >(element: Element): Parser<Element, Sequence, Element> => async parserContext => {
8
+ const actualElement = await parserContext.peek(0);
9
+
10
+ parserContext.invariant(
11
+ actualElement === element,
12
+ 'Expected %s, got %s',
13
+ element,
14
+ actualElement,
15
+ );
16
+
17
+ parserContext.skip(1);
18
+
19
+ return element;
20
+ };
@@ -0,0 +1,27 @@
1
+ import { inspect } from 'util';
2
+ import { setParserName, type Parser } from './parser.js';
3
+
4
+ export const createExactSequenceParser = <Sequence>(sequence: Sequence) => {
5
+ const exactSequenceParser: Parser<Sequence, Sequence, unknown> = async parserContext => {
6
+ const length = parserContext.length(sequence);
7
+
8
+ for (let index = 0; index < length; index++) {
9
+ const element = await parserContext.read(0);
10
+ const expectedElement = parserContext.at(sequence, index);
11
+
12
+ parserContext.invariant(
13
+ element === expectedElement,
14
+ 'Expected "%s", got "%s", at index %s',
15
+ expectedElement,
16
+ element,
17
+ index,
18
+ );
19
+ }
20
+
21
+ return sequence;
22
+ };
23
+
24
+ setParserName(exactSequenceParser, inspect(sequence));
25
+
26
+ return exactSequenceParser;
27
+ }
@@ -0,0 +1,24 @@
1
+ import { setParserName, type Parser } from './parser.js';
2
+ import { parserImplementationInvariant } from './parserImplementationInvariant.js';
3
+
4
+ export const createFixedLengthSequenceParser = <Sequence>(lengthInput: bigint | number) => {
5
+ const length = BigInt(lengthInput);
6
+
7
+ parserImplementationInvariant(length >= 0n, 'Length must be non-negative got %s.', length);
8
+
9
+ const fixedLengthSequenceParser: Parser<Sequence, Sequence, unknown> = async parserContext => {
10
+ const elements = [];
11
+
12
+ for (let i = 0n; i < length; i++) {
13
+ const element = await parserContext.read(0);
14
+
15
+ elements.push(element);
16
+ }
17
+
18
+ return parserContext.from(elements);
19
+ };
20
+
21
+ setParserName(fixedLengthSequenceParser, `.{${length}}`);
22
+
23
+ return fixedLengthSequenceParser;
24
+ };
@@ -0,0 +1,3 @@
1
+ import test from 'ava';
2
+
3
+ test.todo('TODO');