@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/index.d.mts +2 -2
- package/dist/index.mjs +3 -2
- package/dist/index.mjs.map +1 -1
- package/dist/{parser-Cw_zV0M5.mjs → parser-CaplKvRs.mjs} +110 -141
- package/dist/parser-CaplKvRs.mjs.map +1 -0
- package/dist/parser.mjs +1 -1
- package/dist/syntax.d.mts +63 -6
- package/dist/syntax.d.mts.map +1 -1
- package/dist/syntax.mjs +699 -15
- package/dist/syntax.mjs.map +1 -1
- package/dist/tokenizer-DdaOstnz.mjs +233 -0
- package/dist/tokenizer-DdaOstnz.mjs.map +1 -0
- package/dist/tokenizer.mjs +1 -190
- package/package.json +5 -5
- package/src/exports/index.ts +13 -2
- package/src/exports/syntax.ts +7 -1
- package/src/parse.ts +767 -0
- package/src/parser.ts +125 -196
- package/src/source-file.ts +89 -0
- package/src/syntax/ast/declarations.ts +7 -5
- package/src/syntax/ast/expressions.ts +144 -17
- package/src/syntax/syntax-kind.ts +7 -2
- package/src/tokenizer.ts +48 -0
- package/dist/parser-Cw_zV0M5.mjs.map +0 -1
- package/dist/tokenizer.mjs.map +0 -1
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)
|
|
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) ??
|
|
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
|
|
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 === "
|
|
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
|
-
|
|
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
|