jsonc-effect 0.2.0 → 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.
package/package.json CHANGED
@@ -1,58 +1,52 @@
1
1
  {
2
- "name": "jsonc-effect",
3
- "version": "0.2.0",
4
- "private": false,
5
- "description": "Pure Effect-TS implementation of a JSONC (JSON with Comments) parser",
6
- "keywords": [
7
- "jsonc",
8
- "json",
9
- "json-with-comments",
10
- "parser",
11
- "scanner",
12
- "lexer",
13
- "ast",
14
- "effect",
15
- "effect-ts",
16
- "schema",
17
- "typed-errors",
18
- "config",
19
- "tsconfig",
20
- "formatting",
21
- "visitor"
22
- ],
23
- "homepage": "https://github.com/spencerbeggs/jsonc-effect#readme",
24
- "bugs": {
25
- "url": "https://github.com/spencerbeggs/jsonc-effect/issues"
26
- },
27
- "repository": {
28
- "type": "git",
29
- "url": "https://github.com/spencerbeggs/jsonc-effect.git"
30
- },
31
- "license": "MIT",
32
- "author": {
33
- "name": "C. Spencer Beggs",
34
- "email": "spencer@beggs.codes",
35
- "url": "https://spencerbeg.gs"
36
- },
37
- "type": "module",
38
- "exports": {
39
- ".": {
40
- "types": "./index.d.ts",
41
- "import": "./index.js"
42
- }
43
- },
44
- "dependencies": {
45
- "effect": "^3.19.19"
46
- },
47
- "files": [
48
- "!jsonc-effect.api.json",
49
- "!tsconfig.json",
50
- "!tsdoc.json",
51
- "LICENSE",
52
- "README.md",
53
- "index.d.ts",
54
- "index.js",
55
- "package.json",
56
- "tsdoc-metadata.json"
57
- ]
2
+ "name": "jsonc-effect",
3
+ "version": "0.3.0",
4
+ "private": false,
5
+ "description": "Pure Effect-TS implementation of a JSONC (JSON with Comments) parser",
6
+ "keywords": [
7
+ "jsonc",
8
+ "json",
9
+ "json-with-comments",
10
+ "parser",
11
+ "scanner",
12
+ "lexer",
13
+ "ast",
14
+ "effect",
15
+ "effect-ts",
16
+ "schema",
17
+ "typed-errors",
18
+ "config",
19
+ "tsconfig",
20
+ "formatting",
21
+ "visitor"
22
+ ],
23
+ "homepage": "https://github.com/spencerbeggs/jsonc-effect#readme",
24
+ "bugs": {
25
+ "url": "https://github.com/spencerbeggs/jsonc-effect/issues"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/spencerbeggs/jsonc-effect.git"
30
+ },
31
+ "license": "MIT",
32
+ "author": {
33
+ "name": "C. Spencer Beggs",
34
+ "email": "spencer@beggs.codes",
35
+ "url": "https://spencerbeg.gs"
36
+ },
37
+ "sideEffects": false,
38
+ "type": "module",
39
+ "exports": {
40
+ ".": {
41
+ "types": "./index.d.ts",
42
+ "import": "./index.js"
43
+ },
44
+ "./package.json": "./package.json"
45
+ },
46
+ "peerDependencies": {
47
+ "effect": "^3.21.0"
48
+ },
49
+ "engines": {
50
+ "node": ">=24.11.0"
51
+ }
58
52
  }
package/parse.js ADDED
@@ -0,0 +1,548 @@
1
+ import { JsoncParseError, JsoncParseErrorDetail } from "./errors.js";
2
+ import { createScanner } from "./scanner.js";
3
+ import { Effect, Option } from "effect";
4
+
5
+ //#region src/parse.ts
6
+ /**
7
+ * JSONC Parser — converts token stream into JavaScript values or AST nodes.
8
+ *
9
+ * Pure Effect implementation using recursive descent parsing.
10
+ * Reference: Microsoft's jsonc-parser parser design (MIT).
11
+ *
12
+ * @packageDocumentation
13
+ */
14
+ /**
15
+ * Parse a JSONC string into a JavaScript value.
16
+ *
17
+ * @param text - JSONC string to parse
18
+ * @param options - Optional {@link JsoncParseOptions} controlling comment and
19
+ * trailing-comma handling.
20
+ * @returns `Effect<unknown, JsoncParseError>` — succeeds with the parsed value
21
+ * or fails with a {@link JsoncParseError} containing every error encountered.
22
+ *
23
+ * @remarks
24
+ * The return type is `unknown` (not `any`) so consumers are forced to narrow
25
+ * the result, which is safer in Effect pipelines. By default
26
+ * `allowTrailingComma` is `true`, matching common JSONC conventions used in
27
+ * VS Code settings and `tsconfig.json`.
28
+ *
29
+ * @see {@link parseTree} — parse into an AST instead of a plain value
30
+ * @see {@link JsoncParseOptions} — available parse options
31
+ * @see {@link JsoncParseError} — the tagged error type on the failure channel
32
+ *
33
+ * @example
34
+ * Basic parsing:
35
+ * ```ts
36
+ * import { Effect } from "effect";
37
+ * import { parse } from "jsonc-effect";
38
+ *
39
+ * const value = Effect.runSync(parse('{ "key": 42 }'));
40
+ * console.log(value); // { key: 42 }
41
+ * ```
42
+ *
43
+ * @example
44
+ * Parsing with options:
45
+ * ```ts
46
+ * import { Effect } from "effect";
47
+ * import { parse } from "jsonc-effect";
48
+ *
49
+ * const value = Effect.runSync(
50
+ * parse('{ "key": 42 }', { disallowComments: true }),
51
+ * );
52
+ * ```
53
+ *
54
+ * @example
55
+ * Error handling with `catchTag`:
56
+ * ```ts
57
+ * import { Effect } from "effect";
58
+ * import { parse } from "jsonc-effect";
59
+ *
60
+ * const program = parse("{ bad }").pipe(
61
+ * Effect.catchTag("JsoncParseError", (err) =>
62
+ * Effect.succeed({ fallback: true, errors: err.errors }),
63
+ * ),
64
+ * );
65
+ *
66
+ * const result = Effect.runSync(program);
67
+ * console.log(result);
68
+ * ```
69
+ *
70
+ * @example
71
+ * Using `Effect.gen`:
72
+ * ```ts
73
+ * import { Effect } from "effect";
74
+ * import { parse } from "jsonc-effect";
75
+ *
76
+ * const program = Effect.gen(function* () {
77
+ * const config = yield* parse('{ "port": 3000 }');
78
+ * return config;
79
+ * });
80
+ *
81
+ * const result = Effect.runSync(program);
82
+ * console.log(result); // { port: 3000 }
83
+ * ```
84
+ *
85
+ * @privateRemarks
86
+ * Uses {@link createScanner} internally with a recursive descent parser.
87
+ * The scanner is created with `ignoreTrivia = false` so the parser can
88
+ * report comment-related errors when `disallowComments` is set.
89
+ *
90
+ * @public
91
+ */
92
+ const parse = (text, options) => Effect.sync(() => parseInternal(text, options ?? {}, false)).pipe(Effect.flatMap(({ value, errors }) => {
93
+ if (errors.length > 0) return Effect.fail(new JsoncParseError({
94
+ errors,
95
+ text,
96
+ ...options !== void 0 ? { options } : {}
97
+ }));
98
+ return Effect.succeed(value);
99
+ }));
100
+ /**
101
+ * Parse a JSONC string into an immutable AST.
102
+ *
103
+ * @param text - JSONC string to parse
104
+ * @param options - Optional {@link JsoncParseOptions} controlling comment and
105
+ * trailing-comma handling.
106
+ * @returns `Effect<Option<JsoncNode>, JsoncParseError>` — succeeds with
107
+ * `Option.some(root)` for non-empty documents or `Option.none()` when the
108
+ * input is empty (and `allowEmptyContent` is set).
109
+ *
110
+ * @remarks
111
+ * The returned AST is immutable and does **not** contain parent pointers, which
112
+ * keeps nodes safe to share across fibers. Use the AST navigation helpers
113
+ * ({@link findNode}, {@link getNodeValue}) to traverse and extract values from
114
+ * the tree.
115
+ *
116
+ * `Option.none()` is returned only when the document contains no value tokens
117
+ * and `allowEmptyContent` is enabled; otherwise an empty document produces a
118
+ * {@link JsoncParseError}.
119
+ *
120
+ * @see {@link parse} — parse into a plain JavaScript value instead of an AST
121
+ * @see {@link findNode} — locate a node by JSON path segments
122
+ * @see {@link getNodeValue} — extract the JavaScript value from a subtree
123
+ * @see {@link JsoncNode} — the AST node type
124
+ *
125
+ * @example
126
+ * Parsing a JSONC string and navigating the tree:
127
+ * ```ts
128
+ * import { Effect, Option } from "effect";
129
+ * import { parseTree } from "jsonc-effect";
130
+ *
131
+ * const program = Effect.gen(function* () {
132
+ * const maybeRoot = yield* parseTree('{ "a": [1, 2, 3] }');
133
+ * if (Option.isSome(maybeRoot)) {
134
+ * const root = maybeRoot.value;
135
+ * console.log(root.type); // "object"
136
+ * console.log(root.children?.length); // 1
137
+ * }
138
+ * });
139
+ *
140
+ * Effect.runSync(program);
141
+ * ```
142
+ *
143
+ * @privateRemarks
144
+ * Internally the parser builds a mutable tree using `MutableJsoncNode` and
145
+ * casts to the readonly `JsoncNode` on output.
146
+ *
147
+ * @public
148
+ */
149
+ const parseTree = (text, options) => Effect.sync(() => parseInternal(text, options ?? {}, true)).pipe(Effect.flatMap(({ root, errors }) => {
150
+ if (errors.length > 0) return Effect.fail(new JsoncParseError({
151
+ errors,
152
+ text,
153
+ ...options !== void 0 ? { options } : {}
154
+ }));
155
+ return Effect.succeed(root ? Option.some(root) : Option.none());
156
+ }));
157
+ /**
158
+ * Remove all comments from JSONC text, producing valid JSON.
159
+ *
160
+ * @param text - JSONC string to strip comments from
161
+ * @param replaceCh - Optional single character used to replace each character of
162
+ * every comment. When provided, the output has the **same length** as the
163
+ * input so that all offsets are preserved (line breaks inside block comments
164
+ * are kept as-is).
165
+ * @returns `Effect<string>` — the text with all comments removed (or replaced).
166
+ *
167
+ * @remarks
168
+ * When `replaceCh` is omitted the comment text is simply deleted, which means
169
+ * character offsets in the output no longer match the original document. Pass a
170
+ * space (`" "`) as `replaceCh` to keep offsets stable — this is useful when you
171
+ * need to correlate positions between the original JSONC and the stripped JSON.
172
+ *
173
+ * @see {@link parse} — parse JSONC directly without a stripping step
174
+ *
175
+ * @example
176
+ * Basic comment stripping:
177
+ * ```ts
178
+ * import { Effect } from "effect";
179
+ * import { stripComments } from "jsonc-effect";
180
+ *
181
+ * const json = Effect.runSync(
182
+ * stripComments('{ "a": 1 // comment\n}'),
183
+ * );
184
+ * console.log(json); // '{ "a": 1 \n}'
185
+ * ```
186
+ *
187
+ * @example
188
+ * Using a replacement character to preserve offsets:
189
+ * ```ts
190
+ * import { Effect } from "effect";
191
+ * import { stripComments } from "jsonc-effect";
192
+ *
193
+ * const json = Effect.runSync(
194
+ * stripComments('{ "a": 1 // comment\n}', " "),
195
+ * );
196
+ * console.log(json.length === '{ "a": 1 // comment\n}'.length); // true
197
+ * ```
198
+ *
199
+ * @public
200
+ */
201
+ const stripComments = (text, replaceCh) => Effect.sync(() => {
202
+ const scanner = createScanner(text);
203
+ const parts = [];
204
+ let lastOffset = 0;
205
+ let kind;
206
+ do {
207
+ kind = scanner.scan();
208
+ const offset = scanner.getTokenOffset();
209
+ const length = scanner.getTokenLength();
210
+ if (kind === "LineComment" || kind === "BlockComment") {
211
+ if (lastOffset < offset) parts.push(text.substring(lastOffset, offset));
212
+ if (replaceCh !== void 0) for (let i = 0; i < length; i++) {
213
+ const ch = text.charCodeAt(offset + i);
214
+ parts.push(ch === 10 || ch === 13 ? text[offset + i] : replaceCh);
215
+ }
216
+ lastOffset = offset + length;
217
+ }
218
+ } while (kind !== "EOF");
219
+ if (lastOffset < text.length) parts.push(text.substring(lastOffset));
220
+ return parts.join("");
221
+ });
222
+ function parseInternal(text, options, buildTree) {
223
+ const scanner = createScanner(text, false);
224
+ const errors = [];
225
+ const disallowComments = options.disallowComments ?? false;
226
+ const allowTrailingComma = options.allowTrailingComma ?? true;
227
+ const allowEmptyContent = options.allowEmptyContent ?? false;
228
+ let currentToken = "Unknown";
229
+ function token() {
230
+ return currentToken;
231
+ }
232
+ function scanNext() {
233
+ for (;;) {
234
+ currentToken = scanner.scan();
235
+ switch (scanner.getTokenError()) {
236
+ case "InvalidUnicode":
237
+ handleError("InvalidUnicode");
238
+ break;
239
+ case "InvalidEscapeCharacter":
240
+ handleError("InvalidEscapeCharacter");
241
+ break;
242
+ case "UnexpectedEndOfNumber":
243
+ handleError("InvalidNumberFormat");
244
+ break;
245
+ case "UnexpectedEndOfComment":
246
+ handleError("UnexpectedEndOfComment");
247
+ break;
248
+ case "UnexpectedEndOfString":
249
+ handleError("UnexpectedEndOfString");
250
+ break;
251
+ case "InvalidCharacter":
252
+ handleError("InvalidCharacter");
253
+ break;
254
+ }
255
+ switch (currentToken) {
256
+ case "LineComment":
257
+ case "BlockComment":
258
+ if (disallowComments) handleError("InvalidCommentToken");
259
+ break;
260
+ case "Trivia":
261
+ case "LineBreak": break;
262
+ default: return currentToken;
263
+ }
264
+ }
265
+ }
266
+ function handleError(code, skipUntilAfter = [], skipUntil = []) {
267
+ errors.push(new JsoncParseErrorDetail({
268
+ code,
269
+ message: formatError(code, scanner.getTokenOffset()),
270
+ offset: scanner.getTokenOffset(),
271
+ length: scanner.getTokenLength(),
272
+ startLine: scanner.getTokenStartLine(),
273
+ startCharacter: scanner.getTokenStartCharacter()
274
+ }));
275
+ if (skipUntilAfter.length > 0 || skipUntil.length > 0) {
276
+ let t = token();
277
+ while (t !== "EOF") {
278
+ if (skipUntilAfter.includes(t)) {
279
+ scanNext();
280
+ break;
281
+ }
282
+ if (skipUntil.includes(t)) break;
283
+ t = scanNext();
284
+ }
285
+ }
286
+ }
287
+ function parseValue() {
288
+ switch (token()) {
289
+ case "OpenBracket": return parseArray();
290
+ case "OpenBrace": return parseObject();
291
+ case "String": return parseString();
292
+ case "Number": return parseNumber();
293
+ case "True":
294
+ scanNext();
295
+ return true;
296
+ case "False":
297
+ scanNext();
298
+ return false;
299
+ case "Null":
300
+ scanNext();
301
+ return null;
302
+ default: return;
303
+ }
304
+ }
305
+ function parseString() {
306
+ const value = scanner.getTokenValue();
307
+ scanNext();
308
+ return value;
309
+ }
310
+ function parseNumber() {
311
+ const value = Number.parseFloat(scanner.getTokenValue());
312
+ scanNext();
313
+ return value;
314
+ }
315
+ function parseArray() {
316
+ scanNext();
317
+ const arr = [];
318
+ let needsComma = false;
319
+ while (token() !== "CloseBracket" && token() !== "EOF") {
320
+ if (token() === "Comma") {
321
+ if (!needsComma) handleError("ValueExpected");
322
+ scanNext();
323
+ if (token() === "CloseBracket" && allowTrailingComma) break;
324
+ } else if (needsComma) handleError("CommaExpected");
325
+ const value = parseValue();
326
+ if (value === void 0) handleError("ValueExpected", [], ["CloseBracket", "Comma"]);
327
+ else arr.push(value);
328
+ needsComma = true;
329
+ }
330
+ if (token() !== "CloseBracket") handleError("CloseBracketExpected");
331
+ else scanNext();
332
+ return arr;
333
+ }
334
+ function parseObject() {
335
+ scanNext();
336
+ const obj = {};
337
+ let needsComma = false;
338
+ while (token() !== "CloseBrace" && token() !== "EOF") {
339
+ if (token() === "Comma") {
340
+ if (!needsComma) handleError("PropertyNameExpected");
341
+ scanNext();
342
+ if (token() === "CloseBrace" && allowTrailingComma) break;
343
+ } else if (needsComma) handleError("CommaExpected");
344
+ if (token() !== "String") {
345
+ handleError("PropertyNameExpected", [], ["CloseBrace", "Comma"]);
346
+ continue;
347
+ }
348
+ const key = scanner.getTokenValue();
349
+ scanNext();
350
+ if (token() !== "Colon") {
351
+ handleError("ColonExpected", [], ["CloseBrace", "Comma"]);
352
+ continue;
353
+ }
354
+ scanNext();
355
+ const value = parseValue();
356
+ if (value === void 0) handleError("ValueExpected", [], ["CloseBrace", "Comma"]);
357
+ else obj[key] = value;
358
+ needsComma = true;
359
+ }
360
+ if (token() !== "CloseBrace") handleError("CloseBraceExpected");
361
+ else scanNext();
362
+ return obj;
363
+ }
364
+ function parseValueTree() {
365
+ switch (token()) {
366
+ case "OpenBracket": return parseArrayTree();
367
+ case "OpenBrace": return parseObjectTree();
368
+ case "String": {
369
+ const node = {
370
+ type: "string",
371
+ offset: scanner.getTokenOffset(),
372
+ length: 0,
373
+ value: scanner.getTokenValue()
374
+ };
375
+ scanNext();
376
+ node.length = scanner.getTokenOffset() - node.offset;
377
+ return node;
378
+ }
379
+ case "Number": {
380
+ const node = {
381
+ type: "number",
382
+ offset: scanner.getTokenOffset(),
383
+ length: 0,
384
+ value: Number.parseFloat(scanner.getTokenValue())
385
+ };
386
+ scanNext();
387
+ node.length = scanner.getTokenOffset() - node.offset;
388
+ return node;
389
+ }
390
+ case "True": {
391
+ const node = {
392
+ type: "boolean",
393
+ offset: scanner.getTokenOffset(),
394
+ length: 0,
395
+ value: true
396
+ };
397
+ scanNext();
398
+ node.length = scanner.getTokenOffset() - node.offset;
399
+ return node;
400
+ }
401
+ case "False": {
402
+ const node = {
403
+ type: "boolean",
404
+ offset: scanner.getTokenOffset(),
405
+ length: 0,
406
+ value: false
407
+ };
408
+ scanNext();
409
+ node.length = scanner.getTokenOffset() - node.offset;
410
+ return node;
411
+ }
412
+ case "Null": {
413
+ const node = {
414
+ type: "null",
415
+ offset: scanner.getTokenOffset(),
416
+ length: 0,
417
+ value: null
418
+ };
419
+ scanNext();
420
+ node.length = scanner.getTokenOffset() - node.offset;
421
+ return node;
422
+ }
423
+ default: return;
424
+ }
425
+ }
426
+ function parseArrayTree() {
427
+ const node = {
428
+ type: "array",
429
+ offset: scanner.getTokenOffset(),
430
+ length: 0,
431
+ children: []
432
+ };
433
+ scanNext();
434
+ let needsComma = false;
435
+ while (token() !== "CloseBracket" && token() !== "EOF") {
436
+ if (token() === "Comma") {
437
+ if (!needsComma) handleError("ValueExpected");
438
+ scanNext();
439
+ if (token() === "CloseBracket" && allowTrailingComma) break;
440
+ } else if (needsComma) handleError("CommaExpected");
441
+ const child = parseValueTree();
442
+ if (child) node.children.push(child);
443
+ else handleError("ValueExpected", [], ["CloseBracket", "Comma"]);
444
+ needsComma = true;
445
+ }
446
+ if (token() !== "CloseBracket") handleError("CloseBracketExpected");
447
+ else scanNext();
448
+ node.length = scanner.getTokenOffset() - node.offset;
449
+ return node;
450
+ }
451
+ function parseObjectTree() {
452
+ const node = {
453
+ type: "object",
454
+ offset: scanner.getTokenOffset(),
455
+ length: 0,
456
+ children: []
457
+ };
458
+ scanNext();
459
+ let needsComma = false;
460
+ while (token() !== "CloseBrace" && token() !== "EOF") {
461
+ if (token() === "Comma") {
462
+ if (!needsComma) handleError("PropertyNameExpected");
463
+ scanNext();
464
+ if (token() === "CloseBrace" && allowTrailingComma) break;
465
+ } else if (needsComma) handleError("CommaExpected");
466
+ if (token() !== "String") {
467
+ handleError("PropertyNameExpected", [], ["CloseBrace", "Comma"]);
468
+ continue;
469
+ }
470
+ const property = {
471
+ type: "property",
472
+ offset: scanner.getTokenOffset(),
473
+ length: 0,
474
+ children: []
475
+ };
476
+ const keyNode = {
477
+ type: "string",
478
+ offset: scanner.getTokenOffset(),
479
+ length: 0,
480
+ value: scanner.getTokenValue()
481
+ };
482
+ scanNext();
483
+ keyNode.length = scanner.getTokenOffset() - keyNode.offset;
484
+ property.children.push(keyNode);
485
+ if (token() !== "Colon") {
486
+ handleError("ColonExpected", [], ["CloseBrace", "Comma"]);
487
+ property.length = scanner.getTokenOffset() - property.offset;
488
+ node.children.push(property);
489
+ continue;
490
+ }
491
+ property.colonOffset = scanner.getTokenOffset();
492
+ scanNext();
493
+ const valueNode = parseValueTree();
494
+ if (valueNode) property.children.push(valueNode);
495
+ else handleError("ValueExpected", [], ["CloseBrace", "Comma"]);
496
+ property.length = scanner.getTokenOffset() - property.offset;
497
+ node.children.push(property);
498
+ needsComma = true;
499
+ }
500
+ if (token() !== "CloseBrace") handleError("CloseBraceExpected");
501
+ else scanNext();
502
+ node.length = scanner.getTokenOffset() - node.offset;
503
+ return node;
504
+ }
505
+ scanNext();
506
+ if (buildTree) {
507
+ const root = parseValueTree();
508
+ if (token() !== "EOF") handleError("EndOfFileExpected");
509
+ if (!root && !allowEmptyContent) handleError("ValueExpected");
510
+ return {
511
+ value: void 0,
512
+ root,
513
+ errors
514
+ };
515
+ }
516
+ const value = parseValue();
517
+ if (token() !== "EOF") handleError("EndOfFileExpected");
518
+ if (value === void 0 && !allowEmptyContent) handleError("ValueExpected");
519
+ return {
520
+ value,
521
+ root: void 0,
522
+ errors
523
+ };
524
+ }
525
+ function formatError(code, offset) {
526
+ switch (code) {
527
+ case "InvalidSymbol": return `Invalid symbol at offset ${offset}`;
528
+ case "InvalidNumberFormat": return `Invalid number format at offset ${offset}`;
529
+ case "PropertyNameExpected": return `Property name expected at offset ${offset}`;
530
+ case "ValueExpected": return `Value expected at offset ${offset}`;
531
+ case "ColonExpected": return `Colon expected at offset ${offset}`;
532
+ case "CommaExpected": return `Comma expected at offset ${offset}`;
533
+ case "CloseBraceExpected": return `Close brace expected at offset ${offset}`;
534
+ case "CloseBracketExpected": return `Close bracket expected at offset ${offset}`;
535
+ case "EndOfFileExpected": return `End of file expected at offset ${offset}`;
536
+ case "InvalidCommentToken": return `Comments not allowed at offset ${offset}`;
537
+ case "UnexpectedEndOfComment": return `Unexpected end of comment at offset ${offset}`;
538
+ case "UnexpectedEndOfString": return `Unexpected end of string at offset ${offset}`;
539
+ case "UnexpectedEndOfNumber": return `Unexpected end of number at offset ${offset}`;
540
+ case "InvalidUnicode": return `Invalid unicode escape at offset ${offset}`;
541
+ case "InvalidEscapeCharacter": return `Invalid escape character at offset ${offset}`;
542
+ case "InvalidCharacter": return `Invalid character at offset ${offset}`;
543
+ default: return `Parse error at offset ${offset}`;
544
+ }
545
+ }
546
+
547
+ //#endregion
548
+ export { parse, parseTree, stripComments };