@prisma-next/psl-parser 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +115 -40
- package/dist/syntax.d.mts.map +1 -1
- package/dist/syntax.mjs +815 -105
- package/dist/syntax.mjs.map +1 -1
- package/dist/tokenizer-1hAHZzmp.mjs +228 -0
- package/dist/tokenizer-1hAHZzmp.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 +9 -4
- package/src/parse.ts +742 -0
- package/src/parser.ts +125 -196
- package/src/source-file.ts +89 -0
- package/src/syntax/ast/attributes.ts +5 -24
- package/src/syntax/ast/declarations.ts +14 -67
- package/src/syntax/ast/expressions.ts +187 -19
- package/src/syntax/ast/identifier.ts +4 -0
- package/src/syntax/ast/qualified-name.ts +87 -0
- package/src/syntax/ast/type-annotation.ts +11 -41
- package/src/syntax/ast-helpers.ts +12 -0
- package/src/syntax/syntax-kind.ts +8 -4
- package/src/tokenizer.ts +47 -7
- 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-1hAHZzmp.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;
|
|
@@ -127,6 +190,15 @@ function* filterChildren(node, cast) {
|
|
|
127
190
|
if (result !== void 0) yield result;
|
|
128
191
|
}
|
|
129
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Raw source text of a CST node, verbatim (quotes and brackets preserved). For
|
|
195
|
+
* the decoded value of a string literal, decode it instead.
|
|
196
|
+
*/
|
|
197
|
+
function printSyntax(node) {
|
|
198
|
+
let text = "";
|
|
199
|
+
for (const token of node.tokens()) text += token.text;
|
|
200
|
+
return text;
|
|
201
|
+
}
|
|
130
202
|
//#endregion
|
|
131
203
|
//#region src/syntax/ast/identifier.ts
|
|
132
204
|
var IdentifierAst = class IdentifierAst {
|
|
@@ -137,19 +209,104 @@ var IdentifierAst = class IdentifierAst {
|
|
|
137
209
|
token() {
|
|
138
210
|
return findChildToken(this.syntax, "Ident");
|
|
139
211
|
}
|
|
212
|
+
name() {
|
|
213
|
+
return this.token()?.text;
|
|
214
|
+
}
|
|
140
215
|
static cast(node) {
|
|
141
216
|
return node.kind === "Identifier" ? new IdentifierAst(node) : void 0;
|
|
142
217
|
}
|
|
143
218
|
};
|
|
144
219
|
//#endregion
|
|
220
|
+
//#region src/syntax/ast/qualified-name.ts
|
|
221
|
+
/** A namespace-qualified name, e.g. `pgvector.Vector` or `supabase:auth.User`. */
|
|
222
|
+
var QualifiedNameAst = class QualifiedNameAst {
|
|
223
|
+
syntax;
|
|
224
|
+
constructor(syntax) {
|
|
225
|
+
this.syntax = syntax;
|
|
226
|
+
}
|
|
227
|
+
#lastSegment() {
|
|
228
|
+
let last;
|
|
229
|
+
for (const segment of filterChildren(this.syntax, IdentifierAst.cast)) last = segment;
|
|
230
|
+
return last;
|
|
231
|
+
}
|
|
232
|
+
#penultimateSegment() {
|
|
233
|
+
let last;
|
|
234
|
+
let penultimate;
|
|
235
|
+
for (const segment of filterChildren(this.syntax, IdentifierAst.cast)) {
|
|
236
|
+
penultimate = last;
|
|
237
|
+
last = segment;
|
|
238
|
+
}
|
|
239
|
+
return penultimate;
|
|
240
|
+
}
|
|
241
|
+
#separatorCount(kind) {
|
|
242
|
+
let count = 0;
|
|
243
|
+
for (const child of this.syntax.children()) if (!(child instanceof SyntaxNode) && child.kind === kind) count++;
|
|
244
|
+
return count;
|
|
245
|
+
}
|
|
246
|
+
colon() {
|
|
247
|
+
return findChildToken(this.syntax, "Colon");
|
|
248
|
+
}
|
|
249
|
+
dot() {
|
|
250
|
+
return findChildToken(this.syntax, "Dot");
|
|
251
|
+
}
|
|
252
|
+
space() {
|
|
253
|
+
if (!this.colon()) return void 0;
|
|
254
|
+
return findFirstChild(this.syntax, IdentifierAst.cast);
|
|
255
|
+
}
|
|
256
|
+
namespace() {
|
|
257
|
+
if (!this.dot()) return void 0;
|
|
258
|
+
return this.#penultimateSegment();
|
|
259
|
+
}
|
|
260
|
+
identifier() {
|
|
261
|
+
return this.#lastSegment();
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Every identifier segment, in source order. A bare `Vector` yields
|
|
265
|
+
* `['Vector']`; a qualified `pgvector.Vector` yields `['pgvector', 'Vector']`.
|
|
266
|
+
*/
|
|
267
|
+
path() {
|
|
268
|
+
const segments = [];
|
|
269
|
+
for (const segment of filterChildren(this.syntax, IdentifierAst.cast)) {
|
|
270
|
+
const text = segment.token()?.text;
|
|
271
|
+
if (text !== void 0) segments.push(text);
|
|
272
|
+
}
|
|
273
|
+
return segments;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Flags a malformed name with more qualifier segments than allowed (a second
|
|
277
|
+
* `:`-space or a second `.`-namespace).
|
|
278
|
+
*/
|
|
279
|
+
isOverQualified() {
|
|
280
|
+
return this.#separatorCount("Dot") > 1 || this.#separatorCount("Colon") > 1;
|
|
281
|
+
}
|
|
282
|
+
static cast(node) {
|
|
283
|
+
return node.kind === "QualifiedName" ? new QualifiedNameAst(node) : void 0;
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
//#endregion
|
|
145
287
|
//#region src/syntax/ast/expressions.ts
|
|
146
288
|
var FunctionCallAst = class FunctionCallAst {
|
|
147
289
|
syntax;
|
|
148
290
|
constructor(syntax) {
|
|
149
291
|
this.syntax = syntax;
|
|
150
292
|
}
|
|
293
|
+
/** The qualified-name callee, or `undefined` when identifier segments sit directly under the node. */
|
|
151
294
|
name() {
|
|
152
|
-
return findFirstChild(this.syntax,
|
|
295
|
+
return findFirstChild(this.syntax, QualifiedNameAst.cast);
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* The dotted call path, in source order. A bare `Vector(…)` yields
|
|
299
|
+
* `['Vector']`; a namespace-qualified `pgvector.Vector(…)` yields
|
|
300
|
+
* `['pgvector', 'Vector']`. Empty when the call carries no identifier.
|
|
301
|
+
*/
|
|
302
|
+
path() {
|
|
303
|
+
const qualified = this.name();
|
|
304
|
+
const segments = [];
|
|
305
|
+
for (const segment of filterChildren(qualified?.syntax ?? this.syntax, IdentifierAst.cast)) {
|
|
306
|
+
const text = segment.token()?.text;
|
|
307
|
+
if (text !== void 0) segments.push(text);
|
|
308
|
+
}
|
|
309
|
+
return segments;
|
|
153
310
|
}
|
|
154
311
|
lparen() {
|
|
155
312
|
return findChildToken(this.syntax, "LParen");
|
|
@@ -182,6 +339,79 @@ var ArrayLiteralAst = class ArrayLiteralAst {
|
|
|
182
339
|
return node.kind === "ArrayLiteral" ? new ArrayLiteralAst(node) : void 0;
|
|
183
340
|
}
|
|
184
341
|
};
|
|
342
|
+
const HEX = /^[0-9a-fA-F]+$/;
|
|
343
|
+
function decodeFixedHex(raw, start, width) {
|
|
344
|
+
if (start + width > raw.length) return void 0;
|
|
345
|
+
const hex = raw.slice(start, start + width);
|
|
346
|
+
if (!HEX.test(hex)) return void 0;
|
|
347
|
+
return String.fromCharCode(Number.parseInt(hex, 16));
|
|
348
|
+
}
|
|
349
|
+
function decodeStringLiteral(raw) {
|
|
350
|
+
let out = "";
|
|
351
|
+
let i = 0;
|
|
352
|
+
while (i < raw.length) {
|
|
353
|
+
const ch = raw.charAt(i);
|
|
354
|
+
if (ch !== "\\" || i + 1 >= raw.length) {
|
|
355
|
+
out += ch;
|
|
356
|
+
i++;
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
const next = raw.charAt(i + 1);
|
|
360
|
+
switch (next) {
|
|
361
|
+
case "n":
|
|
362
|
+
out += "\n";
|
|
363
|
+
i += 2;
|
|
364
|
+
continue;
|
|
365
|
+
case "r":
|
|
366
|
+
out += "\r";
|
|
367
|
+
i += 2;
|
|
368
|
+
continue;
|
|
369
|
+
case "t":
|
|
370
|
+
out += " ";
|
|
371
|
+
i += 2;
|
|
372
|
+
continue;
|
|
373
|
+
case "\"":
|
|
374
|
+
out += "\"";
|
|
375
|
+
i += 2;
|
|
376
|
+
continue;
|
|
377
|
+
case "'":
|
|
378
|
+
out += "'";
|
|
379
|
+
i += 2;
|
|
380
|
+
continue;
|
|
381
|
+
case "\\":
|
|
382
|
+
out += "\\";
|
|
383
|
+
i += 2;
|
|
384
|
+
continue;
|
|
385
|
+
case "x": {
|
|
386
|
+
const decoded = decodeFixedHex(raw, i + 2, 2);
|
|
387
|
+
if (decoded === void 0) {
|
|
388
|
+
out += "\\x";
|
|
389
|
+
i += 2;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
out += decoded;
|
|
393
|
+
i += 4;
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
case "u": {
|
|
397
|
+
const decoded = decodeFixedHex(raw, i + 2, 4);
|
|
398
|
+
if (decoded === void 0) {
|
|
399
|
+
out += "\\u";
|
|
400
|
+
i += 2;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
out += decoded;
|
|
404
|
+
i += 6;
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
default:
|
|
408
|
+
out += `\\${next}`;
|
|
409
|
+
i += 2;
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return out;
|
|
414
|
+
}
|
|
185
415
|
var StringLiteralExprAst = class StringLiteralExprAst {
|
|
186
416
|
syntax;
|
|
187
417
|
constructor(syntax) {
|
|
@@ -193,16 +423,7 @@ var StringLiteralExprAst = class StringLiteralExprAst {
|
|
|
193
423
|
value() {
|
|
194
424
|
const tok = this.token();
|
|
195
425
|
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
|
-
});
|
|
426
|
+
return decodeStringLiteral(tok.text.slice(1, -1));
|
|
206
427
|
}
|
|
207
428
|
static cast(node) {
|
|
208
429
|
return node.kind === "StringLiteralExpr" ? new StringLiteralExprAst(node) : void 0;
|
|
@@ -243,8 +464,82 @@ var BooleanLiteralExprAst = class BooleanLiteralExprAst {
|
|
|
243
464
|
return node.kind === "BooleanLiteralExpr" ? new BooleanLiteralExprAst(node) : void 0;
|
|
244
465
|
}
|
|
245
466
|
};
|
|
467
|
+
var ObjectLiteralExprAst = class ObjectLiteralExprAst {
|
|
468
|
+
syntax;
|
|
469
|
+
constructor(syntax) {
|
|
470
|
+
this.syntax = syntax;
|
|
471
|
+
}
|
|
472
|
+
lbrace() {
|
|
473
|
+
return findChildToken(this.syntax, "LBrace");
|
|
474
|
+
}
|
|
475
|
+
rbrace() {
|
|
476
|
+
return findChildToken(this.syntax, "RBrace");
|
|
477
|
+
}
|
|
478
|
+
*fields() {
|
|
479
|
+
yield* filterChildren(this.syntax, ObjectFieldAst.cast);
|
|
480
|
+
}
|
|
481
|
+
static cast(node) {
|
|
482
|
+
return node.kind === "ObjectLiteralExpr" ? new ObjectLiteralExprAst(node) : void 0;
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
var ObjectFieldAst = class ObjectFieldAst {
|
|
486
|
+
syntax;
|
|
487
|
+
constructor(syntax) {
|
|
488
|
+
this.syntax = syntax;
|
|
489
|
+
}
|
|
490
|
+
key() {
|
|
491
|
+
for (const child of this.syntax.children()) {
|
|
492
|
+
if (!(child instanceof SyntaxNode)) {
|
|
493
|
+
if (child.kind === "Colon") break;
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
return IdentifierAst.cast(child);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* The field's logical key name, unquoted. An identifier key (`length:`) yields
|
|
501
|
+
* its text; a string-literal key (`"length":`) yields the decoded string.
|
|
502
|
+
* `undefined` when the field carries no key node.
|
|
503
|
+
*/
|
|
504
|
+
keyName() {
|
|
505
|
+
for (const child of this.syntax.children()) {
|
|
506
|
+
if (!(child instanceof SyntaxNode)) {
|
|
507
|
+
if (child.kind === "Colon") break;
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
const identifier = IdentifierAst.cast(child);
|
|
511
|
+
if (identifier) return identifier.token()?.text;
|
|
512
|
+
const stringKey = StringLiteralExprAst.cast(child);
|
|
513
|
+
if (stringKey) return stringKey.value();
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
colon() {
|
|
518
|
+
return findChildToken(this.syntax, "Colon");
|
|
519
|
+
}
|
|
520
|
+
value() {
|
|
521
|
+
if (this.colon()) {
|
|
522
|
+
let pastColon = false;
|
|
523
|
+
for (const child of this.syntax.children()) {
|
|
524
|
+
if (!(child instanceof SyntaxNode)) {
|
|
525
|
+
if (child.kind === "Colon") pastColon = true;
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
if (pastColon) {
|
|
529
|
+
const expr = castExpression(child);
|
|
530
|
+
if (expr) return expr;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
return findFirstChild(this.syntax, castExpression);
|
|
536
|
+
}
|
|
537
|
+
static cast(node) {
|
|
538
|
+
return node.kind === "ObjectField" ? new ObjectFieldAst(node) : void 0;
|
|
539
|
+
}
|
|
540
|
+
};
|
|
246
541
|
function castExpression(node) {
|
|
247
|
-
return FunctionCallAst.cast(node) ?? ArrayLiteralAst.cast(node) ?? StringLiteralExprAst.cast(node) ?? NumberLiteralExprAst.cast(node) ?? BooleanLiteralExprAst.cast(node) ?? IdentifierAst.cast(node);
|
|
542
|
+
return FunctionCallAst.cast(node) ?? ArrayLiteralAst.cast(node) ?? StringLiteralExprAst.cast(node) ?? NumberLiteralExprAst.cast(node) ?? BooleanLiteralExprAst.cast(node) ?? ObjectLiteralExprAst.cast(node) ?? IdentifierAst.cast(node);
|
|
248
543
|
}
|
|
249
544
|
var AttributeArgAst = class AttributeArgAst {
|
|
250
545
|
syntax;
|
|
@@ -308,22 +603,7 @@ var FieldAttributeAst = class FieldAttributeAst {
|
|
|
308
603
|
return findChildToken(this.syntax, "At");
|
|
309
604
|
}
|
|
310
605
|
name() {
|
|
311
|
-
|
|
312
|
-
let count = 0;
|
|
313
|
-
for (const child of this.syntax.childNodes()) if (child.kind === "Identifier") {
|
|
314
|
-
count++;
|
|
315
|
-
if (count === 2) return new IdentifierAst(child);
|
|
316
|
-
}
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
return findFirstChild(this.syntax, IdentifierAst.cast);
|
|
320
|
-
}
|
|
321
|
-
dot() {
|
|
322
|
-
return findChildToken(this.syntax, "Dot");
|
|
323
|
-
}
|
|
324
|
-
namespaceName() {
|
|
325
|
-
if (!this.dot()) return void 0;
|
|
326
|
-
return findFirstChild(this.syntax, IdentifierAst.cast);
|
|
606
|
+
return findFirstChild(this.syntax, QualifiedNameAst.cast);
|
|
327
607
|
}
|
|
328
608
|
argList() {
|
|
329
609
|
return findFirstChild(this.syntax, AttributeArgListAst.cast);
|
|
@@ -341,7 +621,7 @@ var ModelAttributeAst = class ModelAttributeAst {
|
|
|
341
621
|
return findChildToken(this.syntax, "DoubleAt");
|
|
342
622
|
}
|
|
343
623
|
name() {
|
|
344
|
-
return findFirstChild(this.syntax,
|
|
624
|
+
return findFirstChild(this.syntax, QualifiedNameAst.cast);
|
|
345
625
|
}
|
|
346
626
|
argList() {
|
|
347
627
|
return findFirstChild(this.syntax, AttributeArgListAst.cast);
|
|
@@ -357,39 +637,16 @@ var TypeAnnotationAst = class TypeAnnotationAst {
|
|
|
357
637
|
constructor(syntax) {
|
|
358
638
|
this.syntax = syntax;
|
|
359
639
|
}
|
|
360
|
-
|
|
361
|
-
let last;
|
|
362
|
-
for (const segment of filterChildren(this.syntax, IdentifierAst.cast)) last = segment;
|
|
363
|
-
return last;
|
|
364
|
-
}
|
|
365
|
-
#penultimateSegment() {
|
|
366
|
-
let last;
|
|
367
|
-
let penultimate;
|
|
368
|
-
for (const segment of filterChildren(this.syntax, IdentifierAst.cast)) {
|
|
369
|
-
penultimate = last;
|
|
370
|
-
last = segment;
|
|
371
|
-
}
|
|
372
|
-
return penultimate;
|
|
373
|
-
}
|
|
640
|
+
/** The annotation's reference, doubling as the constructor callee when an {@link argList} follows. */
|
|
374
641
|
name() {
|
|
375
|
-
return this
|
|
376
|
-
}
|
|
377
|
-
colon() {
|
|
378
|
-
return findChildToken(this.syntax, "Colon");
|
|
642
|
+
return findFirstChild(this.syntax, QualifiedNameAst.cast);
|
|
379
643
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
spaceName() {
|
|
384
|
-
if (!this.colon()) return void 0;
|
|
385
|
-
return findFirstChild(this.syntax, IdentifierAst.cast);
|
|
386
|
-
}
|
|
387
|
-
namespaceName() {
|
|
388
|
-
if (!this.dot()) return void 0;
|
|
389
|
-
return this.#penultimateSegment();
|
|
644
|
+
/** Present when the annotation is a constructor (`Vector(1536)`) rather than a plain reference. */
|
|
645
|
+
argList() {
|
|
646
|
+
return findFirstChild(this.syntax, AttributeArgListAst.cast);
|
|
390
647
|
}
|
|
391
|
-
|
|
392
|
-
return
|
|
648
|
+
isConstructor() {
|
|
649
|
+
return this.argList() !== void 0;
|
|
393
650
|
}
|
|
394
651
|
lbracket() {
|
|
395
652
|
return findChildToken(this.syntax, "LBracket");
|
|
@@ -413,7 +670,7 @@ var TypeAnnotationAst = class TypeAnnotationAst {
|
|
|
413
670
|
//#endregion
|
|
414
671
|
//#region src/syntax/ast/declarations.ts
|
|
415
672
|
function castNamespaceMember(node) {
|
|
416
|
-
return ModelDeclarationAst.cast(node) ??
|
|
673
|
+
return ModelDeclarationAst.cast(node) ?? CompositeTypeDeclarationAst.cast(node) ?? GenericBlockDeclarationAst.cast(node);
|
|
417
674
|
}
|
|
418
675
|
var DocumentAst = class DocumentAst {
|
|
419
676
|
syntax;
|
|
@@ -454,33 +711,6 @@ var ModelDeclarationAst = class ModelDeclarationAst {
|
|
|
454
711
|
return node.kind === "ModelDeclaration" ? new ModelDeclarationAst(node) : void 0;
|
|
455
712
|
}
|
|
456
713
|
};
|
|
457
|
-
var EnumDeclarationAst = class EnumDeclarationAst {
|
|
458
|
-
syntax;
|
|
459
|
-
constructor(syntax) {
|
|
460
|
-
this.syntax = syntax;
|
|
461
|
-
}
|
|
462
|
-
keyword() {
|
|
463
|
-
return findChildToken(this.syntax, "Ident");
|
|
464
|
-
}
|
|
465
|
-
name() {
|
|
466
|
-
return findFirstChild(this.syntax, IdentifierAst.cast);
|
|
467
|
-
}
|
|
468
|
-
lbrace() {
|
|
469
|
-
return findChildToken(this.syntax, "LBrace");
|
|
470
|
-
}
|
|
471
|
-
rbrace() {
|
|
472
|
-
return findChildToken(this.syntax, "RBrace");
|
|
473
|
-
}
|
|
474
|
-
*values() {
|
|
475
|
-
yield* filterChildren(this.syntax, EnumValueDeclarationAst.cast);
|
|
476
|
-
}
|
|
477
|
-
*attributes() {
|
|
478
|
-
yield* filterChildren(this.syntax, ModelAttributeAst.cast);
|
|
479
|
-
}
|
|
480
|
-
static cast(node) {
|
|
481
|
-
return node.kind === "EnumDeclaration" ? new EnumDeclarationAst(node) : void 0;
|
|
482
|
-
}
|
|
483
|
-
};
|
|
484
714
|
var CompositeTypeDeclarationAst = class CompositeTypeDeclarationAst {
|
|
485
715
|
syntax;
|
|
486
716
|
constructor(syntax) {
|
|
@@ -553,7 +783,7 @@ var TypesBlockAst = class TypesBlockAst {
|
|
|
553
783
|
return node.kind === "TypesBlock" ? new TypesBlockAst(node) : void 0;
|
|
554
784
|
}
|
|
555
785
|
};
|
|
556
|
-
var
|
|
786
|
+
var GenericBlockDeclarationAst = class GenericBlockDeclarationAst {
|
|
557
787
|
syntax;
|
|
558
788
|
constructor(syntax) {
|
|
559
789
|
this.syntax = syntax;
|
|
@@ -573,8 +803,11 @@ var BlockDeclarationAst = class BlockDeclarationAst {
|
|
|
573
803
|
*entries() {
|
|
574
804
|
yield* filterChildren(this.syntax, KeyValuePairAst.cast);
|
|
575
805
|
}
|
|
806
|
+
*attributes() {
|
|
807
|
+
yield* filterChildren(this.syntax, ModelAttributeAst.cast);
|
|
808
|
+
}
|
|
576
809
|
static cast(node) {
|
|
577
|
-
return node.kind === "
|
|
810
|
+
return node.kind === "GenericBlockDeclaration" ? new GenericBlockDeclarationAst(node) : void 0;
|
|
578
811
|
}
|
|
579
812
|
};
|
|
580
813
|
var KeyValuePairAst = class KeyValuePairAst {
|
|
@@ -623,21 +856,6 @@ var FieldDeclarationAst = class FieldDeclarationAst {
|
|
|
623
856
|
return node.kind === "FieldDeclaration" ? new FieldDeclarationAst(node) : void 0;
|
|
624
857
|
}
|
|
625
858
|
};
|
|
626
|
-
var EnumValueDeclarationAst = class EnumValueDeclarationAst {
|
|
627
|
-
syntax;
|
|
628
|
-
constructor(syntax) {
|
|
629
|
-
this.syntax = syntax;
|
|
630
|
-
}
|
|
631
|
-
name() {
|
|
632
|
-
return findFirstChild(this.syntax, IdentifierAst.cast);
|
|
633
|
-
}
|
|
634
|
-
*attributes() {
|
|
635
|
-
yield* filterChildren(this.syntax, FieldAttributeAst.cast);
|
|
636
|
-
}
|
|
637
|
-
static cast(node) {
|
|
638
|
-
return node.kind === "EnumValueDeclaration" ? new EnumValueDeclarationAst(node) : void 0;
|
|
639
|
-
}
|
|
640
|
-
};
|
|
641
859
|
var NamedTypeDeclarationAst = class NamedTypeDeclarationAst {
|
|
642
860
|
syntax;
|
|
643
861
|
constructor(syntax) {
|
|
@@ -703,6 +921,498 @@ var GreenNodeBuilder = class {
|
|
|
703
921
|
}
|
|
704
922
|
};
|
|
705
923
|
//#endregion
|
|
706
|
-
|
|
924
|
+
//#region src/parse.ts
|
|
925
|
+
const TRIVIA_KINDS = new Set([
|
|
926
|
+
"Whitespace",
|
|
927
|
+
"Newline",
|
|
928
|
+
"Comment"
|
|
929
|
+
]);
|
|
930
|
+
/**
|
|
931
|
+
* The fault-tolerant parser substrate the grammars drive. Trivia is flushed
|
|
932
|
+
* into the enclosing open node, so every child node spans exactly its first
|
|
933
|
+
* through last significant token.
|
|
934
|
+
*/
|
|
935
|
+
var Cursor = class {
|
|
936
|
+
#tokenizer;
|
|
937
|
+
#sourceFile;
|
|
938
|
+
#builder = new GreenNodeBuilder();
|
|
939
|
+
#diagnostics = [];
|
|
940
|
+
#offset = 0;
|
|
941
|
+
#depth = 0;
|
|
942
|
+
constructor(source) {
|
|
943
|
+
this.#tokenizer = new Tokenizer(source);
|
|
944
|
+
this.#sourceFile = new SourceFile(source);
|
|
945
|
+
}
|
|
946
|
+
get diagnostics() {
|
|
947
|
+
return this.#diagnostics;
|
|
948
|
+
}
|
|
949
|
+
get sourceFile() {
|
|
950
|
+
return this.#sourceFile;
|
|
951
|
+
}
|
|
952
|
+
peekKind(ahead = 0) {
|
|
953
|
+
return this.peekToken(ahead).kind;
|
|
954
|
+
}
|
|
955
|
+
peekToken(ahead = 0) {
|
|
956
|
+
let rawIndex = 0;
|
|
957
|
+
let remaining = ahead;
|
|
958
|
+
for (;;) {
|
|
959
|
+
const token = this.#tokenizer.peek(rawIndex);
|
|
960
|
+
if (token.kind === "Eof") return token;
|
|
961
|
+
if (TRIVIA_KINDS.has(token.kind)) {
|
|
962
|
+
rawIndex++;
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
if (remaining === 0) return token;
|
|
966
|
+
remaining--;
|
|
967
|
+
rawIndex++;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
/** Span of the significant token `lookahead` positions ahead (`mark(0)` = the next). */
|
|
971
|
+
mark(lookahead = 0) {
|
|
972
|
+
let rawIndex = 0;
|
|
973
|
+
let offset = this.#offset;
|
|
974
|
+
let remaining = lookahead;
|
|
975
|
+
for (;;) {
|
|
976
|
+
const token = this.#tokenizer.peek(rawIndex);
|
|
977
|
+
if (token.kind === "Eof") return {
|
|
978
|
+
offset,
|
|
979
|
+
length: token.text.length
|
|
980
|
+
};
|
|
981
|
+
if (!TRIVIA_KINDS.has(token.kind) && remaining === 0) return {
|
|
982
|
+
offset,
|
|
983
|
+
length: token.text.length
|
|
984
|
+
};
|
|
985
|
+
if (!TRIVIA_KINDS.has(token.kind)) remaining--;
|
|
986
|
+
offset += token.text.length;
|
|
987
|
+
rawIndex++;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* Zero-width mark just past the last consumed significant token — anchors an
|
|
992
|
+
* "expected here" diagnostic, e.g. the `{` missing after a declaration's name.
|
|
993
|
+
*/
|
|
994
|
+
markAfterLastToken() {
|
|
995
|
+
return {
|
|
996
|
+
offset: this.#offset,
|
|
997
|
+
length: 0
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
startNode(kind) {
|
|
1001
|
+
if (this.#depth > 0) this.flushTrivia();
|
|
1002
|
+
this.#builder.startNode(kind);
|
|
1003
|
+
this.#depth++;
|
|
1004
|
+
}
|
|
1005
|
+
finishNode() {
|
|
1006
|
+
this.#depth--;
|
|
1007
|
+
return this.#builder.finishNode();
|
|
1008
|
+
}
|
|
1009
|
+
bump() {
|
|
1010
|
+
this.flushTrivia();
|
|
1011
|
+
const token = this.#tokenizer.peek();
|
|
1012
|
+
if (token.kind === "Eof") return token;
|
|
1013
|
+
this.#builder.token(token.kind, token.text);
|
|
1014
|
+
this.#advance();
|
|
1015
|
+
return token;
|
|
1016
|
+
}
|
|
1017
|
+
recoverToSyncPoint() {
|
|
1018
|
+
for (;;) {
|
|
1019
|
+
const token = this.#tokenizer.peek();
|
|
1020
|
+
if (token.kind === "Eof" || token.kind === "Newline" || token.kind === "RBrace") return;
|
|
1021
|
+
this.#builder.token(token.kind, token.text);
|
|
1022
|
+
this.#advance();
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
flushTrivia() {
|
|
1026
|
+
for (;;) {
|
|
1027
|
+
const token = this.#tokenizer.peek();
|
|
1028
|
+
if (!TRIVIA_KINDS.has(token.kind)) return;
|
|
1029
|
+
this.#builder.token(token.kind, token.text);
|
|
1030
|
+
this.#advance();
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
diagnostic(code, message, mark) {
|
|
1034
|
+
const start = mark.offset;
|
|
1035
|
+
const end = start + mark.length;
|
|
1036
|
+
this.#diagnostics.push({
|
|
1037
|
+
code,
|
|
1038
|
+
message,
|
|
1039
|
+
range: {
|
|
1040
|
+
start: this.#sourceFile.positionAt(start),
|
|
1041
|
+
end: this.#sourceFile.positionAt(end)
|
|
1042
|
+
}
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
#advance() {
|
|
1046
|
+
this.#offset += this.#tokenizer.next().text.length;
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1049
|
+
function parseIdentifier(cursor) {
|
|
1050
|
+
cursor.startNode("Identifier");
|
|
1051
|
+
cursor.bump();
|
|
1052
|
+
cursor.finishNode();
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Returns `undefined` when the next significant token does not start a
|
|
1056
|
+
* recognised expression, leaving recovery to the caller.
|
|
1057
|
+
*/
|
|
1058
|
+
function parseExpression(cursor) {
|
|
1059
|
+
return parseStringLiteralExpr(cursor) ?? parseNumberLiteralExpr(cursor) ?? parseArrayLiteral(cursor) ?? parseObjectLiteralExpr(cursor) ?? parseFunctionCall(cursor) ?? parseBooleanLiteralExpr(cursor) ?? parseIdentifierExpr(cursor);
|
|
1060
|
+
}
|
|
1061
|
+
function parseStringLiteralExpr(cursor) {
|
|
1062
|
+
if (cursor.peekKind() !== "StringLiteral") return void 0;
|
|
1063
|
+
const stringMark = cursor.mark();
|
|
1064
|
+
const text = cursor.peekToken().text;
|
|
1065
|
+
cursor.startNode("StringLiteralExpr");
|
|
1066
|
+
cursor.bump();
|
|
1067
|
+
if (!isTerminatedStringLiteral(text)) cursor.diagnostic("PSL_UNTERMINATED_STRING", "Unterminated string literal", stringMark);
|
|
1068
|
+
return cursor.finishNode();
|
|
1069
|
+
}
|
|
1070
|
+
function parseNumberLiteralExpr(cursor) {
|
|
1071
|
+
if (cursor.peekKind() !== "NumberLiteral") return void 0;
|
|
1072
|
+
cursor.startNode("NumberLiteralExpr");
|
|
1073
|
+
cursor.bump();
|
|
1074
|
+
return cursor.finishNode();
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Parses a namespace-qualified name `[space ':']? Ident ('.' Ident)*`. The
|
|
1078
|
+
* caller guarantees a leading `Ident`.
|
|
1079
|
+
*
|
|
1080
|
+
* Parsing the whole chain up front lets a position decide
|
|
1081
|
+
* constructor-vs-reference by peeking exactly one token for `(`, with no scan of
|
|
1082
|
+
* the dotted chain's length.
|
|
1083
|
+
*/
|
|
1084
|
+
function parseQualifiedName(cursor) {
|
|
1085
|
+
cursor.startNode("QualifiedName");
|
|
1086
|
+
parseIdentifier(cursor);
|
|
1087
|
+
parseQualifiedSegments(cursor, "Colon");
|
|
1088
|
+
parseQualifiedSegments(cursor, "Dot");
|
|
1089
|
+
cursor.finishNode();
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* A well-formed name carries at most one colon space and one dot namespace, so
|
|
1093
|
+
* each separator past the first of its kind reports `PSL_INVALID_QUALIFIED_NAME`.
|
|
1094
|
+
* The separator is consumed regardless, keeping the lossless round-trip intact.
|
|
1095
|
+
*/
|
|
1096
|
+
function parseQualifiedSegments(cursor, separator) {
|
|
1097
|
+
let seen = 0;
|
|
1098
|
+
while (cursor.peekKind() === separator) {
|
|
1099
|
+
seen++;
|
|
1100
|
+
const separatorMark = cursor.mark();
|
|
1101
|
+
cursor.bump();
|
|
1102
|
+
if (seen > 1) cursor.diagnostic("PSL_INVALID_QUALIFIED_NAME", "Qualified name has too many segments", separatorMark);
|
|
1103
|
+
if (cursor.peekKind() === "Ident") parseIdentifier(cursor);
|
|
1104
|
+
else cursor.diagnostic("PSL_INVALID_QUALIFIED_NAME", "Qualified name is missing a name after the separator", cursor.mark());
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
function parseBooleanLiteralExpr(cursor) {
|
|
1108
|
+
if (cursor.peekKind() !== "Ident") return void 0;
|
|
1109
|
+
const text = cursor.peekToken().text;
|
|
1110
|
+
if (text !== "true" && text !== "false") return void 0;
|
|
1111
|
+
cursor.startNode("BooleanLiteralExpr");
|
|
1112
|
+
cursor.bump();
|
|
1113
|
+
return cursor.finishNode();
|
|
1114
|
+
}
|
|
1115
|
+
function parseIdentifierExpr(cursor) {
|
|
1116
|
+
if (cursor.peekKind() !== "Ident") return void 0;
|
|
1117
|
+
cursor.startNode("Identifier");
|
|
1118
|
+
cursor.bump();
|
|
1119
|
+
return cursor.finishNode();
|
|
1120
|
+
}
|
|
1121
|
+
function parseArrayLiteral(cursor) {
|
|
1122
|
+
if (cursor.peekKind() !== "LBracket") return void 0;
|
|
1123
|
+
cursor.startNode("ArrayLiteral");
|
|
1124
|
+
cursor.bump();
|
|
1125
|
+
while (cursor.peekKind() !== "RBracket" && cursor.peekKind() !== "Eof") {
|
|
1126
|
+
if (!parseExpression(cursor)) break;
|
|
1127
|
+
if (cursor.peekKind() === "Comma") cursor.bump();
|
|
1128
|
+
else break;
|
|
1129
|
+
}
|
|
1130
|
+
if (cursor.peekKind() === "RBracket") cursor.bump();
|
|
1131
|
+
return cursor.finishNode();
|
|
1132
|
+
}
|
|
1133
|
+
function parseObjectLiteralExpr(cursor) {
|
|
1134
|
+
if (cursor.peekKind() !== "LBrace") return void 0;
|
|
1135
|
+
const braceMark = cursor.mark();
|
|
1136
|
+
cursor.startNode("ObjectLiteralExpr");
|
|
1137
|
+
cursor.bump();
|
|
1138
|
+
while (cursor.peekKind() !== "RBrace" && cursor.peekKind() !== "Eof") {
|
|
1139
|
+
parseObjectField(cursor);
|
|
1140
|
+
if (cursor.peekKind() === "Comma") cursor.bump();
|
|
1141
|
+
else if (cursor.peekKind() === "Ident") cursor.diagnostic("PSL_INVALID_OBJECT_LITERAL", "Expected \",\" between object-literal fields", cursor.markAfterLastToken());
|
|
1142
|
+
else break;
|
|
1143
|
+
}
|
|
1144
|
+
if (cursor.peekKind() === "RBrace") cursor.bump();
|
|
1145
|
+
else cursor.diagnostic("PSL_INVALID_OBJECT_LITERAL", "Unterminated object literal", braceMark);
|
|
1146
|
+
return cursor.finishNode();
|
|
1147
|
+
}
|
|
1148
|
+
function parseObjectField(cursor) {
|
|
1149
|
+
cursor.startNode("ObjectField");
|
|
1150
|
+
const keyMark = cursor.mark();
|
|
1151
|
+
const keyText = cursor.peekToken().text;
|
|
1152
|
+
if (cursor.peekKind() === "Ident") parseIdentifier(cursor);
|
|
1153
|
+
else if (cursor.peekKind() === "StringLiteral") parseStringLiteralExpr(cursor);
|
|
1154
|
+
if (cursor.peekKind() === "Colon") {
|
|
1155
|
+
cursor.bump();
|
|
1156
|
+
if (!parseExpression(cursor)) cursor.diagnostic("PSL_INVALID_OBJECT_LITERAL", "Expected a value after \":\"", cursor.mark());
|
|
1157
|
+
} else {
|
|
1158
|
+
cursor.diagnostic("PSL_INVALID_OBJECT_LITERAL", `Expected ":" after "${keyText}"`, keyMark);
|
|
1159
|
+
if (!(cursor.peekKind() === "Ident" && cursor.peekKind(1) === "Colon")) parseExpression(cursor);
|
|
1160
|
+
}
|
|
1161
|
+
return cursor.finishNode();
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Whether the next tokens open a call: a bare `Ident(` or a namespace-qualified
|
|
1165
|
+
* `Ident.Ident(`. The lookahead is deliberately bounded so a bare dotted
|
|
1166
|
+
* reference like `a.b` is not mistaken for a call, rather than scanning an
|
|
1167
|
+
* unbounded dotted chain ahead to find the paren.
|
|
1168
|
+
*/
|
|
1169
|
+
function isCallAhead(cursor) {
|
|
1170
|
+
if (cursor.peekKind() !== "Ident") return false;
|
|
1171
|
+
if (cursor.peekKind(1) === "LParen") return true;
|
|
1172
|
+
return cursor.peekKind(1) === "Dot" && cursor.peekKind(2) === "Ident" && cursor.peekKind(3) === "LParen";
|
|
1173
|
+
}
|
|
1174
|
+
/**
|
|
1175
|
+
* Parses a function/constructor call — bare `autoincrement()` or qualified
|
|
1176
|
+
* `temporal.updatedAt()`. Returns `undefined` unless {@link isCallAhead}
|
|
1177
|
+
* confirms a trailing `(`, so the `parseExpression` chain falls through to the
|
|
1178
|
+
* boolean and bare-identifier forms.
|
|
1179
|
+
*/
|
|
1180
|
+
function parseFunctionCall(cursor) {
|
|
1181
|
+
if (!isCallAhead(cursor)) return void 0;
|
|
1182
|
+
cursor.startNode("FunctionCall");
|
|
1183
|
+
parseQualifiedName(cursor);
|
|
1184
|
+
if (cursor.peekKind() === "LParen") parseParenArgs(cursor);
|
|
1185
|
+
return cursor.finishNode();
|
|
1186
|
+
}
|
|
1187
|
+
/** Parses a parenthesised, comma-separated `AttributeArg` list into the currently open node. */
|
|
1188
|
+
function parseParenArgs(cursor) {
|
|
1189
|
+
cursor.bump();
|
|
1190
|
+
while (cursor.peekKind() !== "RParen" && cursor.peekKind() !== "Eof") {
|
|
1191
|
+
parseAttributeArg(cursor);
|
|
1192
|
+
if (cursor.peekKind() === "Comma") cursor.bump();
|
|
1193
|
+
else break;
|
|
1194
|
+
}
|
|
1195
|
+
if (cursor.peekKind() === "RParen") cursor.bump();
|
|
1196
|
+
}
|
|
1197
|
+
function parseAttributeArg(cursor) {
|
|
1198
|
+
cursor.startNode("AttributeArg");
|
|
1199
|
+
if (cursor.peekKind() === "Ident" && cursor.peekKind(1) === "Colon") {
|
|
1200
|
+
parseIdentifier(cursor);
|
|
1201
|
+
cursor.bump();
|
|
1202
|
+
}
|
|
1203
|
+
parseArgValue(cursor);
|
|
1204
|
+
return cursor.finishNode();
|
|
1205
|
+
}
|
|
1206
|
+
function parseArgValue(cursor) {
|
|
1207
|
+
parseExpression(cursor);
|
|
1208
|
+
}
|
|
1209
|
+
function parseAttributeArgList(cursor) {
|
|
1210
|
+
cursor.startNode("AttributeArgList");
|
|
1211
|
+
parseParenArgs(cursor);
|
|
1212
|
+
return cursor.finishNode();
|
|
1213
|
+
}
|
|
1214
|
+
function parseAttribute(cursor) {
|
|
1215
|
+
const isBlockAttribute = cursor.peekKind() === "DoubleAt";
|
|
1216
|
+
const attributeMark = cursor.mark();
|
|
1217
|
+
cursor.startNode(isBlockAttribute ? "ModelAttribute" : "FieldAttribute");
|
|
1218
|
+
cursor.bump();
|
|
1219
|
+
if (cursor.peekKind() === "Ident") parseQualifiedName(cursor);
|
|
1220
|
+
else cursor.diagnostic("PSL_INVALID_ATTRIBUTE_SYNTAX", "Attribute name expected", attributeMark);
|
|
1221
|
+
if (cursor.peekKind() === "LParen") parseAttributeArgList(cursor);
|
|
1222
|
+
return cursor.finishNode();
|
|
1223
|
+
}
|
|
1224
|
+
/** A type annotation: `QualifiedName (argList)? ([])? (?)?`, e.g. `pgvector.Vector(1536)[]?`. */
|
|
1225
|
+
function parseTypeAnnotation(cursor) {
|
|
1226
|
+
cursor.startNode("TypeAnnotation");
|
|
1227
|
+
if (cursor.peekKind() === "Ident") {
|
|
1228
|
+
parseQualifiedName(cursor);
|
|
1229
|
+
if (cursor.peekKind() === "LParen") parseAttributeArgList(cursor);
|
|
1230
|
+
}
|
|
1231
|
+
if (cursor.peekKind() === "LBracket") {
|
|
1232
|
+
cursor.bump();
|
|
1233
|
+
if (cursor.peekKind() === "RBracket") cursor.bump();
|
|
1234
|
+
}
|
|
1235
|
+
if (cursor.peekKind() === "Question") cursor.bump();
|
|
1236
|
+
return cursor.finishNode();
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Parses a full PSL document. Never throws — malformed input yields diagnostics
|
|
1240
|
+
* and a recovered tree, not an exception.
|
|
1241
|
+
*/
|
|
1242
|
+
function parse(source) {
|
|
1243
|
+
const cursor = new Cursor(source);
|
|
1244
|
+
const root = createSyntaxTree(parseDocument(cursor));
|
|
1245
|
+
return {
|
|
1246
|
+
document: DocumentAst.cast(root) ?? new DocumentAst(root),
|
|
1247
|
+
diagnostics: cursor.diagnostics,
|
|
1248
|
+
sourceFile: cursor.sourceFile
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
function parseDocument(cursor) {
|
|
1252
|
+
cursor.startNode("Document");
|
|
1253
|
+
while (cursor.peekKind() !== "Eof") parseDeclaration(cursor, false);
|
|
1254
|
+
cursor.flushTrivia();
|
|
1255
|
+
return cursor.finishNode();
|
|
1256
|
+
}
|
|
1257
|
+
const RESERVED_BLOCK_KEYWORDS = new Set([
|
|
1258
|
+
"model",
|
|
1259
|
+
"namespace",
|
|
1260
|
+
"type",
|
|
1261
|
+
"types"
|
|
1262
|
+
]);
|
|
1263
|
+
function keywordIs(cursor, keyword) {
|
|
1264
|
+
return cursor.peekKind() === "Ident" && cursor.peekToken().text === keyword;
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Each alternative is a no-op on non-match, consuming nothing, so the
|
|
1268
|
+
* forward-only cursor is never left half-consumed by a rejected alternative.
|
|
1269
|
+
* Recovery runs via the `if (!node)` tail rather than as a `??` arm, because it
|
|
1270
|
+
* appends raw tokens to the open parent instead of returning a child node.
|
|
1271
|
+
*/
|
|
1272
|
+
function parseDeclaration(cursor, insideNamespace) {
|
|
1273
|
+
const name = cursor.peekKind(1) === "Ident" ? cursor.peekToken(1).text : "";
|
|
1274
|
+
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());
|
|
1275
|
+
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());
|
|
1276
|
+
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));
|
|
1277
|
+
if (!(parseModel(cursor) ?? parseNamespace(cursor) ?? parseCompositeType(cursor) ?? parseTypesBlock(cursor) ?? parseGenericBlock(cursor))) parseUnsupportedTopLevel(cursor);
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Reports only the first missing piece — a missing name suppresses the
|
|
1281
|
+
* missing-brace diagnostic. `nameRequired` is false only for the `types` block.
|
|
1282
|
+
*/
|
|
1283
|
+
function parseBlock(cursor, kind, nameRequired, parseMember) {
|
|
1284
|
+
const keyword = cursor.peekToken().text;
|
|
1285
|
+
const keywordMark = cursor.mark();
|
|
1286
|
+
cursor.startNode(kind);
|
|
1287
|
+
cursor.bump();
|
|
1288
|
+
const hasName = nameRequired && cursor.peekKind() === "Ident";
|
|
1289
|
+
if (hasName) parseIdentifier(cursor);
|
|
1290
|
+
if (nameRequired && !hasName) cursor.diagnostic("PSL_INVALID_DECLARATION", `Expected a name after "${keyword}"`, keywordMark);
|
|
1291
|
+
else if (cursor.peekKind() !== "LBrace") cursor.diagnostic("PSL_INVALID_DECLARATION", `Expected "{" to open the "${keyword}" block`, cursor.markAfterLastToken());
|
|
1292
|
+
if (cursor.peekKind() === "LBrace") parseBlockBody(cursor, parseMember);
|
|
1293
|
+
else cursor.recoverToSyncPoint();
|
|
1294
|
+
return cursor.finishNode();
|
|
1295
|
+
}
|
|
1296
|
+
function parseModel(cursor) {
|
|
1297
|
+
if (!keywordIs(cursor, "model")) return void 0;
|
|
1298
|
+
return parseBlock(cursor, "ModelDeclaration", true, parseModelMember);
|
|
1299
|
+
}
|
|
1300
|
+
/**
|
|
1301
|
+
* Excluding the reserved keywords keeps a malformed reserved block (e.g. `model
|
|
1302
|
+
* {` with no name) routed to its dedicated parser. The generic keyword set is
|
|
1303
|
+
* open, so a bare identifier with no brace (e.g. `oops`) is read as an unfinished
|
|
1304
|
+
* custom declaration rather than unsupported content.
|
|
1305
|
+
*/
|
|
1306
|
+
function parseGenericBlock(cursor) {
|
|
1307
|
+
if (cursor.peekKind() !== "Ident") return void 0;
|
|
1308
|
+
const keyword = cursor.peekToken().text;
|
|
1309
|
+
if (RESERVED_BLOCK_KEYWORDS.has(keyword)) return void 0;
|
|
1310
|
+
const hasName = cursor.peekKind(1) === "Ident" && cursor.peekKind(2) === "LBrace";
|
|
1311
|
+
cursor.startNode("GenericBlockDeclaration");
|
|
1312
|
+
cursor.bump();
|
|
1313
|
+
if (hasName) parseIdentifier(cursor);
|
|
1314
|
+
if (cursor.peekKind() === "LBrace") parseBlockBody(cursor, parseKeyValueMember);
|
|
1315
|
+
else {
|
|
1316
|
+
cursor.diagnostic("PSL_INVALID_DECLARATION", `Expected "{" to open the "${keyword}" block`, cursor.markAfterLastToken());
|
|
1317
|
+
cursor.recoverToSyncPoint();
|
|
1318
|
+
}
|
|
1319
|
+
return cursor.finishNode();
|
|
1320
|
+
}
|
|
1321
|
+
function parseNamespace(cursor) {
|
|
1322
|
+
if (!keywordIs(cursor, "namespace")) return void 0;
|
|
1323
|
+
return parseBlock(cursor, "Namespace", true, (inner) => parseDeclaration(inner, true));
|
|
1324
|
+
}
|
|
1325
|
+
function parseCompositeType(cursor) {
|
|
1326
|
+
if (!keywordIs(cursor, "type")) return void 0;
|
|
1327
|
+
return parseBlock(cursor, "CompositeTypeDeclaration", true, parseModelMember);
|
|
1328
|
+
}
|
|
1329
|
+
/** `types` (plural) is the no-name types block; the singular `type` is the composite type above. */
|
|
1330
|
+
function parseTypesBlock(cursor) {
|
|
1331
|
+
if (!keywordIs(cursor, "types")) return void 0;
|
|
1332
|
+
return parseBlock(cursor, "TypesBlock", false, parseNamedTypeMember);
|
|
1333
|
+
}
|
|
1334
|
+
/** Every `parseMember` consumes at least one significant token, so the loop always terminates. */
|
|
1335
|
+
function parseBlockBody(cursor, parseMember) {
|
|
1336
|
+
const braceMark = cursor.mark();
|
|
1337
|
+
cursor.bump();
|
|
1338
|
+
for (;;) {
|
|
1339
|
+
const kind = cursor.peekKind();
|
|
1340
|
+
if (kind === "RBrace" || kind === "Eof") break;
|
|
1341
|
+
parseMember(cursor);
|
|
1342
|
+
}
|
|
1343
|
+
if (cursor.peekKind() === "RBrace") cursor.bump();
|
|
1344
|
+
else cursor.diagnostic("PSL_UNTERMINATED_BLOCK", "Unterminated block declaration", braceMark);
|
|
1345
|
+
}
|
|
1346
|
+
function parseUnsupportedTopLevel(cursor) {
|
|
1347
|
+
const offending = cursor.peekToken().text;
|
|
1348
|
+
const message = cursor.peekKind(1) === "LBrace" ? `Unsupported top-level block "${offending}"` : `Unsupported top-level declaration "${offending}"`;
|
|
1349
|
+
cursor.diagnostic("PSL_UNSUPPORTED_TOP_LEVEL_BLOCK", message, cursor.mark());
|
|
1350
|
+
cursor.bump();
|
|
1351
|
+
cursor.recoverToSyncPoint();
|
|
1352
|
+
}
|
|
1353
|
+
/**
|
|
1354
|
+
* Matches a leading `@@` block attribute, a no-op otherwise. Single-`@`
|
|
1355
|
+
* attributes belong to fields and are parsed inside `parseField`.
|
|
1356
|
+
*/
|
|
1357
|
+
function parseBlockAttribute(cursor) {
|
|
1358
|
+
if (cursor.peekKind() !== "DoubleAt") return void 0;
|
|
1359
|
+
return parseAttribute(cursor);
|
|
1360
|
+
}
|
|
1361
|
+
function parseModelMember(cursor) {
|
|
1362
|
+
if (!(parseBlockAttribute(cursor) ?? parseField(cursor))) invalidMember(cursor, "PSL_INVALID_MODEL_MEMBER", `Invalid model member declaration "${cursor.peekToken().text}"`);
|
|
1363
|
+
}
|
|
1364
|
+
function parseNamedTypeMember(cursor) {
|
|
1365
|
+
if (!parseNamedType(cursor)) invalidMember(cursor, "PSL_INVALID_TYPES_MEMBER", `Invalid types declaration "${cursor.peekToken().text}"`);
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* A generic-block member is either a `@@`-block attribute or a `key = value`
|
|
1369
|
+
* entry. The block-attribute alternative is purely syntactic — it does not judge
|
|
1370
|
+
* whether the attribute is valid for the block's kind.
|
|
1371
|
+
*/
|
|
1372
|
+
function parseKeyValueMember(cursor) {
|
|
1373
|
+
if (!(parseBlockAttribute(cursor) ?? parseKeyValue(cursor))) invalidMember(cursor, "PSL_INVALID_EXTENSION_BLOCK_MEMBER", "Invalid block entry");
|
|
1374
|
+
}
|
|
1375
|
+
function invalidMember(cursor, code, message) {
|
|
1376
|
+
cursor.diagnostic(code, message, cursor.mark());
|
|
1377
|
+
cursor.bump();
|
|
1378
|
+
cursor.recoverToSyncPoint();
|
|
1379
|
+
}
|
|
1380
|
+
function parseField(cursor) {
|
|
1381
|
+
if (cursor.peekKind() !== "Ident") return void 0;
|
|
1382
|
+
cursor.startNode("FieldDeclaration");
|
|
1383
|
+
parseIdentifier(cursor);
|
|
1384
|
+
parseTypeAnnotation(cursor);
|
|
1385
|
+
while (cursor.peekKind() === "At") parseAttribute(cursor);
|
|
1386
|
+
return cursor.finishNode();
|
|
1387
|
+
}
|
|
1388
|
+
function parseNamedType(cursor) {
|
|
1389
|
+
if (cursor.peekKind() !== "Ident") return void 0;
|
|
1390
|
+
cursor.startNode("NamedTypeDeclaration");
|
|
1391
|
+
const nameMark = cursor.mark();
|
|
1392
|
+
const nameText = cursor.peekToken().text;
|
|
1393
|
+
parseIdentifier(cursor);
|
|
1394
|
+
if (cursor.peekKind() === "Equals") cursor.bump();
|
|
1395
|
+
else cursor.diagnostic("PSL_INVALID_TYPES_MEMBER", `Expected "=" after "${nameText}"`, nameMark);
|
|
1396
|
+
parseTypeAnnotation(cursor);
|
|
1397
|
+
while (cursor.peekKind() === "At") parseAttribute(cursor);
|
|
1398
|
+
return cursor.finishNode();
|
|
1399
|
+
}
|
|
1400
|
+
/**
|
|
1401
|
+
* A generic-block entry is either `key = value` or a bare `key` (committing a
|
|
1402
|
+
* `KeyValuePair` carrying only the key). A `key =` with no following expression
|
|
1403
|
+
* is flagged.
|
|
1404
|
+
*/
|
|
1405
|
+
function parseKeyValue(cursor) {
|
|
1406
|
+
if (cursor.peekKind() !== "Ident") return void 0;
|
|
1407
|
+
cursor.startNode("KeyValuePair");
|
|
1408
|
+
parseIdentifier(cursor);
|
|
1409
|
+
if (cursor.peekKind() === "Equals") {
|
|
1410
|
+
cursor.bump();
|
|
1411
|
+
if (!parseExpression(cursor)) cursor.diagnostic("PSL_INVALID_EXTENSION_BLOCK_MEMBER", "Expected a value after \"=\"", cursor.mark());
|
|
1412
|
+
}
|
|
1413
|
+
return cursor.finishNode();
|
|
1414
|
+
}
|
|
1415
|
+
//#endregion
|
|
1416
|
+
export { ArrayLiteralAst, AttributeArgAst, AttributeArgListAst, BooleanLiteralExprAst, CompositeTypeDeclarationAst, DocumentAst, FieldAttributeAst, FieldDeclarationAst, FunctionCallAst, GenericBlockDeclarationAst, GreenNodeBuilder, IdentifierAst, KeyValuePairAst, ModelAttributeAst, ModelDeclarationAst, NamedTypeDeclarationAst, NamespaceDeclarationAst, NumberLiteralExprAst, ObjectFieldAst, ObjectLiteralExprAst, QualifiedNameAst, SourceFile, StringLiteralExprAst, SyntaxNode, TypeAnnotationAst, TypesBlockAst, castExpression, createSyntaxTree, filterChildren, findChildToken, findFirstChild, greenNode, greenToken, parse, printSyntax };
|
|
707
1417
|
|
|
708
1418
|
//# sourceMappingURL=syntax.mjs.map
|