@prisma-next/psl-parser 0.14.0-dev.5 → 0.14.0-dev.51

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 (62) hide show
  1. package/README.md +28 -10
  2. package/dist/declarations-DR6To8_k.mjs +1060 -0
  3. package/dist/declarations-DR6To8_k.mjs.map +1 -0
  4. package/dist/format.d.mts +1 -1
  5. package/dist/format.mjs +2 -1
  6. package/dist/format.mjs.map +1 -1
  7. package/dist/index.d.mts +239 -3
  8. package/dist/index.d.mts.map +1 -1
  9. package/dist/index.mjs +785 -3
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/parse-3-vr14ej.mjs +626 -0
  12. package/dist/parse-3-vr14ej.mjs.map +1 -0
  13. package/dist/{parse-BjZ1LPe6.d.mts → parse-BazJr7Ye.d.mts} +128 -51
  14. package/dist/parse-BazJr7Ye.d.mts.map +1 -0
  15. package/dist/syntax.d.mts +20 -2
  16. package/dist/syntax.d.mts.map +1 -1
  17. package/dist/syntax.mjs +43 -2
  18. package/dist/syntax.mjs.map +1 -0
  19. package/package.json +9 -25
  20. package/src/attribute-spec/combinators/bool.ts +19 -0
  21. package/src/attribute-spec/combinators/diagnostic.ts +15 -0
  22. package/src/attribute-spec/combinators/entity-ref.ts +24 -0
  23. package/src/attribute-spec/combinators/field-ref.ts +36 -0
  24. package/src/attribute-spec/combinators/identifier.ts +16 -0
  25. package/src/attribute-spec/combinators/int.ts +19 -0
  26. package/src/attribute-spec/combinators/list.ts +43 -0
  27. package/src/attribute-spec/combinators/one-of.ts +29 -0
  28. package/src/attribute-spec/combinators/record.ts +43 -0
  29. package/src/attribute-spec/combinators/str.ts +19 -0
  30. package/src/attribute-spec/field-attribute.ts +27 -0
  31. package/src/attribute-spec/interpret.ts +154 -0
  32. package/src/attribute-spec/model-attribute.ts +27 -0
  33. package/src/attribute-spec/optional.ts +8 -0
  34. package/src/attribute-spec/types.ts +72 -0
  35. package/src/block-reconstruction.ts +139 -0
  36. package/src/exports/index.ts +57 -3
  37. package/src/exports/syntax.ts +25 -5
  38. package/src/extension-block.ts +107 -0
  39. package/src/parse.ts +23 -5
  40. package/src/resolve.ts +123 -0
  41. package/src/source-file.ts +25 -0
  42. package/src/symbol-table.ts +446 -0
  43. package/src/syntax/ast/attributes.ts +5 -6
  44. package/src/syntax/ast/declarations.ts +51 -26
  45. package/src/syntax/ast/expressions.ts +12 -13
  46. package/src/syntax/ast/identifier.ts +2 -3
  47. package/src/syntax/ast/qualified-name.ts +28 -19
  48. package/src/syntax/ast/type-annotation.ts +4 -5
  49. package/src/syntax/ast-helpers.ts +27 -3
  50. package/src/syntax/navigation.ts +55 -0
  51. package/src/syntax/red.ts +317 -42
  52. package/dist/parse-B_3gIEFd.mjs +0 -1421
  53. package/dist/parse-B_3gIEFd.mjs.map +0 -1
  54. package/dist/parse-BjZ1LPe6.d.mts.map +0 -1
  55. package/dist/parser-CaplKvRs.mjs +0 -1145
  56. package/dist/parser-CaplKvRs.mjs.map +0 -1
  57. package/dist/parser-Dfi3Wfdq.d.mts +0 -7
  58. package/dist/parser-Dfi3Wfdq.d.mts.map +0 -1
  59. package/dist/parser.d.mts +0 -3
  60. package/dist/parser.mjs +0 -3
  61. package/src/exports/parser.ts +0 -4
  62. package/src/parser.ts +0 -1642
@@ -0,0 +1,626 @@
1
+ import { N as createSyntaxTree, n as DocumentAst } from "./declarations-DR6To8_k.mjs";
2
+ import { n as isTerminatedStringLiteral, t as Tokenizer } from "./tokenizer-1hAHZzmp.mjs";
3
+ import { UNSPECIFIED_PSL_NAMESPACE_ID } from "@prisma-next/framework-components/psl-ast";
4
+ //#region src/source-file.ts
5
+ const CARRIAGE_RETURN = 13;
6
+ const LINE_FEED = 10;
7
+ var SourceFile = class {
8
+ #text;
9
+ #lineStarts;
10
+ constructor(text) {
11
+ this.#text = text;
12
+ const lineStarts = [0];
13
+ for (let offset = 0; offset < text.length; offset++) if (text.charCodeAt(offset) === LINE_FEED) lineStarts.push(offset + 1);
14
+ this.#lineStarts = lineStarts;
15
+ }
16
+ get text() {
17
+ return this.#text;
18
+ }
19
+ get length() {
20
+ return this.#text.length;
21
+ }
22
+ get lineCount() {
23
+ return this.#lineStarts.length;
24
+ }
25
+ lineStartOffsets() {
26
+ return this.#lineStarts;
27
+ }
28
+ lineStartOffset(line) {
29
+ if (line <= 0) return 0;
30
+ return this.#lineStarts[line] ?? this.#text.length;
31
+ }
32
+ lineEndOffset(line) {
33
+ if (line < 0) return 0;
34
+ const nextLineStart = this.#lineStarts[line + 1];
35
+ if (nextLineStart === void 0) return this.#text.length;
36
+ const lineFeedOffset = nextLineStart - 1;
37
+ const carriageReturnOffset = lineFeedOffset - 1;
38
+ return this.#text.charCodeAt(carriageReturnOffset) === CARRIAGE_RETURN ? carriageReturnOffset : lineFeedOffset;
39
+ }
40
+ positionAt(offset) {
41
+ const clamped = clamp(offset, 0, this.#text.length);
42
+ const line = this.#lineIndexAt(clamped);
43
+ return {
44
+ line,
45
+ character: clamped - this.#lineStartAt(line)
46
+ };
47
+ }
48
+ offsetAt(position) {
49
+ const line = clamp(position.line, 0, this.#lineStarts.length - 1);
50
+ const lineStart = this.#lineStartAt(line);
51
+ const lineEnd = this.#lineEndAt(line);
52
+ return clamp(lineStart + position.character, lineStart, lineEnd);
53
+ }
54
+ #lineStartAt(line) {
55
+ return this.#lineStarts[line] ?? 0;
56
+ }
57
+ #lineEndAt(line) {
58
+ return line + 1 < this.#lineStarts.length ? this.#lineStartAt(line + 1) - 1 : this.#text.length;
59
+ }
60
+ #lineIndexAt(offset) {
61
+ const lineStarts = this.#lineStarts;
62
+ let low = 0;
63
+ let high = lineStarts.length - 1;
64
+ while (low < high) {
65
+ const mid = low + high + 1 >>> 1;
66
+ if ((lineStarts[mid] ?? 0) <= offset) low = mid;
67
+ else high = mid - 1;
68
+ }
69
+ return low;
70
+ }
71
+ };
72
+ function clamp(value, min, max) {
73
+ if (value < min) return min;
74
+ if (value > max) return max;
75
+ return value;
76
+ }
77
+ //#endregion
78
+ //#region src/syntax/green.ts
79
+ function greenToken(kind, text) {
80
+ return {
81
+ type: "token",
82
+ kind,
83
+ text
84
+ };
85
+ }
86
+ function greenNode(kind, children) {
87
+ let textLength = 0;
88
+ for (const child of children) textLength += child.type === "token" ? child.text.length : child.textLength;
89
+ return {
90
+ type: "node",
91
+ kind,
92
+ children,
93
+ textLength
94
+ };
95
+ }
96
+ //#endregion
97
+ //#region src/syntax/green-builder.ts
98
+ var GreenNodeBuilder = class {
99
+ #stack = [];
100
+ startNode(kind) {
101
+ this.#stack.push({
102
+ kind,
103
+ children: []
104
+ });
105
+ }
106
+ token(kind, text) {
107
+ const current = this.#stack.at(-1);
108
+ if (!current) throw new Error("GreenNodeBuilder: token() called with no open node");
109
+ current.children.push(greenToken(kind, text));
110
+ }
111
+ finishNode() {
112
+ const completed = this.#stack.pop();
113
+ if (!completed) throw new Error("GreenNodeBuilder: finishNode() called with no open node");
114
+ const node = greenNode(completed.kind, completed.children);
115
+ const parent = this.#stack.at(-1);
116
+ if (parent) parent.children.push(node);
117
+ return node;
118
+ }
119
+ };
120
+ //#endregion
121
+ //#region src/parse.ts
122
+ const TRIVIA_KINDS = new Set([
123
+ "Whitespace",
124
+ "Newline",
125
+ "Comment"
126
+ ]);
127
+ /**
128
+ * The fault-tolerant parser substrate the grammars drive. Trivia is flushed
129
+ * into the enclosing open node, so every child node spans exactly its first
130
+ * through last significant token.
131
+ */
132
+ var Cursor = class {
133
+ #tokenizer;
134
+ #sourceFile;
135
+ #builder = new GreenNodeBuilder();
136
+ #diagnostics = [];
137
+ #offset = 0;
138
+ #depth = 0;
139
+ constructor(source) {
140
+ this.#tokenizer = new Tokenizer(source);
141
+ this.#sourceFile = new SourceFile(source);
142
+ }
143
+ get diagnostics() {
144
+ return this.#diagnostics;
145
+ }
146
+ get sourceFile() {
147
+ return this.#sourceFile;
148
+ }
149
+ peekKind(ahead = 0) {
150
+ return this.peekToken(ahead).kind;
151
+ }
152
+ peekToken(ahead = 0) {
153
+ let rawIndex = 0;
154
+ let remaining = ahead;
155
+ for (;;) {
156
+ const token = this.#tokenizer.peek(rawIndex);
157
+ if (token.kind === "Eof") return token;
158
+ if (TRIVIA_KINDS.has(token.kind)) {
159
+ rawIndex++;
160
+ continue;
161
+ }
162
+ if (remaining === 0) return token;
163
+ remaining--;
164
+ rawIndex++;
165
+ }
166
+ }
167
+ /** Span of the significant token `lookahead` positions ahead (`mark(0)` = the next). */
168
+ mark(lookahead = 0) {
169
+ let rawIndex = 0;
170
+ let offset = this.#offset;
171
+ let remaining = lookahead;
172
+ for (;;) {
173
+ const token = this.#tokenizer.peek(rawIndex);
174
+ if (token.kind === "Eof") return {
175
+ offset,
176
+ length: token.text.length
177
+ };
178
+ if (!TRIVIA_KINDS.has(token.kind) && remaining === 0) return {
179
+ offset,
180
+ length: token.text.length
181
+ };
182
+ if (!TRIVIA_KINDS.has(token.kind)) remaining--;
183
+ offset += token.text.length;
184
+ rawIndex++;
185
+ }
186
+ }
187
+ /**
188
+ * Zero-width mark just past the last consumed significant token — anchors an
189
+ * "expected here" diagnostic, e.g. the `{` missing after a declaration's name.
190
+ */
191
+ markAfterLastToken() {
192
+ return {
193
+ offset: this.#offset,
194
+ length: 0
195
+ };
196
+ }
197
+ startNode(kind) {
198
+ if (this.#depth > 0) this.flushTrivia();
199
+ this.#builder.startNode(kind);
200
+ this.#depth++;
201
+ }
202
+ finishNode() {
203
+ this.#depth--;
204
+ return this.#builder.finishNode();
205
+ }
206
+ bump() {
207
+ this.flushTrivia();
208
+ const token = this.#tokenizer.peek();
209
+ if (token.kind === "Eof") return token;
210
+ this.#builder.token(token.kind, token.text);
211
+ this.#advance();
212
+ return token;
213
+ }
214
+ recoverToSyncPoint() {
215
+ for (;;) {
216
+ const token = this.#tokenizer.peek();
217
+ if (token.kind === "Eof" || token.kind === "Newline" || token.kind === "RBrace") return;
218
+ this.#builder.token(token.kind, token.text);
219
+ this.#advance();
220
+ }
221
+ }
222
+ flushTrivia() {
223
+ for (;;) {
224
+ const token = this.#tokenizer.peek();
225
+ if (!TRIVIA_KINDS.has(token.kind)) return;
226
+ this.#builder.token(token.kind, token.text);
227
+ this.#advance();
228
+ }
229
+ }
230
+ diagnostic(code, message, mark) {
231
+ const start = mark.offset;
232
+ const end = start + mark.length;
233
+ this.#diagnostics.push({
234
+ code,
235
+ message,
236
+ range: {
237
+ start: this.#sourceFile.positionAt(start),
238
+ end: this.#sourceFile.positionAt(end)
239
+ }
240
+ });
241
+ }
242
+ #advance() {
243
+ this.#offset += this.#tokenizer.next().text.length;
244
+ }
245
+ };
246
+ function parseIdentifier(cursor) {
247
+ cursor.startNode("Identifier");
248
+ cursor.bump();
249
+ cursor.finishNode();
250
+ }
251
+ /**
252
+ * Returns `undefined` when the next significant token does not start a
253
+ * recognised expression, leaving recovery to the caller.
254
+ */
255
+ function parseExpression(cursor) {
256
+ return parseStringLiteralExpr(cursor) ?? parseNumberLiteralExpr(cursor) ?? parseArrayLiteral(cursor) ?? parseObjectLiteralExpr(cursor) ?? parseFunctionCall(cursor) ?? parseBooleanLiteralExpr(cursor) ?? parseIdentifierExpr(cursor);
257
+ }
258
+ function parseStringLiteralExpr(cursor) {
259
+ if (cursor.peekKind() !== "StringLiteral") return void 0;
260
+ const stringMark = cursor.mark();
261
+ const text = cursor.peekToken().text;
262
+ cursor.startNode("StringLiteralExpr");
263
+ cursor.bump();
264
+ if (!isTerminatedStringLiteral(text)) cursor.diagnostic("PSL_UNTERMINATED_STRING", "Unterminated string literal", stringMark);
265
+ return cursor.finishNode();
266
+ }
267
+ function parseNumberLiteralExpr(cursor) {
268
+ if (cursor.peekKind() !== "NumberLiteral") return void 0;
269
+ cursor.startNode("NumberLiteralExpr");
270
+ cursor.bump();
271
+ return cursor.finishNode();
272
+ }
273
+ /**
274
+ * Parses a namespace-qualified name `[space ':']? Ident ('.' Ident)*`. The
275
+ * caller guarantees a leading `Ident`.
276
+ *
277
+ * Parsing the whole chain up front lets a position decide
278
+ * constructor-vs-reference by peeking exactly one token for `(`, with no scan of
279
+ * the dotted chain's length.
280
+ */
281
+ function parseQualifiedName(cursor) {
282
+ cursor.startNode("QualifiedName");
283
+ parseIdentifier(cursor);
284
+ parseQualifiedSegments(cursor, "Colon");
285
+ parseQualifiedSegments(cursor, "Dot");
286
+ cursor.finishNode();
287
+ }
288
+ /**
289
+ * A well-formed name carries at most one colon space and one dot namespace, so
290
+ * each separator past the first of its kind reports `PSL_INVALID_QUALIFIED_NAME`.
291
+ * The separator is consumed regardless, keeping the lossless round-trip intact.
292
+ */
293
+ function parseQualifiedSegments(cursor, separator) {
294
+ let seen = 0;
295
+ while (cursor.peekKind() === separator) {
296
+ seen++;
297
+ const separatorMark = cursor.mark();
298
+ cursor.bump();
299
+ if (seen > 1) cursor.diagnostic("PSL_INVALID_QUALIFIED_NAME", "Qualified name has too many segments", separatorMark);
300
+ if (cursor.peekKind() === "Ident") parseIdentifier(cursor);
301
+ else cursor.diagnostic("PSL_INVALID_QUALIFIED_NAME", "Qualified name is missing a name after the separator", cursor.mark());
302
+ }
303
+ }
304
+ function parseBooleanLiteralExpr(cursor) {
305
+ if (cursor.peekKind() !== "Ident") return void 0;
306
+ const text = cursor.peekToken().text;
307
+ if (text !== "true" && text !== "false") return void 0;
308
+ cursor.startNode("BooleanLiteralExpr");
309
+ cursor.bump();
310
+ return cursor.finishNode();
311
+ }
312
+ function parseIdentifierExpr(cursor) {
313
+ if (cursor.peekKind() !== "Ident") return void 0;
314
+ cursor.startNode("Identifier");
315
+ cursor.bump();
316
+ return cursor.finishNode();
317
+ }
318
+ function parseArrayLiteral(cursor) {
319
+ if (cursor.peekKind() !== "LBracket") return void 0;
320
+ cursor.startNode("ArrayLiteral");
321
+ cursor.bump();
322
+ while (cursor.peekKind() !== "RBracket" && cursor.peekKind() !== "Eof") {
323
+ if (!parseExpression(cursor)) break;
324
+ if (cursor.peekKind() === "Comma") cursor.bump();
325
+ else break;
326
+ }
327
+ if (cursor.peekKind() === "RBracket") cursor.bump();
328
+ return cursor.finishNode();
329
+ }
330
+ function parseObjectLiteralExpr(cursor) {
331
+ if (cursor.peekKind() !== "LBrace") return void 0;
332
+ const braceMark = cursor.mark();
333
+ cursor.startNode("ObjectLiteralExpr");
334
+ cursor.bump();
335
+ while (cursor.peekKind() !== "RBrace" && cursor.peekKind() !== "Eof") {
336
+ parseObjectField(cursor);
337
+ if (cursor.peekKind() === "Comma") cursor.bump();
338
+ else if (cursor.peekKind() === "Ident") cursor.diagnostic("PSL_INVALID_OBJECT_LITERAL", "Expected \",\" between object-literal fields", cursor.markAfterLastToken());
339
+ else break;
340
+ }
341
+ if (cursor.peekKind() === "RBrace") cursor.bump();
342
+ else cursor.diagnostic("PSL_INVALID_OBJECT_LITERAL", "Unterminated object literal", braceMark);
343
+ return cursor.finishNode();
344
+ }
345
+ function parseObjectField(cursor) {
346
+ cursor.startNode("ObjectField");
347
+ const keyMark = cursor.mark();
348
+ const keyText = cursor.peekToken().text;
349
+ if (cursor.peekKind() === "Ident") parseIdentifier(cursor);
350
+ else if (cursor.peekKind() === "StringLiteral") parseStringLiteralExpr(cursor);
351
+ if (cursor.peekKind() === "Colon") {
352
+ cursor.bump();
353
+ if (!parseExpression(cursor)) cursor.diagnostic("PSL_INVALID_OBJECT_LITERAL", "Expected a value after \":\"", cursor.mark());
354
+ } else {
355
+ cursor.diagnostic("PSL_INVALID_OBJECT_LITERAL", `Expected ":" after "${keyText}"`, keyMark);
356
+ if (!(cursor.peekKind() === "Ident" && cursor.peekKind(1) === "Colon")) parseExpression(cursor);
357
+ }
358
+ return cursor.finishNode();
359
+ }
360
+ /**
361
+ * Whether the next tokens open a call: a bare `Ident(` or a namespace-qualified
362
+ * `Ident.Ident(`. The lookahead is deliberately bounded so a bare dotted
363
+ * reference like `a.b` is not mistaken for a call, rather than scanning an
364
+ * unbounded dotted chain ahead to find the paren.
365
+ */
366
+ function isCallAhead(cursor) {
367
+ if (cursor.peekKind() !== "Ident") return false;
368
+ if (cursor.peekKind(1) === "LParen") return true;
369
+ return cursor.peekKind(1) === "Dot" && cursor.peekKind(2) === "Ident" && cursor.peekKind(3) === "LParen";
370
+ }
371
+ /**
372
+ * Parses a function/constructor call — bare `autoincrement()` or qualified
373
+ * `temporal.updatedAt()`. Returns `undefined` unless {@link isCallAhead}
374
+ * confirms a trailing `(`, so the `parseExpression` chain falls through to the
375
+ * boolean and bare-identifier forms.
376
+ */
377
+ function parseFunctionCall(cursor) {
378
+ if (!isCallAhead(cursor)) return void 0;
379
+ cursor.startNode("FunctionCall");
380
+ parseQualifiedName(cursor);
381
+ if (cursor.peekKind() === "LParen") parseParenArgs(cursor);
382
+ return cursor.finishNode();
383
+ }
384
+ /** Parses a parenthesised, comma-separated `AttributeArg` list into the currently open node. */
385
+ function parseParenArgs(cursor) {
386
+ cursor.bump();
387
+ while (cursor.peekKind() !== "RParen" && cursor.peekKind() !== "Eof") {
388
+ parseAttributeArg(cursor);
389
+ if (cursor.peekKind() === "Comma") cursor.bump();
390
+ else break;
391
+ }
392
+ if (cursor.peekKind() === "RParen") cursor.bump();
393
+ }
394
+ function parseAttributeArg(cursor) {
395
+ const kind = cursor.peekKind();
396
+ if (kind !== "Ident" && kind !== "StringLiteral" && kind !== "NumberLiteral" && kind !== "LBracket" && kind !== "LBrace") return;
397
+ cursor.startNode("AttributeArg");
398
+ if (cursor.peekKind() === "Ident" && cursor.peekKind(1) === "Colon") {
399
+ parseIdentifier(cursor);
400
+ cursor.bump();
401
+ }
402
+ parseArgValue(cursor);
403
+ cursor.finishNode();
404
+ }
405
+ function parseArgValue(cursor) {
406
+ parseExpression(cursor);
407
+ }
408
+ function parseAttributeArgList(cursor) {
409
+ cursor.startNode("AttributeArgList");
410
+ parseParenArgs(cursor);
411
+ return cursor.finishNode();
412
+ }
413
+ function parseAttribute(cursor) {
414
+ const isBlockAttribute = cursor.peekKind() === "DoubleAt";
415
+ const attributeMark = cursor.mark();
416
+ cursor.startNode(isBlockAttribute ? "ModelAttribute" : "FieldAttribute");
417
+ cursor.bump();
418
+ if (cursor.peekKind() === "Ident") parseQualifiedName(cursor);
419
+ else cursor.diagnostic("PSL_INVALID_ATTRIBUTE_SYNTAX", "Attribute name expected", attributeMark);
420
+ if (cursor.peekKind() === "LParen") parseAttributeArgList(cursor);
421
+ return cursor.finishNode();
422
+ }
423
+ /**
424
+ * A type annotation: `QualifiedName (argList)? ([])? (?)?`, e.g.
425
+ * `pgvector.Vector(1536)[]?`. When the field has no type, no node is emitted —
426
+ * a missing type is the absence of a `TypeAnnotation`, not a zero-width one.
427
+ */
428
+ function parseTypeAnnotation(cursor) {
429
+ const kind = cursor.peekKind();
430
+ if (kind !== "Ident" && kind !== "LBracket" && kind !== "Question") return;
431
+ cursor.startNode("TypeAnnotation");
432
+ if (cursor.peekKind() === "Ident") {
433
+ parseQualifiedName(cursor);
434
+ if (cursor.peekKind() === "LParen") parseAttributeArgList(cursor);
435
+ }
436
+ if (cursor.peekKind() === "LBracket") {
437
+ cursor.bump();
438
+ if (cursor.peekKind() === "RBracket") cursor.bump();
439
+ }
440
+ if (cursor.peekKind() === "Question") cursor.bump();
441
+ cursor.finishNode();
442
+ }
443
+ /**
444
+ * Parses a full PSL document. Never throws — malformed input yields diagnostics
445
+ * and a recovered tree, not an exception.
446
+ */
447
+ function parse(source) {
448
+ const cursor = new Cursor(source);
449
+ const root = createSyntaxTree(parseDocument(cursor));
450
+ return {
451
+ document: DocumentAst.cast(root) ?? new DocumentAst(root),
452
+ diagnostics: cursor.diagnostics,
453
+ sourceFile: cursor.sourceFile
454
+ };
455
+ }
456
+ function parseDocument(cursor) {
457
+ cursor.startNode("Document");
458
+ while (cursor.peekKind() !== "Eof") parseDeclaration(cursor, false);
459
+ cursor.flushTrivia();
460
+ return cursor.finishNode();
461
+ }
462
+ const RESERVED_BLOCK_KEYWORDS = new Set([
463
+ "model",
464
+ "namespace",
465
+ "type",
466
+ "types"
467
+ ]);
468
+ function keywordIs(cursor, keyword) {
469
+ return cursor.peekKind() === "Ident" && cursor.peekToken().text === keyword;
470
+ }
471
+ /**
472
+ * Each alternative is a no-op on non-match, consuming nothing, so the
473
+ * forward-only cursor is never left half-consumed by a rejected alternative.
474
+ * Recovery runs via the `if (!node)` tail rather than as a `??` arm, because it
475
+ * appends raw tokens to the open parent instead of returning a child node.
476
+ */
477
+ function parseDeclaration(cursor, insideNamespace) {
478
+ const name = cursor.peekKind(1) === "Ident" ? cursor.peekToken(1).text : "";
479
+ if (insideNamespace && keywordIs(cursor, "namespace")) cursor.diagnostic("PSL_INVALID_NAMESPACE_BLOCK", `Recursive "namespace ${name}" block is not allowed; namespace blocks may not nest`, cursor.mark());
480
+ else if (insideNamespace && keywordIs(cursor, "types")) cursor.diagnostic("PSL_INVALID_NAMESPACE_BLOCK", "`types` blocks must be declared at the document top level, not inside a namespace block", cursor.mark());
481
+ else if (keywordIs(cursor, "namespace") && name === UNSPECIFIED_PSL_NAMESPACE_ID) cursor.diagnostic("PSL_INVALID_NAMESPACE_BLOCK", `Namespace name "${UNSPECIFIED_PSL_NAMESPACE_ID}" is reserved for the parser-synthesised bucket for top-level declarations`, cursor.mark(1));
482
+ if (!(parseModel(cursor) ?? parseNamespace(cursor) ?? parseCompositeType(cursor) ?? parseTypesBlock(cursor) ?? parseGenericBlock(cursor))) parseUnsupportedTopLevel(cursor);
483
+ }
484
+ /**
485
+ * Reports only the first missing piece — a missing name suppresses the
486
+ * missing-brace diagnostic. `nameRequired` is false only for the `types` block.
487
+ */
488
+ function parseBlock(cursor, kind, nameRequired, parseMember) {
489
+ const keyword = cursor.peekToken().text;
490
+ const keywordMark = cursor.mark();
491
+ cursor.startNode(kind);
492
+ cursor.bump();
493
+ const hasName = nameRequired && cursor.peekKind() === "Ident";
494
+ if (hasName) parseIdentifier(cursor);
495
+ if (nameRequired && !hasName) cursor.diagnostic("PSL_INVALID_DECLARATION", `Expected a name after "${keyword}"`, keywordMark);
496
+ else if (cursor.peekKind() !== "LBrace") cursor.diagnostic("PSL_INVALID_DECLARATION", `Expected "{" to open the "${keyword}" block`, cursor.markAfterLastToken());
497
+ if (cursor.peekKind() === "LBrace") parseBlockBody(cursor, parseMember);
498
+ else cursor.recoverToSyncPoint();
499
+ return cursor.finishNode();
500
+ }
501
+ function parseModel(cursor) {
502
+ if (!keywordIs(cursor, "model")) return void 0;
503
+ return parseBlock(cursor, "ModelDeclaration", true, parseModelMember);
504
+ }
505
+ /**
506
+ * Excluding the reserved keywords keeps a malformed reserved block (e.g. `model
507
+ * {` with no name) routed to its dedicated parser. The generic keyword set is
508
+ * open, so a bare identifier with no brace (e.g. `oops`) is read as an unfinished
509
+ * custom declaration rather than unsupported content.
510
+ */
511
+ function parseGenericBlock(cursor) {
512
+ if (cursor.peekKind() !== "Ident") return void 0;
513
+ const keyword = cursor.peekToken().text;
514
+ if (RESERVED_BLOCK_KEYWORDS.has(keyword)) return void 0;
515
+ const hasName = cursor.peekKind(1) === "Ident" && cursor.peekKind(2) === "LBrace";
516
+ cursor.startNode("GenericBlockDeclaration");
517
+ cursor.bump();
518
+ if (hasName) parseIdentifier(cursor);
519
+ if (cursor.peekKind() === "LBrace") parseBlockBody(cursor, parseKeyValueMember);
520
+ else {
521
+ cursor.diagnostic("PSL_INVALID_DECLARATION", `Expected "{" to open the "${keyword}" block`, cursor.markAfterLastToken());
522
+ cursor.recoverToSyncPoint();
523
+ }
524
+ return cursor.finishNode();
525
+ }
526
+ function parseNamespace(cursor) {
527
+ if (!keywordIs(cursor, "namespace")) return void 0;
528
+ return parseBlock(cursor, "Namespace", true, (inner) => parseDeclaration(inner, true));
529
+ }
530
+ function parseCompositeType(cursor) {
531
+ if (!keywordIs(cursor, "type")) return void 0;
532
+ return parseBlock(cursor, "CompositeTypeDeclaration", true, parseModelMember);
533
+ }
534
+ /** `types` (plural) is the no-name types block; the singular `type` is the composite type above. */
535
+ function parseTypesBlock(cursor) {
536
+ if (!keywordIs(cursor, "types")) return void 0;
537
+ return parseBlock(cursor, "TypesBlock", false, parseNamedTypeMember);
538
+ }
539
+ /** Every `parseMember` consumes at least one significant token, so the loop always terminates. */
540
+ function parseBlockBody(cursor, parseMember) {
541
+ const braceMark = cursor.mark();
542
+ cursor.bump();
543
+ for (;;) {
544
+ const kind = cursor.peekKind();
545
+ if (kind === "RBrace" || kind === "Eof") break;
546
+ parseMember(cursor);
547
+ }
548
+ if (cursor.peekKind() === "RBrace") cursor.bump();
549
+ else cursor.diagnostic("PSL_UNTERMINATED_BLOCK", "Unterminated block declaration", braceMark);
550
+ }
551
+ function parseUnsupportedTopLevel(cursor) {
552
+ const offending = cursor.peekToken().text;
553
+ const message = cursor.peekKind(1) === "LBrace" ? `Unsupported top-level block "${offending}"` : `Unsupported top-level declaration "${offending}"`;
554
+ cursor.diagnostic("PSL_UNSUPPORTED_TOP_LEVEL_BLOCK", message, cursor.mark());
555
+ cursor.bump();
556
+ cursor.recoverToSyncPoint();
557
+ }
558
+ /**
559
+ * Matches a leading `@@` block attribute, a no-op otherwise. Single-`@`
560
+ * attributes belong to fields and are parsed inside `parseField`.
561
+ */
562
+ function parseBlockAttribute(cursor) {
563
+ if (cursor.peekKind() !== "DoubleAt") return void 0;
564
+ return parseAttribute(cursor);
565
+ }
566
+ function parseModelMember(cursor) {
567
+ if (!(parseBlockAttribute(cursor) ?? parseField(cursor))) invalidMember(cursor, "PSL_INVALID_MODEL_MEMBER", `Invalid model member declaration "${cursor.peekToken().text}"`);
568
+ }
569
+ function parseNamedTypeMember(cursor) {
570
+ if (!parseNamedType(cursor)) invalidMember(cursor, "PSL_INVALID_TYPES_MEMBER", `Invalid types declaration "${cursor.peekToken().text}"`);
571
+ }
572
+ /**
573
+ * A generic-block member is either a `@@`-block attribute or a `key = value`
574
+ * entry. The block-attribute alternative is purely syntactic — it does not judge
575
+ * whether the attribute is valid for the block's kind.
576
+ */
577
+ function parseKeyValueMember(cursor) {
578
+ if (!(parseBlockAttribute(cursor) ?? parseKeyValue(cursor))) invalidMember(cursor, "PSL_INVALID_EXTENSION_BLOCK_MEMBER", "Invalid block entry");
579
+ }
580
+ function invalidMember(cursor, code, message) {
581
+ cursor.diagnostic(code, message, cursor.mark());
582
+ cursor.bump();
583
+ cursor.recoverToSyncPoint();
584
+ }
585
+ function parseField(cursor) {
586
+ if (cursor.peekKind() !== "Ident") return void 0;
587
+ cursor.startNode("FieldDeclaration");
588
+ const nameMark = cursor.mark();
589
+ const nameText = cursor.peekToken().text;
590
+ parseIdentifier(cursor);
591
+ if (cursor.peekKind() !== "Ident") cursor.diagnostic("PSL_INVALID_MODEL_MEMBER", `Expected a type after field "${nameText}"`, nameMark);
592
+ parseTypeAnnotation(cursor);
593
+ while (cursor.peekKind() === "At") parseAttribute(cursor);
594
+ return cursor.finishNode();
595
+ }
596
+ function parseNamedType(cursor) {
597
+ if (cursor.peekKind() !== "Ident") return void 0;
598
+ cursor.startNode("NamedTypeDeclaration");
599
+ const nameMark = cursor.mark();
600
+ const nameText = cursor.peekToken().text;
601
+ parseIdentifier(cursor);
602
+ if (cursor.peekKind() === "Equals") cursor.bump();
603
+ else cursor.diagnostic("PSL_INVALID_TYPES_MEMBER", `Expected "=" after "${nameText}"`, nameMark);
604
+ parseTypeAnnotation(cursor);
605
+ while (cursor.peekKind() === "At") parseAttribute(cursor);
606
+ return cursor.finishNode();
607
+ }
608
+ /**
609
+ * A generic-block entry is either `key = value` or a bare `key` (committing a
610
+ * `KeyValuePair` carrying only the key). A `key =` with no following expression
611
+ * is flagged.
612
+ */
613
+ function parseKeyValue(cursor) {
614
+ if (cursor.peekKind() !== "Ident") return void 0;
615
+ cursor.startNode("KeyValuePair");
616
+ parseIdentifier(cursor);
617
+ if (cursor.peekKind() === "Equals") {
618
+ cursor.bump();
619
+ if (!parseExpression(cursor)) cursor.diagnostic("PSL_INVALID_EXTENSION_BLOCK_MEMBER", "Expected a value after \"=\"", cursor.mark());
620
+ }
621
+ return cursor.finishNode();
622
+ }
623
+ //#endregion
624
+ export { SourceFile as a, greenToken as i, GreenNodeBuilder as n, greenNode as r, parse as t };
625
+
626
+ //# sourceMappingURL=parse-3-vr14ej.mjs.map