@prisma-next/psl-parser 0.13.0-dev.4 → 0.13.0-dev.40

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/dist/syntax.mjs CHANGED
@@ -1,3 +1,66 @@
1
+ import { n as isTerminatedStringLiteral, t as Tokenizer } from "./tokenizer-DdaOstnz.mjs";
2
+ import { UNSPECIFIED_PSL_NAMESPACE_ID } from "@prisma-next/framework-components/psl-ast";
3
+ //#region src/source-file.ts
4
+ const LINE_FEED = 10;
5
+ var SourceFile = class {
6
+ #text;
7
+ #lineStarts;
8
+ constructor(text) {
9
+ this.#text = text;
10
+ const lineStarts = [0];
11
+ for (let offset = 0; offset < text.length; offset++) if (text.charCodeAt(offset) === LINE_FEED) lineStarts.push(offset + 1);
12
+ this.#lineStarts = lineStarts;
13
+ }
14
+ get text() {
15
+ return this.#text;
16
+ }
17
+ get length() {
18
+ return this.#text.length;
19
+ }
20
+ get lineCount() {
21
+ return this.#lineStarts.length;
22
+ }
23
+ lineStartOffsets() {
24
+ return this.#lineStarts;
25
+ }
26
+ positionAt(offset) {
27
+ const clamped = clamp(offset, 0, this.#text.length);
28
+ const line = this.#lineIndexAt(clamped);
29
+ return {
30
+ line,
31
+ character: clamped - this.#lineStartAt(line)
32
+ };
33
+ }
34
+ offsetAt(position) {
35
+ const line = clamp(position.line, 0, this.#lineStarts.length - 1);
36
+ const lineStart = this.#lineStartAt(line);
37
+ const lineEnd = this.#lineEndAt(line);
38
+ return clamp(lineStart + position.character, lineStart, lineEnd);
39
+ }
40
+ #lineStartAt(line) {
41
+ return this.#lineStarts[line] ?? 0;
42
+ }
43
+ #lineEndAt(line) {
44
+ return line + 1 < this.#lineStarts.length ? this.#lineStartAt(line + 1) - 1 : this.#text.length;
45
+ }
46
+ #lineIndexAt(offset) {
47
+ const lineStarts = this.#lineStarts;
48
+ let low = 0;
49
+ let high = lineStarts.length - 1;
50
+ while (low < high) {
51
+ const mid = low + high + 1 >>> 1;
52
+ if ((lineStarts[mid] ?? 0) <= offset) low = mid;
53
+ else high = mid - 1;
54
+ }
55
+ return low;
56
+ }
57
+ };
58
+ function clamp(value, min, max) {
59
+ if (value < min) return min;
60
+ if (value > max) return max;
61
+ return value;
62
+ }
63
+ //#endregion
1
64
  //#region src/syntax/red.ts
2
65
  var SyntaxNode = class SyntaxNode {
3
66
  green;
@@ -182,6 +245,75 @@ var ArrayLiteralAst = class ArrayLiteralAst {
182
245
  return node.kind === "ArrayLiteral" ? new ArrayLiteralAst(node) : void 0;
183
246
  }
184
247
  };
248
+ const HEX = /^[0-9a-fA-F]+$/;
249
+ function decodeFixedHex(raw, start, width) {
250
+ if (start + width > raw.length) return void 0;
251
+ const hex = raw.slice(start, start + width);
252
+ if (!HEX.test(hex)) return void 0;
253
+ return String.fromCharCode(Number.parseInt(hex, 16));
254
+ }
255
+ function decodeStringLiteral(raw) {
256
+ let out = "";
257
+ let i = 0;
258
+ while (i < raw.length) {
259
+ const ch = raw.charAt(i);
260
+ if (ch !== "\\" || i + 1 >= raw.length) {
261
+ out += ch;
262
+ i++;
263
+ continue;
264
+ }
265
+ const next = raw.charAt(i + 1);
266
+ switch (next) {
267
+ case "n":
268
+ out += "\n";
269
+ i += 2;
270
+ continue;
271
+ case "r":
272
+ out += "\r";
273
+ i += 2;
274
+ continue;
275
+ case "t":
276
+ out += " ";
277
+ i += 2;
278
+ continue;
279
+ case "\"":
280
+ out += "\"";
281
+ i += 2;
282
+ continue;
283
+ case "\\":
284
+ out += "\\";
285
+ i += 2;
286
+ continue;
287
+ case "x": {
288
+ const decoded = decodeFixedHex(raw, i + 2, 2);
289
+ if (decoded === void 0) {
290
+ out += "\\x";
291
+ i += 2;
292
+ continue;
293
+ }
294
+ out += decoded;
295
+ i += 4;
296
+ continue;
297
+ }
298
+ case "u": {
299
+ const decoded = decodeFixedHex(raw, i + 2, 4);
300
+ if (decoded === void 0) {
301
+ out += "\\u";
302
+ i += 2;
303
+ continue;
304
+ }
305
+ out += decoded;
306
+ i += 6;
307
+ continue;
308
+ }
309
+ default:
310
+ out += `\\${next}`;
311
+ i += 2;
312
+ continue;
313
+ }
314
+ }
315
+ return out;
316
+ }
185
317
  var StringLiteralExprAst = class StringLiteralExprAst {
186
318
  syntax;
187
319
  constructor(syntax) {
@@ -193,16 +325,7 @@ var StringLiteralExprAst = class StringLiteralExprAst {
193
325
  value() {
194
326
  const tok = this.token();
195
327
  if (!tok) return void 0;
196
- return tok.text.slice(1, -1).replace(/\\(.)/g, (_match, char) => {
197
- switch (char) {
198
- case "n": return "\n";
199
- case "r": return "\r";
200
- case "t": return " ";
201
- case "\"": return "\"";
202
- case "\\": return "\\";
203
- default: return `\\${char}`;
204
- }
205
- });
328
+ return decodeStringLiteral(tok.text.slice(1, -1));
206
329
  }
207
330
  static cast(node) {
208
331
  return node.kind === "StringLiteralExpr" ? new StringLiteralExprAst(node) : void 0;
@@ -243,8 +366,64 @@ var BooleanLiteralExprAst = class BooleanLiteralExprAst {
243
366
  return node.kind === "BooleanLiteralExpr" ? new BooleanLiteralExprAst(node) : void 0;
244
367
  }
245
368
  };
369
+ var ObjectLiteralExprAst = class ObjectLiteralExprAst {
370
+ syntax;
371
+ constructor(syntax) {
372
+ this.syntax = syntax;
373
+ }
374
+ lbrace() {
375
+ return findChildToken(this.syntax, "LBrace");
376
+ }
377
+ rbrace() {
378
+ return findChildToken(this.syntax, "RBrace");
379
+ }
380
+ *fields() {
381
+ yield* filterChildren(this.syntax, ObjectFieldAst.cast);
382
+ }
383
+ static cast(node) {
384
+ return node.kind === "ObjectLiteralExpr" ? new ObjectLiteralExprAst(node) : void 0;
385
+ }
386
+ };
387
+ var ObjectFieldAst = class ObjectFieldAst {
388
+ syntax;
389
+ constructor(syntax) {
390
+ this.syntax = syntax;
391
+ }
392
+ key() {
393
+ for (const child of this.syntax.children()) {
394
+ if (!(child instanceof SyntaxNode)) {
395
+ if (child.kind === "Colon") break;
396
+ continue;
397
+ }
398
+ return IdentifierAst.cast(child);
399
+ }
400
+ }
401
+ colon() {
402
+ return findChildToken(this.syntax, "Colon");
403
+ }
404
+ value() {
405
+ if (this.colon()) {
406
+ let pastColon = false;
407
+ for (const child of this.syntax.children()) {
408
+ if (!(child instanceof SyntaxNode)) {
409
+ if (child.kind === "Colon") pastColon = true;
410
+ continue;
411
+ }
412
+ if (pastColon) {
413
+ const expr = castExpression(child);
414
+ if (expr) return expr;
415
+ }
416
+ }
417
+ return;
418
+ }
419
+ return findFirstChild(this.syntax, castExpression);
420
+ }
421
+ static cast(node) {
422
+ return node.kind === "ObjectField" ? new ObjectFieldAst(node) : void 0;
423
+ }
424
+ };
246
425
  function castExpression(node) {
247
- return FunctionCallAst.cast(node) ?? ArrayLiteralAst.cast(node) ?? StringLiteralExprAst.cast(node) ?? NumberLiteralExprAst.cast(node) ?? BooleanLiteralExprAst.cast(node) ?? IdentifierAst.cast(node);
426
+ return FunctionCallAst.cast(node) ?? ArrayLiteralAst.cast(node) ?? StringLiteralExprAst.cast(node) ?? NumberLiteralExprAst.cast(node) ?? BooleanLiteralExprAst.cast(node) ?? ObjectLiteralExprAst.cast(node) ?? IdentifierAst.cast(node);
248
427
  }
249
428
  var AttributeArgAst = class AttributeArgAst {
250
429
  syntax;
@@ -413,7 +592,7 @@ var TypeAnnotationAst = class TypeAnnotationAst {
413
592
  //#endregion
414
593
  //#region src/syntax/ast/declarations.ts
415
594
  function castNamespaceMember(node) {
416
- return ModelDeclarationAst.cast(node) ?? EnumDeclarationAst.cast(node) ?? CompositeTypeDeclarationAst.cast(node) ?? BlockDeclarationAst.cast(node);
595
+ return ModelDeclarationAst.cast(node) ?? EnumDeclarationAst.cast(node) ?? CompositeTypeDeclarationAst.cast(node) ?? GenericBlockDeclarationAst.cast(node);
417
596
  }
418
597
  var DocumentAst = class DocumentAst {
419
598
  syntax;
@@ -553,7 +732,7 @@ var TypesBlockAst = class TypesBlockAst {
553
732
  return node.kind === "TypesBlock" ? new TypesBlockAst(node) : void 0;
554
733
  }
555
734
  };
556
- var BlockDeclarationAst = class BlockDeclarationAst {
735
+ var GenericBlockDeclarationAst = class GenericBlockDeclarationAst {
557
736
  syntax;
558
737
  constructor(syntax) {
559
738
  this.syntax = syntax;
@@ -574,7 +753,7 @@ var BlockDeclarationAst = class BlockDeclarationAst {
574
753
  yield* filterChildren(this.syntax, KeyValuePairAst.cast);
575
754
  }
576
755
  static cast(node) {
577
- return node.kind === "BlockDeclaration" ? new BlockDeclarationAst(node) : void 0;
756
+ return node.kind === "GenericBlockDeclaration" ? new GenericBlockDeclarationAst(node) : void 0;
578
757
  }
579
758
  };
580
759
  var KeyValuePairAst = class KeyValuePairAst {
@@ -703,6 +882,511 @@ var GreenNodeBuilder = class {
703
882
  }
704
883
  };
705
884
  //#endregion
706
- export { ArrayLiteralAst, AttributeArgAst, AttributeArgListAst, BlockDeclarationAst, BooleanLiteralExprAst, CompositeTypeDeclarationAst, DocumentAst, EnumDeclarationAst, EnumValueDeclarationAst, FieldAttributeAst, FieldDeclarationAst, FunctionCallAst, GreenNodeBuilder, IdentifierAst, KeyValuePairAst, ModelAttributeAst, ModelDeclarationAst, NamedTypeDeclarationAst, NamespaceDeclarationAst, NumberLiteralExprAst, StringLiteralExprAst, SyntaxNode, TypeAnnotationAst, TypesBlockAst, castExpression, createSyntaxTree, filterChildren, findChildToken, findFirstChild, greenNode, greenToken };
885
+ //#region src/parse.ts
886
+ const TRIVIA_KINDS = new Set([
887
+ "Whitespace",
888
+ "Newline",
889
+ "Comment"
890
+ ]);
891
+ /**
892
+ * The fault-tolerant parser substrate the leaf and (later) declaration grammars
893
+ * drive. It owns the token cursor, the green-tree builder with its
894
+ * trivia-attachment discipline, the diagnostic sink, and the recovery
895
+ * primitive. Trivia is flushed into the enclosing open node, so every child
896
+ * node spans exactly its first through last significant token.
897
+ */
898
+ var Cursor = class {
899
+ #tokenizer;
900
+ #sourceFile;
901
+ #builder = new GreenNodeBuilder();
902
+ #diagnostics = [];
903
+ #offset = 0;
904
+ #depth = 0;
905
+ constructor(source) {
906
+ this.#tokenizer = new Tokenizer(source);
907
+ this.#sourceFile = new SourceFile(source);
908
+ }
909
+ get diagnostics() {
910
+ return this.#diagnostics;
911
+ }
912
+ get sourceFile() {
913
+ return this.#sourceFile;
914
+ }
915
+ peekKind(ahead = 0) {
916
+ return this.peekToken(ahead).kind;
917
+ }
918
+ peekToken(ahead = 0) {
919
+ let rawIndex = 0;
920
+ let remaining = ahead;
921
+ for (;;) {
922
+ const token = this.#tokenizer.peek(rawIndex);
923
+ if (token.kind === "Eof") return token;
924
+ if (TRIVIA_KINDS.has(token.kind)) {
925
+ rawIndex++;
926
+ continue;
927
+ }
928
+ if (remaining === 0) return token;
929
+ remaining--;
930
+ rawIndex++;
931
+ }
932
+ }
933
+ /**
934
+ * Span of the significant token `lookahead` positions ahead (`mark(0)` = the next,
935
+ * `mark(1)` = the one after), captured eagerly so it stays valid after the cursor advances.
936
+ */
937
+ mark(lookahead = 0) {
938
+ let rawIndex = 0;
939
+ let offset = this.#offset;
940
+ let remaining = lookahead;
941
+ for (;;) {
942
+ const token = this.#tokenizer.peek(rawIndex);
943
+ if (token.kind === "Eof") return {
944
+ offset,
945
+ length: token.text.length
946
+ };
947
+ if (!TRIVIA_KINDS.has(token.kind) && remaining === 0) return {
948
+ offset,
949
+ length: token.text.length
950
+ };
951
+ if (!TRIVIA_KINDS.has(token.kind)) remaining--;
952
+ offset += token.text.length;
953
+ rawIndex++;
954
+ }
955
+ }
956
+ /**
957
+ * Zero-width mark just past the last consumed significant token (before any trailing
958
+ * trivia) — anchors an "expected here" diagnostic at the spot, e.g. the `{` missing
959
+ * after a declaration's name.
960
+ */
961
+ markAfterLastToken() {
962
+ return {
963
+ offset: this.#offset,
964
+ length: 0
965
+ };
966
+ }
967
+ startNode(kind) {
968
+ if (this.#depth > 0) this.flushTrivia();
969
+ this.#builder.startNode(kind);
970
+ this.#depth++;
971
+ }
972
+ finishNode() {
973
+ this.#depth--;
974
+ return this.#builder.finishNode();
975
+ }
976
+ bump() {
977
+ this.flushTrivia();
978
+ const token = this.#tokenizer.peek();
979
+ if (token.kind === "Eof") return token;
980
+ this.#builder.token(token.kind, token.text);
981
+ this.#advance();
982
+ return token;
983
+ }
984
+ recoverToSyncPoint() {
985
+ for (;;) {
986
+ const token = this.#tokenizer.peek();
987
+ if (token.kind === "Eof" || token.kind === "Newline" || token.kind === "RBrace") return;
988
+ this.#builder.token(token.kind, token.text);
989
+ this.#advance();
990
+ }
991
+ }
992
+ flushTrivia() {
993
+ for (;;) {
994
+ const token = this.#tokenizer.peek();
995
+ if (!TRIVIA_KINDS.has(token.kind)) return;
996
+ this.#builder.token(token.kind, token.text);
997
+ this.#advance();
998
+ }
999
+ }
1000
+ diagnostic(code, message, mark) {
1001
+ const start = mark.offset;
1002
+ const end = start + mark.length;
1003
+ this.#diagnostics.push({
1004
+ code,
1005
+ message,
1006
+ range: {
1007
+ start: this.#sourceFile.positionAt(start),
1008
+ end: this.#sourceFile.positionAt(end)
1009
+ }
1010
+ });
1011
+ }
1012
+ #advance() {
1013
+ this.#offset += this.#tokenizer.next().text.length;
1014
+ }
1015
+ };
1016
+ function parseIdentifier(cursor) {
1017
+ cursor.startNode("Identifier");
1018
+ cursor.bump();
1019
+ cursor.finishNode();
1020
+ }
1021
+ /**
1022
+ * Parses a single expression in argument or element position. Returns the
1023
+ * produced node, or `undefined` when the next significant token does not start a
1024
+ * recognised expression (the caller decides how to recover).
1025
+ */
1026
+ function parseExpression(cursor) {
1027
+ return parseStringLiteralExpr(cursor) ?? parseNumberLiteralExpr(cursor) ?? parseArrayLiteral(cursor) ?? parseObjectLiteralExpr(cursor) ?? parseFunctionCall(cursor) ?? parseBooleanLiteralExpr(cursor) ?? parseIdentifierExpr(cursor);
1028
+ }
1029
+ function parseStringLiteralExpr(cursor) {
1030
+ if (cursor.peekKind() !== "StringLiteral") return void 0;
1031
+ const stringMark = cursor.mark();
1032
+ const text = cursor.peekToken().text;
1033
+ cursor.startNode("StringLiteralExpr");
1034
+ cursor.bump();
1035
+ if (!isTerminatedStringLiteral(text)) cursor.diagnostic("PSL_UNTERMINATED_STRING", "Unterminated string literal", stringMark);
1036
+ return cursor.finishNode();
1037
+ }
1038
+ function parseNumberLiteralExpr(cursor) {
1039
+ if (cursor.peekKind() !== "NumberLiteral") return void 0;
1040
+ cursor.startNode("NumberLiteralExpr");
1041
+ cursor.bump();
1042
+ return cursor.finishNode();
1043
+ }
1044
+ function parseBooleanLiteralExpr(cursor) {
1045
+ if (cursor.peekKind() !== "Ident") return void 0;
1046
+ const text = cursor.peekToken().text;
1047
+ if (text !== "true" && text !== "false") return void 0;
1048
+ cursor.startNode("BooleanLiteralExpr");
1049
+ cursor.bump();
1050
+ return cursor.finishNode();
1051
+ }
1052
+ function parseIdentifierExpr(cursor) {
1053
+ if (cursor.peekKind() !== "Ident") return void 0;
1054
+ cursor.startNode("Identifier");
1055
+ cursor.bump();
1056
+ return cursor.finishNode();
1057
+ }
1058
+ function parseArrayLiteral(cursor) {
1059
+ if (cursor.peekKind() !== "LBracket") return void 0;
1060
+ cursor.startNode("ArrayLiteral");
1061
+ cursor.bump();
1062
+ while (cursor.peekKind() !== "RBracket" && cursor.peekKind() !== "Eof") {
1063
+ if (!parseExpression(cursor)) break;
1064
+ if (cursor.peekKind() === "Comma") cursor.bump();
1065
+ else break;
1066
+ }
1067
+ if (cursor.peekKind() === "RBracket") cursor.bump();
1068
+ return cursor.finishNode();
1069
+ }
1070
+ function parseObjectLiteralExpr(cursor) {
1071
+ if (cursor.peekKind() !== "LBrace") return void 0;
1072
+ const braceMark = cursor.mark();
1073
+ cursor.startNode("ObjectLiteralExpr");
1074
+ cursor.bump();
1075
+ while (cursor.peekKind() !== "RBrace" && cursor.peekKind() !== "Eof") {
1076
+ parseObjectField(cursor);
1077
+ if (cursor.peekKind() === "Comma") cursor.bump();
1078
+ else if (cursor.peekKind() === "Ident") cursor.diagnostic("PSL_INVALID_OBJECT_LITERAL", "Expected \",\" between object-literal fields", cursor.markAfterLastToken());
1079
+ else break;
1080
+ }
1081
+ if (cursor.peekKind() === "RBrace") cursor.bump();
1082
+ else cursor.diagnostic("PSL_INVALID_OBJECT_LITERAL", "Unterminated object literal", braceMark);
1083
+ return cursor.finishNode();
1084
+ }
1085
+ function parseObjectField(cursor) {
1086
+ cursor.startNode("ObjectField");
1087
+ const keyMark = cursor.mark();
1088
+ const keyText = cursor.peekToken().text;
1089
+ if (cursor.peekKind() === "Ident") parseIdentifier(cursor);
1090
+ else if (cursor.peekKind() === "StringLiteral") {
1091
+ cursor.diagnostic("PSL_INVALID_OBJECT_LITERAL", "Object literal keys must be identifiers", keyMark);
1092
+ parseStringLiteralExpr(cursor);
1093
+ }
1094
+ if (cursor.peekKind() === "Colon") {
1095
+ cursor.bump();
1096
+ if (!parseExpression(cursor)) cursor.diagnostic("PSL_INVALID_OBJECT_LITERAL", "Expected a value after \":\"", cursor.mark());
1097
+ } else {
1098
+ cursor.diagnostic("PSL_INVALID_OBJECT_LITERAL", `Expected ":" after "${keyText}"`, keyMark);
1099
+ if (!(cursor.peekKind() === "Ident" && cursor.peekKind(1) === "Colon")) parseExpression(cursor);
1100
+ }
1101
+ return cursor.finishNode();
1102
+ }
1103
+ function parseFunctionCall(cursor) {
1104
+ if (cursor.peekKind() !== "Ident" || cursor.peekKind(1) !== "LParen") return void 0;
1105
+ cursor.startNode("FunctionCall");
1106
+ parseIdentifier(cursor);
1107
+ parseParenArgs(cursor);
1108
+ return cursor.finishNode();
1109
+ }
1110
+ /**
1111
+ * Parses a parenthesised, comma-separated `AttributeArg` list into the
1112
+ * currently open node (a `FunctionCall` or an `AttributeArgList`), consuming the
1113
+ * surrounding parentheses.
1114
+ */
1115
+ function parseParenArgs(cursor) {
1116
+ cursor.bump();
1117
+ while (cursor.peekKind() !== "RParen" && cursor.peekKind() !== "Eof") {
1118
+ parseAttributeArg(cursor);
1119
+ if (cursor.peekKind() === "Comma") cursor.bump();
1120
+ else break;
1121
+ }
1122
+ if (cursor.peekKind() === "RParen") cursor.bump();
1123
+ }
1124
+ function parseAttributeArg(cursor) {
1125
+ cursor.startNode("AttributeArg");
1126
+ if (cursor.peekKind() === "Ident" && cursor.peekKind(1) === "Colon") {
1127
+ parseIdentifier(cursor);
1128
+ cursor.bump();
1129
+ }
1130
+ parseArgValue(cursor);
1131
+ return cursor.finishNode();
1132
+ }
1133
+ function parseArgValue(cursor) {
1134
+ parseExpression(cursor);
1135
+ }
1136
+ function parseAttributeArgList(cursor) {
1137
+ cursor.startNode("AttributeArgList");
1138
+ parseParenArgs(cursor);
1139
+ return cursor.finishNode();
1140
+ }
1141
+ function parseAttribute(cursor) {
1142
+ const isBlockAttribute = cursor.peekKind() === "DoubleAt";
1143
+ const attributeMark = cursor.mark();
1144
+ cursor.startNode(isBlockAttribute ? "ModelAttribute" : "FieldAttribute");
1145
+ cursor.bump();
1146
+ if (cursor.peekKind() === "Ident") {
1147
+ parseIdentifier(cursor);
1148
+ if (cursor.peekKind() === "Dot") {
1149
+ cursor.bump();
1150
+ if (cursor.peekKind() === "Ident") parseIdentifier(cursor);
1151
+ else cursor.diagnostic("PSL_INVALID_ATTRIBUTE_SYNTAX", "Attribute name expected after \".\"", cursor.mark());
1152
+ }
1153
+ } else cursor.diagnostic("PSL_INVALID_ATTRIBUTE_SYNTAX", "Attribute name expected", attributeMark);
1154
+ if (cursor.peekKind() === "LParen") parseAttributeArgList(cursor);
1155
+ return cursor.finishNode();
1156
+ }
1157
+ function parseTypeAnnotation(cursor) {
1158
+ cursor.startNode("TypeAnnotation");
1159
+ if (cursor.peekKind() === "Ident" && cursor.peekKind(1) === "LParen") parseFunctionCall(cursor);
1160
+ else if (cursor.peekKind() === "Ident") {
1161
+ parseIdentifier(cursor);
1162
+ parseQualifierSegments(cursor, "Colon");
1163
+ parseQualifierSegments(cursor, "Dot");
1164
+ }
1165
+ if (cursor.peekKind() === "LBracket") {
1166
+ cursor.bump();
1167
+ if (cursor.peekKind() === "RBracket") cursor.bump();
1168
+ }
1169
+ if (cursor.peekKind() === "Question") cursor.bump();
1170
+ return cursor.finishNode();
1171
+ }
1172
+ /**
1173
+ * Consumes a run of `<separator> Ident` qualifier segments. A well-formed type
1174
+ * carries at most one colon-introduced space and one dot-introduced namespace;
1175
+ * any second separator of the same kind is over-qualification and emits
1176
+ * `PSL_INVALID_QUALIFIED_TYPE` pointed at the offending separator, while still
1177
+ * consuming the segment so the subtree (and the round-trip) stays intact.
1178
+ */
1179
+ function parseQualifierSegments(cursor, separator) {
1180
+ let seen = 0;
1181
+ while (cursor.peekKind() === separator) {
1182
+ seen++;
1183
+ const separatorMark = cursor.mark();
1184
+ cursor.bump();
1185
+ if (seen > 1) cursor.diagnostic("PSL_INVALID_QUALIFIED_TYPE", "Qualified type reference has too many segments", separatorMark);
1186
+ if (cursor.peekKind() === "Ident") parseIdentifier(cursor);
1187
+ }
1188
+ }
1189
+ /**
1190
+ * Drives the recursive descent over a full PSL document. Tokenizes via the
1191
+ * substrate cursor, builds a complete green/red tree wrapped as a
1192
+ * {@link DocumentAst}, collects every syntactic {@link ParseDiagnostic}, and
1193
+ * never throws — malformed input yields diagnostics and a recovered tree, not
1194
+ * an exception.
1195
+ */
1196
+ function parse(source) {
1197
+ const cursor = new Cursor(source);
1198
+ const root = createSyntaxTree(parseDocument(cursor));
1199
+ return {
1200
+ document: DocumentAst.cast(root) ?? new DocumentAst(root),
1201
+ diagnostics: cursor.diagnostics,
1202
+ sourceFile: cursor.sourceFile
1203
+ };
1204
+ }
1205
+ function parseDocument(cursor) {
1206
+ cursor.startNode("Document");
1207
+ while (cursor.peekKind() !== "Eof") parseDeclaration(cursor, false);
1208
+ cursor.flushTrivia();
1209
+ return cursor.finishNode();
1210
+ }
1211
+ const RESERVED_BLOCK_KEYWORDS = new Set([
1212
+ "model",
1213
+ "enum",
1214
+ "namespace",
1215
+ "type",
1216
+ "types"
1217
+ ]);
1218
+ function keywordIs(cursor, keyword) {
1219
+ return cursor.peekKind() === "Ident" && cursor.peekToken().text === keyword;
1220
+ }
1221
+ /**
1222
+ * Recognises one top-level (or namespace-body) declaration as an ordered list of
1223
+ * alternatives composed with `??`. Each alternative owns its discriminating
1224
+ * `peekKind`/`peekToken` lookahead and is a no-op on non-match: it returns
1225
+ * `undefined` having consumed and mutated nothing, so the forward-only cursor is
1226
+ * never left half-consumed by a rejected alternative. The first alternative to
1227
+ * commit wins; when none match, the input is recovered as an unsupported
1228
+ * declaration. Recovery runs via the `if (!node)` tail rather than as a `??`
1229
+ * arm, because it appends raw tokens to the open parent instead of returning a
1230
+ * child node.
1231
+ */
1232
+ function parseDeclaration(cursor, insideNamespace) {
1233
+ const name = cursor.peekKind(1) === "Ident" ? cursor.peekToken(1).text : "";
1234
+ 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());
1235
+ 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());
1236
+ 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));
1237
+ if (!(parseModel(cursor) ?? parseEnum(cursor) ?? parseNamespace(cursor) ?? parseCompositeType(cursor) ?? parseTypesBlock(cursor) ?? parseGenericBlock(cursor))) parseUnsupportedTopLevel(cursor);
1238
+ }
1239
+ /**
1240
+ * Reports only the first missing piece — a missing name suppresses the missing-brace
1241
+ * diagnostic. `nameRequired` is false only for the `types` block, which never has a name.
1242
+ */
1243
+ function parseBlock(cursor, kind, nameRequired, parseMember) {
1244
+ const keyword = cursor.peekToken().text;
1245
+ const keywordMark = cursor.mark();
1246
+ cursor.startNode(kind);
1247
+ cursor.bump();
1248
+ const hasName = nameRequired && cursor.peekKind() === "Ident";
1249
+ if (hasName) parseIdentifier(cursor);
1250
+ if (nameRequired && !hasName) cursor.diagnostic("PSL_INVALID_DECLARATION", `Expected a name after "${keyword}"`, keywordMark);
1251
+ else if (cursor.peekKind() !== "LBrace") cursor.diagnostic("PSL_INVALID_DECLARATION", `Expected "{" to open the "${keyword}" block`, cursor.markAfterLastToken());
1252
+ if (cursor.peekKind() === "LBrace") parseBlockBody(cursor, parseMember);
1253
+ else cursor.recoverToSyncPoint();
1254
+ return cursor.finishNode();
1255
+ }
1256
+ function parseModel(cursor) {
1257
+ if (!keywordIs(cursor, "model")) return void 0;
1258
+ return parseBlock(cursor, "ModelDeclaration", true, parseModelMember);
1259
+ }
1260
+ function parseEnum(cursor) {
1261
+ if (!keywordIs(cursor, "enum")) return void 0;
1262
+ return parseBlock(cursor, "EnumDeclaration", true, parseEnumMember);
1263
+ }
1264
+ /**
1265
+ * Excluding the reserved keywords keeps a malformed reserved block (e.g. `model {` with
1266
+ * no name) routed to its dedicated parser. The generic keyword set is open
1267
+ * (extension-contributed), so a bare identifier with no brace (e.g. `oops`) is read as an
1268
+ * unfinished custom declaration — a committed `GenericBlockDeclaration` + missing-brace diagnostic
1269
+ * — not unsupported content. A non-identifier lead can't be a declaration name, so it falls
1270
+ * through to `parseUnsupportedTopLevel`.
1271
+ */
1272
+ function parseGenericBlock(cursor) {
1273
+ if (cursor.peekKind() !== "Ident") return void 0;
1274
+ const keyword = cursor.peekToken().text;
1275
+ if (RESERVED_BLOCK_KEYWORDS.has(keyword)) return void 0;
1276
+ const hasName = cursor.peekKind(1) === "Ident" && cursor.peekKind(2) === "LBrace";
1277
+ cursor.startNode("GenericBlockDeclaration");
1278
+ cursor.bump();
1279
+ if (hasName) parseIdentifier(cursor);
1280
+ if (cursor.peekKind() === "LBrace") parseBlockBody(cursor, parseKeyValueMember);
1281
+ else {
1282
+ cursor.diagnostic("PSL_INVALID_DECLARATION", `Expected "{" to open the "${keyword}" block`, cursor.markAfterLastToken());
1283
+ cursor.recoverToSyncPoint();
1284
+ }
1285
+ return cursor.finishNode();
1286
+ }
1287
+ function parseNamespace(cursor) {
1288
+ if (!keywordIs(cursor, "namespace")) return void 0;
1289
+ return parseBlock(cursor, "Namespace", true, (inner) => parseDeclaration(inner, true));
1290
+ }
1291
+ function parseCompositeType(cursor) {
1292
+ if (!keywordIs(cursor, "type")) return void 0;
1293
+ return parseBlock(cursor, "CompositeTypeDeclaration", true, parseModelMember);
1294
+ }
1295
+ /** `types` (plural) is the no-name types block; the singular `type` is the composite type above. */
1296
+ function parseTypesBlock(cursor) {
1297
+ if (!keywordIs(cursor, "types")) return void 0;
1298
+ return parseBlock(cursor, "TypesBlock", false, parseNamedTypeMember);
1299
+ }
1300
+ /**
1301
+ * Parses a `{ … }` block body: consumes the braces, dispatches each member to
1302
+ * `parseMember` until the closing brace or EOF, and flags an unclosed block.
1303
+ * Every `parseMember` consumes at least one significant token, so the loop
1304
+ * always terminates.
1305
+ */
1306
+ function parseBlockBody(cursor, parseMember) {
1307
+ const braceMark = cursor.mark();
1308
+ cursor.bump();
1309
+ for (;;) {
1310
+ const kind = cursor.peekKind();
1311
+ if (kind === "RBrace" || kind === "Eof") break;
1312
+ parseMember(cursor);
1313
+ }
1314
+ if (cursor.peekKind() === "RBrace") cursor.bump();
1315
+ else cursor.diagnostic("PSL_UNTERMINATED_BLOCK", "Unterminated block declaration", braceMark);
1316
+ }
1317
+ function parseUnsupportedTopLevel(cursor) {
1318
+ const offending = cursor.peekToken().text;
1319
+ const message = cursor.peekKind(1) === "LBrace" ? `Unsupported top-level block "${offending}"` : `Unsupported top-level declaration "${offending}"`;
1320
+ cursor.diagnostic("PSL_UNSUPPORTED_TOP_LEVEL_BLOCK", message, cursor.mark());
1321
+ cursor.bump();
1322
+ cursor.recoverToSyncPoint();
1323
+ }
1324
+ /**
1325
+ * Block-attribute alternative shared by model and enum members: matches a
1326
+ * leading `@@` (yielding a `ModelAttribute`) and is a no-op on anything else. The
1327
+ * `@@`-vs-`@` distinction is preserved exactly — single-`@` attributes belong to
1328
+ * fields and enum values and are parsed inside `parseField`/`parseEnumValue`.
1329
+ */
1330
+ function parseBlockAttribute(cursor) {
1331
+ if (cursor.peekKind() !== "DoubleAt") return void 0;
1332
+ return parseAttribute(cursor);
1333
+ }
1334
+ function parseModelMember(cursor) {
1335
+ if (!(parseBlockAttribute(cursor) ?? parseField(cursor))) invalidMember(cursor, "PSL_INVALID_MODEL_MEMBER", `Invalid model member declaration "${cursor.peekToken().text}"`);
1336
+ }
1337
+ function parseEnumMember(cursor) {
1338
+ if (!(parseBlockAttribute(cursor) ?? parseEnumValue(cursor))) invalidMember(cursor, "PSL_INVALID_ENUM_MEMBER", `Invalid enum value declaration "${cursor.peekToken().text}"`);
1339
+ }
1340
+ function parseNamedTypeMember(cursor) {
1341
+ if (!parseNamedType(cursor)) invalidMember(cursor, "PSL_INVALID_TYPES_MEMBER", `Invalid types declaration "${cursor.peekToken().text}"`);
1342
+ }
1343
+ function parseKeyValueMember(cursor) {
1344
+ if (!parseKeyValue(cursor)) invalidMember(cursor, "PSL_INVALID_EXTENSION_BLOCK_MEMBER", "Invalid block entry");
1345
+ }
1346
+ function invalidMember(cursor, code, message) {
1347
+ cursor.diagnostic(code, message, cursor.mark());
1348
+ cursor.bump();
1349
+ cursor.recoverToSyncPoint();
1350
+ }
1351
+ function parseField(cursor) {
1352
+ if (cursor.peekKind() !== "Ident") return void 0;
1353
+ cursor.startNode("FieldDeclaration");
1354
+ parseIdentifier(cursor);
1355
+ parseTypeAnnotation(cursor);
1356
+ while (cursor.peekKind() === "At") parseAttribute(cursor);
1357
+ return cursor.finishNode();
1358
+ }
1359
+ function parseEnumValue(cursor) {
1360
+ if (cursor.peekKind() !== "Ident") return void 0;
1361
+ cursor.startNode("EnumValueDeclaration");
1362
+ parseIdentifier(cursor);
1363
+ while (cursor.peekKind() === "At") parseAttribute(cursor);
1364
+ return cursor.finishNode();
1365
+ }
1366
+ function parseNamedType(cursor) {
1367
+ if (cursor.peekKind() !== "Ident") return void 0;
1368
+ cursor.startNode("NamedTypeDeclaration");
1369
+ const nameMark = cursor.mark();
1370
+ const nameText = cursor.peekToken().text;
1371
+ parseIdentifier(cursor);
1372
+ if (cursor.peekKind() === "Equals") cursor.bump();
1373
+ else cursor.diagnostic("PSL_INVALID_TYPES_MEMBER", `Expected "=" after "${nameText}"`, nameMark);
1374
+ parseTypeAnnotation(cursor);
1375
+ while (cursor.peekKind() === "At") parseAttribute(cursor);
1376
+ return cursor.finishNode();
1377
+ }
1378
+ function parseKeyValue(cursor) {
1379
+ if (cursor.peekKind() !== "Ident") return void 0;
1380
+ cursor.startNode("KeyValuePair");
1381
+ const keyMark = cursor.mark();
1382
+ const keyText = cursor.peekToken().text;
1383
+ parseIdentifier(cursor);
1384
+ if (cursor.peekKind() === "Equals") cursor.bump();
1385
+ else cursor.diagnostic("PSL_INVALID_EXTENSION_BLOCK_MEMBER", `Expected "=" after "${keyText}"`, keyMark);
1386
+ parseExpression(cursor);
1387
+ return cursor.finishNode();
1388
+ }
1389
+ //#endregion
1390
+ export { ArrayLiteralAst, AttributeArgAst, AttributeArgListAst, BooleanLiteralExprAst, CompositeTypeDeclarationAst, DocumentAst, EnumDeclarationAst, EnumValueDeclarationAst, FieldAttributeAst, FieldDeclarationAst, FunctionCallAst, GenericBlockDeclarationAst, GreenNodeBuilder, IdentifierAst, KeyValuePairAst, ModelAttributeAst, ModelDeclarationAst, NamedTypeDeclarationAst, NamespaceDeclarationAst, NumberLiteralExprAst, ObjectFieldAst, ObjectLiteralExprAst, SourceFile, StringLiteralExprAst, SyntaxNode, TypeAnnotationAst, TypesBlockAst, castExpression, createSyntaxTree, filterChildren, findChildToken, findFirstChild, greenNode, greenToken, parse };
707
1391
 
708
1392
  //# sourceMappingURL=syntax.mjs.map