@effected/toml 0.1.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.js ADDED
@@ -0,0 +1,10 @@
1
+ import { TomlLocalDate, TomlLocalDateTime, TomlLocalTime, TomlOffsetDateTime } from "./TomlDateTime.js";
2
+ import { TomlArray, TomlArrayTableHeader, TomlBoolean, TomlDateTimeLiteral, TomlExpression, TomlFloat, TomlInlineEntry, TomlInlineTable, TomlInteger, TomlKey, TomlKeyKind, TomlKeyValue, TomlString, TomlStringStyle, TomlTableHeader, TomlTrivia, TomlValueNode } from "./TomlNode.js";
3
+ import { TomlDiagnostic, TomlErrorCode, TomlLexErrorCode, TomlParseErrorCode, TomlSemanticErrorCode, TomlStringifyErrorCode } from "./TomlDiagnostic.js";
4
+ import { Toml, TomlParseError, TomlStringifyError, TomlStringifyOptions } from "./Toml.js";
5
+ import { TomlDocument } from "./TomlDocument.js";
6
+ import { TomlEdit, TomlRange } from "./TomlEdit.js";
7
+ import { TomlFormat, TomlFormattingOptions, TomlModificationError } from "./TomlFormat.js";
8
+ import { TomlVisitor, TomlVisitorEvent } from "./TomlVisitor.js";
9
+
10
+ export { Toml, TomlArray, TomlArrayTableHeader, TomlBoolean, TomlDateTimeLiteral, TomlDiagnostic, TomlDocument, TomlEdit, TomlErrorCode, TomlExpression, TomlFloat, TomlFormat, TomlFormattingOptions, TomlInlineEntry, TomlInlineTable, TomlInteger, TomlKey, TomlKeyKind, TomlKeyValue, TomlLexErrorCode, TomlLocalDate, TomlLocalDateTime, TomlLocalTime, TomlModificationError, TomlOffsetDateTime, TomlParseError, TomlParseErrorCode, TomlRange, TomlSemanticErrorCode, TomlString, TomlStringStyle, TomlStringifyError, TomlStringifyErrorCode, TomlStringifyOptions, TomlTableHeader, TomlTrivia, TomlValueNode, TomlVisitor, TomlVisitorEvent };
@@ -0,0 +1,53 @@
1
+ //#region src/internal/diagnostics.ts
2
+ const TOML_LEX_ERROR_CODES = [
3
+ "InvalidUtf8",
4
+ "UnterminatedString",
5
+ "InvalidEscape",
6
+ "InvalidUnicodeEscape",
7
+ "ControlCharacterInString",
8
+ "ControlCharacterInComment",
9
+ "InvalidCharacter",
10
+ "BareCarriageReturn"
11
+ ];
12
+ const TOML_PARSE_ERROR_CODES = [
13
+ "ExpectedKey",
14
+ "ExpectedEquals",
15
+ "ExpectedValue",
16
+ "ExpectedNewline",
17
+ "ExpectedTableHeaderClose",
18
+ "UnterminatedArray",
19
+ "UnterminatedInlineTable",
20
+ "TrailingCommaInInlineTable",
21
+ "NewlineInInlineTable",
22
+ "InvalidValue",
23
+ "InvalidNumber",
24
+ "IntegerOutOfRange",
25
+ "InvalidDateTime",
26
+ "NestingDepthExceeded"
27
+ ];
28
+ const TOML_SEMANTIC_ERROR_CODES = [
29
+ "DuplicateKey",
30
+ "TableRedefined",
31
+ "ArrayOfTablesConflict",
32
+ "DottedKeyConflict",
33
+ "InlineTableExtended"
34
+ ];
35
+ const TOML_STRINGIFY_ERROR_CODES = [
36
+ "CircularReference",
37
+ "UnsupportedValue",
38
+ "IntegerOutOfRange",
39
+ "NestingDepthExceeded"
40
+ ];
41
+ /** The engine's only throw carrier besides GuardExceeded. */
42
+ var RawTomlError = class extends Error {
43
+ diagnostic;
44
+ _tag = "RawTomlError";
45
+ constructor(diagnostic) {
46
+ super(diagnostic.message);
47
+ this.diagnostic = diagnostic;
48
+ }
49
+ };
50
+ const isRawTomlError = (u) => u instanceof RawTomlError;
51
+
52
+ //#endregion
53
+ export { RawTomlError, TOML_LEX_ERROR_CODES, TOML_PARSE_ERROR_CODES, TOML_SEMANTIC_ERROR_CODES, TOML_STRINGIFY_ERROR_CODES, isRawTomlError };
@@ -0,0 +1,24 @@
1
+ //#region src/internal/limits.ts
2
+ /**
3
+ * Raw guard-trip signal. The engine throws it; ONLY the public modules catch
4
+ * it and materialize the typed error. It must never escape a public entry
5
+ * point as a defect.
6
+ */
7
+ var GuardExceeded = class extends Error {
8
+ reason;
9
+ limit;
10
+ actual;
11
+ offset;
12
+ _tag = "GuardExceeded";
13
+ constructor(reason, limit, actual, offset) {
14
+ super(`${reason}: limit ${limit}, actual ${actual}`);
15
+ this.reason = reason;
16
+ this.limit = limit;
17
+ this.actual = actual;
18
+ this.offset = offset;
19
+ }
20
+ };
21
+ const isGuardExceeded = (u) => u instanceof GuardExceeded;
22
+
23
+ //#endregion
24
+ export { GuardExceeded, isGuardExceeded };
@@ -0,0 +1,408 @@
1
+ import { RawTomlError } from "./diagnostics.js";
2
+ import { GuardExceeded } from "./limits.js";
3
+ import { TomlLocalDate, TomlLocalDateTime, TomlLocalTime, TomlOffsetDateTime } from "../TomlDateTime.js";
4
+ import { TomlArray, TomlArrayTableHeader, TomlBoolean, TomlDateTimeLiteral, TomlFloat, TomlInlineEntry, TomlInlineTable, TomlInteger, TomlKey, TomlKeyValue, TomlString, TomlTableHeader, TomlTrivia } from "../TomlNode.js";
5
+ import { assertValidUnicode, classifyValueToken, scanBareKey, scanBasicString, scanComment, scanLiteralString, scanMultilineBasicString, scanMultilineLiteralString, scanNewline, scanValueToken, scanWhitespace, skipBom } from "./scanner.js";
6
+
7
+ //#region src/internal/parser.ts
8
+ const LF = 10;
9
+ const CR = 13;
10
+ const QUOTE = 34;
11
+ const HASH = 35;
12
+ const APOSTROPHE = 39;
13
+ const COMMA = 44;
14
+ const DOT = 46;
15
+ const EQUALS = 61;
16
+ const LEFT_BRACKET = 91;
17
+ const RIGHT_BRACKET = 93;
18
+ const LEFT_BRACE = 123;
19
+ const RIGHT_BRACE = 125;
20
+ const raise = (code, message, offset, length) => {
21
+ throw new RawTomlError({
22
+ code,
23
+ message,
24
+ offset,
25
+ length
26
+ });
27
+ };
28
+ /** Decoded trailing-comment text: the raw text after `#` with one leading space stripped. */
29
+ const decodeComment = (raw) => raw.startsWith(" ") ? raw.slice(1) : raw;
30
+ /** One simple key: bare, basic-quoted or literal-quoted. */
31
+ const parseSimpleKey = (source, pos) => {
32
+ const code = source.charCodeAt(pos);
33
+ if (code === QUOTE) {
34
+ const scanned = scanBasicString(source, pos);
35
+ return {
36
+ node: new TomlKey({
37
+ value: scanned.value,
38
+ kind: "basic",
39
+ offset: pos,
40
+ length: scanned.end - pos
41
+ }),
42
+ end: scanned.end
43
+ };
44
+ }
45
+ if (code === APOSTROPHE) {
46
+ const scanned = scanLiteralString(source, pos);
47
+ return {
48
+ node: new TomlKey({
49
+ value: scanned.value,
50
+ kind: "literal",
51
+ offset: pos,
52
+ length: scanned.end - pos
53
+ }),
54
+ end: scanned.end
55
+ };
56
+ }
57
+ const bare = scanBareKey(source, pos);
58
+ if (bare.end === pos) return raise("ExpectedKey", "expected a key", pos, 1);
59
+ return {
60
+ node: new TomlKey({
61
+ value: bare.value,
62
+ kind: "bare",
63
+ offset: pos,
64
+ length: bare.end - pos
65
+ }),
66
+ end: bare.end
67
+ };
68
+ };
69
+ /** A dotted key path: `simple-key ( ws "." ws simple-key )*`. */
70
+ const parseKeyPath = (source, pos) => {
71
+ const keys = [];
72
+ const first = parseSimpleKey(source, pos);
73
+ keys.push(first.node);
74
+ let i = first.end;
75
+ for (;;) {
76
+ const afterWs = scanWhitespace(source, i);
77
+ if (source.charCodeAt(afterWs) !== DOT) break;
78
+ const keyStart = scanWhitespace(source, afterWs + 1);
79
+ const next = parseSimpleKey(source, keyStart);
80
+ keys.push(next.node);
81
+ i = next.end;
82
+ }
83
+ return {
84
+ node: keys,
85
+ end: i
86
+ };
87
+ };
88
+ /** The end of an expression line: ws + optional comment + newline (or EOF), else ExpectedNewline. */
89
+ const parseLineEnd = (source, pos) => {
90
+ let i = scanWhitespace(source, pos);
91
+ let comment;
92
+ if (source.charCodeAt(i) === HASH) {
93
+ const scanned = scanComment(source, i);
94
+ comment = decodeComment(scanned.value);
95
+ i = scanned.end;
96
+ }
97
+ if (i >= source.length) return {
98
+ ...comment !== void 0 ? { comment } : {},
99
+ end: i
100
+ };
101
+ const after = scanNewline(source, i);
102
+ if (after === i) return raise("ExpectedNewline", "expected a newline after the expression", i, 1);
103
+ return {
104
+ ...comment !== void 0 ? { comment } : {},
105
+ end: after
106
+ };
107
+ };
108
+ /** Skip whitespace, newlines and comments inside array brackets. */
109
+ const skipArrayGap = (source, pos) => {
110
+ let i = pos;
111
+ for (;;) {
112
+ i = scanWhitespace(source, i);
113
+ if (source.charCodeAt(i) === HASH) {
114
+ i = scanComment(source, i).end;
115
+ continue;
116
+ }
117
+ const after = scanNewline(source, i);
118
+ if (after !== i) {
119
+ i = after;
120
+ continue;
121
+ }
122
+ return i;
123
+ }
124
+ };
125
+ /** Guard the inline-table single-line rule and unterminated end-of-input. */
126
+ const checkInsideInlineTable = (source, pos, openPos) => {
127
+ if (pos >= source.length) raise("UnterminatedInlineTable", "inline table not closed before end of input", openPos, pos - openPos);
128
+ const code = source.charCodeAt(pos);
129
+ if (code === LF || code === CR) raise("NewlineInInlineTable", "inline tables must fit on a single line", pos, 1);
130
+ };
131
+ /** Whether a token that classified as `number` spells a float (never called on hex/oct/bin). */
132
+ const isFloatToken = (token) => !/^0[xob]/.test(token) && (/[.eE]/.test(token) || token.includes("inf") || token.includes("nan"));
133
+ /** An array value starting at `[`; `depth` is this array's own nesting count. */
134
+ const parseArray = (source, openPos, depth) => {
135
+ if (depth > 256) throw new GuardExceeded("NestingDepthExceeded", 256, depth, openPos);
136
+ const items = [];
137
+ let i = openPos + 1;
138
+ for (;;) {
139
+ i = skipArrayGap(source, i);
140
+ if (i >= source.length) raise("UnterminatedArray", "array not closed before end of input", openPos, i - openPos);
141
+ if (source.charCodeAt(i) === RIGHT_BRACKET) {
142
+ i += 1;
143
+ break;
144
+ }
145
+ const item = parseValue(source, i, depth);
146
+ items.push(item.node);
147
+ i = skipArrayGap(source, item.end);
148
+ const code = source.charCodeAt(i);
149
+ if (code === COMMA) {
150
+ i += 1;
151
+ continue;
152
+ }
153
+ if (code === RIGHT_BRACKET) {
154
+ i += 1;
155
+ break;
156
+ }
157
+ if (i >= source.length) raise("UnterminatedArray", "array not closed before end of input", openPos, i - openPos);
158
+ raise("UnterminatedArray", "expected , or ] in array", i, 1);
159
+ }
160
+ return {
161
+ node: new TomlArray({
162
+ items,
163
+ offset: openPos,
164
+ length: i - openPos
165
+ }),
166
+ end: i
167
+ };
168
+ };
169
+ /** An inline table starting at `{`; `depth` is this table's own nesting count. */
170
+ const parseInlineTable = (source, openPos, depth) => {
171
+ if (depth > 256) throw new GuardExceeded("NestingDepthExceeded", 256, depth, openPos);
172
+ const entries = [];
173
+ let i = scanWhitespace(source, openPos + 1);
174
+ checkInsideInlineTable(source, i, openPos);
175
+ if (source.charCodeAt(i) === RIGHT_BRACE) return {
176
+ node: new TomlInlineTable({
177
+ entries,
178
+ offset: openPos,
179
+ length: i + 1 - openPos
180
+ }),
181
+ end: i + 1
182
+ };
183
+ for (;;) {
184
+ const entryStart = i;
185
+ const keyPath = parseKeyPath(source, i);
186
+ i = scanWhitespace(source, keyPath.end);
187
+ checkInsideInlineTable(source, i, openPos);
188
+ if (source.charCodeAt(i) !== EQUALS) raise("ExpectedEquals", "expected = after key", i, 1);
189
+ i = scanWhitespace(source, i + 1);
190
+ checkInsideInlineTable(source, i, openPos);
191
+ const value = parseValue(source, i, depth);
192
+ entries.push(new TomlInlineEntry({
193
+ keyPath: keyPath.node,
194
+ value: value.node,
195
+ offset: entryStart,
196
+ length: value.end - entryStart
197
+ }));
198
+ i = scanWhitespace(source, value.end);
199
+ checkInsideInlineTable(source, i, openPos);
200
+ const code = source.charCodeAt(i);
201
+ if (code === RIGHT_BRACE) {
202
+ i += 1;
203
+ break;
204
+ }
205
+ if (code === COMMA) {
206
+ const commaPos = i;
207
+ i = scanWhitespace(source, i + 1);
208
+ checkInsideInlineTable(source, i, openPos);
209
+ if (source.charCodeAt(i) === RIGHT_BRACE) raise("TrailingCommaInInlineTable", "inline tables may not end with a trailing comma", commaPos, 1);
210
+ continue;
211
+ }
212
+ raise("UnterminatedInlineTable", "expected , or } in inline table", i, 1);
213
+ }
214
+ return {
215
+ node: new TomlInlineTable({
216
+ entries,
217
+ offset: openPos,
218
+ length: i - openPos
219
+ }),
220
+ end: i
221
+ };
222
+ };
223
+ /** Wrap a classified scalar token into its value node. */
224
+ const scalarNode = (source, pos) => {
225
+ const token = scanValueToken(source, pos);
226
+ if (token.value === "") return raise("ExpectedValue", "expected a value", pos, 1);
227
+ const scalar = classifyValueToken(token.value, pos);
228
+ const length = token.end - pos;
229
+ if (typeof scalar === "boolean") return {
230
+ node: new TomlBoolean({
231
+ value: scalar,
232
+ offset: pos,
233
+ length
234
+ }),
235
+ end: token.end
236
+ };
237
+ if (typeof scalar === "bigint") return {
238
+ node: new TomlInteger({
239
+ value: scalar,
240
+ offset: pos,
241
+ length
242
+ }),
243
+ end: token.end
244
+ };
245
+ if (typeof scalar === "number") return {
246
+ node: isFloatToken(token.value) ? new TomlFloat({
247
+ value: scalar,
248
+ offset: pos,
249
+ length
250
+ }) : new TomlInteger({
251
+ value: scalar,
252
+ offset: pos,
253
+ length
254
+ }),
255
+ end: token.end
256
+ };
257
+ if (scalar instanceof TomlOffsetDateTime || scalar instanceof TomlLocalDateTime || scalar instanceof TomlLocalDate || scalar instanceof TomlLocalTime) return {
258
+ node: new TomlDateTimeLiteral({
259
+ value: scalar,
260
+ offset: pos,
261
+ length
262
+ }),
263
+ end: token.end
264
+ };
265
+ return raise("InvalidValue", `${String(scalar)} is not a valid TOML value`, pos, length);
266
+ };
267
+ /** A value at `pos`; `depth` is the number of containers already enclosing it. */
268
+ const parseValue = (source, pos, depth) => {
269
+ const code = source.charCodeAt(pos);
270
+ if (pos >= source.length || code === LF || code === CR || code === HASH || code === COMMA || code === EQUALS || code === RIGHT_BRACKET || code === RIGHT_BRACE) return raise("ExpectedValue", "expected a value", pos, 1);
271
+ if (code === QUOTE) {
272
+ if (source.charCodeAt(pos + 1) === QUOTE && source.charCodeAt(pos + 2) === QUOTE) {
273
+ const scanned = scanMultilineBasicString(source, pos);
274
+ return {
275
+ node: new TomlString({
276
+ value: scanned.value,
277
+ style: "multiline-basic",
278
+ offset: pos,
279
+ length: scanned.end - pos
280
+ }),
281
+ end: scanned.end
282
+ };
283
+ }
284
+ const scanned = scanBasicString(source, pos);
285
+ return {
286
+ node: new TomlString({
287
+ value: scanned.value,
288
+ style: "basic",
289
+ offset: pos,
290
+ length: scanned.end - pos
291
+ }),
292
+ end: scanned.end
293
+ };
294
+ }
295
+ if (code === APOSTROPHE) {
296
+ if (source.charCodeAt(pos + 1) === APOSTROPHE && source.charCodeAt(pos + 2) === APOSTROPHE) {
297
+ const scanned = scanMultilineLiteralString(source, pos);
298
+ return {
299
+ node: new TomlString({
300
+ value: scanned.value,
301
+ style: "multiline-literal",
302
+ offset: pos,
303
+ length: scanned.end - pos
304
+ }),
305
+ end: scanned.end
306
+ };
307
+ }
308
+ const scanned = scanLiteralString(source, pos);
309
+ return {
310
+ node: new TomlString({
311
+ value: scanned.value,
312
+ style: "literal",
313
+ offset: pos,
314
+ length: scanned.end - pos
315
+ }),
316
+ end: scanned.end
317
+ };
318
+ }
319
+ if (code === LEFT_BRACKET) return parseArray(source, pos, depth + 1);
320
+ if (code === LEFT_BRACE) return parseInlineTable(source, pos, depth + 1);
321
+ return scalarNode(source, pos);
322
+ };
323
+ /** One `key = value` expression line; returns the position after its newline. */
324
+ const parseKeyValueExpression = (source, lineStart, keyStart) => {
325
+ const keyPath = parseKeyPath(source, keyStart);
326
+ const equalsPos = scanWhitespace(source, keyPath.end);
327
+ if (source.charCodeAt(equalsPos) !== EQUALS) raise("ExpectedEquals", "expected = after key", equalsPos, 1);
328
+ const valueStart = scanWhitespace(source, equalsPos + 1);
329
+ const value = parseValue(source, valueStart, 0);
330
+ const lineEnd = parseLineEnd(source, value.end);
331
+ return {
332
+ node: new TomlKeyValue({
333
+ keyPath: keyPath.node,
334
+ value: value.node,
335
+ ...lineEnd.comment !== void 0 ? { comment: lineEnd.comment } : {},
336
+ offset: lineStart,
337
+ length: lineEnd.end - lineStart
338
+ }),
339
+ end: lineEnd.end
340
+ };
341
+ };
342
+ /** One `[table]` or `[[array-of-tables]]` header line. */
343
+ const parseHeaderExpression = (source, lineStart, bracketPos) => {
344
+ const isArrayTable = source.charCodeAt(bracketPos + 1) === LEFT_BRACKET;
345
+ const keyStart = scanWhitespace(source, bracketPos + (isArrayTable ? 2 : 1));
346
+ const keyPath = parseKeyPath(source, keyStart);
347
+ let i = scanWhitespace(source, keyPath.end);
348
+ if (isArrayTable) {
349
+ if (source.charCodeAt(i) !== RIGHT_BRACKET || source.charCodeAt(i + 1) !== RIGHT_BRACKET) raise("ExpectedTableHeaderClose", "expected ]] to close the array-of-tables header", i, 1);
350
+ i += 2;
351
+ } else {
352
+ if (source.charCodeAt(i) !== RIGHT_BRACKET) raise("ExpectedTableHeaderClose", "expected ] to close the table header", i, 1);
353
+ i += 1;
354
+ }
355
+ const lineEnd = parseLineEnd(source, i);
356
+ const fields = {
357
+ keyPath: keyPath.node,
358
+ ...lineEnd.comment !== void 0 ? { comment: lineEnd.comment } : {},
359
+ offset: lineStart,
360
+ length: lineEnd.end - lineStart
361
+ };
362
+ return {
363
+ node: isArrayTable ? new TomlArrayTableHeader(fields) : new TomlTableHeader(fields),
364
+ end: lineEnd.end
365
+ };
366
+ };
367
+ /**
368
+ * Parse a TOML document into its linear CST: the flat, source-tiling list of
369
+ * expressions. Throws RawTomlError on malformed input and GuardExceeded when
370
+ * value nesting exceeds MAX_NESTING_DEPTH; the facade (Task 7) materializes
371
+ * both into typed errors.
372
+ */
373
+ const parseExpressions = (source) => {
374
+ assertValidUnicode(source);
375
+ const expressions = [];
376
+ let triviaStart = -1;
377
+ const flushTrivia = (end) => {
378
+ if (triviaStart !== -1) {
379
+ expressions.push(new TomlTrivia({
380
+ text: source.slice(triviaStart, end),
381
+ offset: triviaStart,
382
+ length: end - triviaStart
383
+ }));
384
+ triviaStart = -1;
385
+ }
386
+ };
387
+ let pos = 0;
388
+ while (pos < source.length) {
389
+ const lineStart = pos;
390
+ let i = scanWhitespace(source, lineStart === 0 ? skipBom(source) : lineStart);
391
+ const code = source.charCodeAt(i);
392
+ if (i >= source.length || code === LF || code === CR || code === HASH) {
393
+ if (code === HASH) i = scanComment(source, i).end;
394
+ pos = i < source.length ? scanNewline(source, i) : i;
395
+ if (triviaStart === -1) triviaStart = lineStart;
396
+ continue;
397
+ }
398
+ flushTrivia(lineStart);
399
+ const parsed = code === LEFT_BRACKET ? parseHeaderExpression(source, lineStart, i) : parseKeyValueExpression(source, lineStart, i);
400
+ expressions.push(parsed.node);
401
+ pos = parsed.end;
402
+ }
403
+ flushTrivia(source.length);
404
+ return expressions;
405
+ };
406
+
407
+ //#endregion
408
+ export { parseExpressions };