@prisma-next/psl-parser 0.13.0 → 0.14.0-dev.10
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/README.md +28 -10
- package/dist/declarations-D9h_ihD3.mjs +820 -0
- package/dist/declarations-D9h_ihD3.mjs.map +1 -0
- package/dist/format.d.mts +19 -0
- package/dist/format.d.mts.map +1 -0
- package/dist/format.mjs +470 -0
- package/dist/format.mjs.map +1 -0
- package/dist/index.d.mts +136 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +472 -2
- package/dist/index.mjs.map +1 -1
- package/dist/parse-BjZ1LPe6.d.mts +349 -0
- package/dist/parse-BjZ1LPe6.d.mts.map +1 -0
- package/dist/parse-DhEV6av6.mjs +605 -0
- package/dist/parse-DhEV6av6.mjs.map +1 -0
- package/dist/syntax.d.mts +3 -272
- package/dist/syntax.d.mts.map +1 -1
- package/dist/syntax.mjs +3 -708
- package/dist/tokenizer-1hAHZzmp.mjs +228 -0
- package/dist/tokenizer-1hAHZzmp.mjs.map +1 -0
- package/dist/tokenizer.mjs +1 -190
- package/package.json +6 -6
- package/src/block-reconstruction.ts +139 -0
- package/src/exports/format.ts +3 -0
- package/src/exports/index.ts +40 -5
- package/src/exports/syntax.ts +9 -4
- package/src/extension-block.ts +107 -0
- package/src/format/emit.ts +603 -0
- package/src/format/error.ts +13 -0
- package/src/format/format.ts +13 -0
- package/src/format/options.ts +28 -0
- package/src/parse.ts +751 -0
- package/src/resolve.ts +120 -0
- package/src/source-file.ts +89 -0
- package/src/symbol-table.ts +446 -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 +0 -1176
- package/dist/parser-Cw_zV0M5.mjs.map +0 -1
- package/dist/parser-Dfi3Wfdq.d.mts +0 -7
- package/dist/parser-Dfi3Wfdq.d.mts.map +0 -1
- package/dist/parser.d.mts +0 -2
- package/dist/parser.mjs +0 -2
- package/dist/syntax.mjs.map +0 -1
- package/dist/tokenizer.mjs.map +0 -1
- package/src/exports/parser.ts +0 -1
- package/src/parser.ts +0 -1713
package/dist/parser-Cw_zV0M5.mjs
DELETED
|
@@ -1,1176 +0,0 @@
|
|
|
1
|
-
import { isAuthoringPslBlockDescriptor } from "@prisma-next/framework-components/authoring";
|
|
2
|
-
import { emptyCodecLookup } from "@prisma-next/framework-components/codec";
|
|
3
|
-
import { UNSPECIFIED_PSL_NAMESPACE_ID, makePslNamespace, makePslNamespaceEntries, namespacePslExtensionBlocks, validateExtensionBlock } from "@prisma-next/framework-components/psl-ast";
|
|
4
|
-
import { ifDefined } from "@prisma-next/utils/defined";
|
|
5
|
-
//#region src/parser.ts
|
|
6
|
-
const SCALAR_TYPES = new Set([
|
|
7
|
-
"String",
|
|
8
|
-
"Boolean",
|
|
9
|
-
"Int",
|
|
10
|
-
"BigInt",
|
|
11
|
-
"Float",
|
|
12
|
-
"Decimal",
|
|
13
|
-
"DateTime",
|
|
14
|
-
"Json",
|
|
15
|
-
"Bytes"
|
|
16
|
-
]);
|
|
17
|
-
function parsePslDocument(input) {
|
|
18
|
-
const normalizedSchema = input.schema.replaceAll("\r\n", "\n");
|
|
19
|
-
const lines = normalizedSchema.split("\n");
|
|
20
|
-
const lineOffsets = computeLineOffsets(normalizedSchema);
|
|
21
|
-
const diagnostics = [];
|
|
22
|
-
const context = {
|
|
23
|
-
schema: normalizedSchema,
|
|
24
|
-
sourceId: input.sourceId,
|
|
25
|
-
lines,
|
|
26
|
-
lineOffsets,
|
|
27
|
-
diagnostics
|
|
28
|
-
};
|
|
29
|
-
const namespaceOrder = [];
|
|
30
|
-
const namespacesByName = /* @__PURE__ */ new Map();
|
|
31
|
-
const getOrCreateNamespace = (name, spanIfNew) => {
|
|
32
|
-
let acc = namespacesByName.get(name);
|
|
33
|
-
if (!acc) {
|
|
34
|
-
acc = {
|
|
35
|
-
name,
|
|
36
|
-
models: [],
|
|
37
|
-
enums: [],
|
|
38
|
-
compositeTypes: [],
|
|
39
|
-
extensionBlocks: [],
|
|
40
|
-
span: spanIfNew
|
|
41
|
-
};
|
|
42
|
-
namespacesByName.set(name, acc);
|
|
43
|
-
namespaceOrder.push(name);
|
|
44
|
-
}
|
|
45
|
-
return acc;
|
|
46
|
-
};
|
|
47
|
-
let typesBlock;
|
|
48
|
-
const pslBlockNamespace = input.pslBlockDescriptors ?? {};
|
|
49
|
-
const parseBody = (startLine, endLineExclusive, currentNamespaceName, isInsideNamespace) => {
|
|
50
|
-
let lineIndex = startLine;
|
|
51
|
-
while (lineIndex < endLineExclusive) {
|
|
52
|
-
const line = stripInlineComment(context.lines[lineIndex] ?? "").trim();
|
|
53
|
-
if (line.length === 0) {
|
|
54
|
-
lineIndex += 1;
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
const modelMatch = line.match(/^model\s+([A-Za-z_]\w*)\s*\{$/);
|
|
58
|
-
if (modelMatch) {
|
|
59
|
-
const bounds = findBlockBounds(context, lineIndex);
|
|
60
|
-
const name = modelMatch[1] ?? "";
|
|
61
|
-
if (name.length > 0) getOrCreateNamespace(currentNamespaceName, createTrimmedLineSpan(context, lineIndex)).models.push(parseModelBlock(context, name, bounds));
|
|
62
|
-
lineIndex = bounds.endLine + 1;
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
const enumMatch = line.match(/^enum\s+([A-Za-z_]\w*)\s*\{$/);
|
|
66
|
-
if (enumMatch) {
|
|
67
|
-
const bounds = findBlockBounds(context, lineIndex);
|
|
68
|
-
const name = enumMatch[1] ?? "";
|
|
69
|
-
if (name.length > 0) getOrCreateNamespace(currentNamespaceName, createTrimmedLineSpan(context, lineIndex)).enums.push(parseEnumBlock(context, name, bounds));
|
|
70
|
-
lineIndex = bounds.endLine + 1;
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
const compositeTypeMatch = line.match(/^type\s+([A-Za-z_]\w*)\s*\{$/);
|
|
74
|
-
if (compositeTypeMatch) {
|
|
75
|
-
const bounds = findBlockBounds(context, lineIndex);
|
|
76
|
-
const name = compositeTypeMatch[1] ?? "";
|
|
77
|
-
if (name.length > 0) getOrCreateNamespace(currentNamespaceName, createTrimmedLineSpan(context, lineIndex)).compositeTypes.push(parseCompositeTypeBlock(context, name, bounds));
|
|
78
|
-
lineIndex = bounds.endLine + 1;
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
const namespaceMatch = line.match(/^namespace\s+([A-Za-z_]\w*)\s*\{$/);
|
|
82
|
-
if (namespaceMatch) {
|
|
83
|
-
const bounds = findBlockBounds(context, lineIndex);
|
|
84
|
-
const name = namespaceMatch[1] ?? "";
|
|
85
|
-
if (isInsideNamespace) pushDiagnostic(context, {
|
|
86
|
-
code: "PSL_INVALID_NAMESPACE_BLOCK",
|
|
87
|
-
message: `Recursive "namespace ${name}" block is not allowed; namespace blocks may not nest`,
|
|
88
|
-
span: createTrimmedLineSpan(context, lineIndex)
|
|
89
|
-
});
|
|
90
|
-
else if (name === UNSPECIFIED_PSL_NAMESPACE_ID) pushDiagnostic(context, {
|
|
91
|
-
code: "PSL_INVALID_NAMESPACE_BLOCK",
|
|
92
|
-
message: `Namespace name "${UNSPECIFIED_PSL_NAMESPACE_ID}" is reserved for the parser-synthesised bucket for top-level declarations`,
|
|
93
|
-
span: createTrimmedLineSpan(context, lineIndex)
|
|
94
|
-
});
|
|
95
|
-
else if (name.length > 0) {
|
|
96
|
-
getOrCreateNamespace(name, createTrimmedLineSpan(context, lineIndex));
|
|
97
|
-
parseBody(bounds.startLine + 1, bounds.endLine, name, true);
|
|
98
|
-
}
|
|
99
|
-
lineIndex = bounds.endLine + 1;
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
if (/^types\s*\{$/.test(line)) {
|
|
103
|
-
const bounds = findBlockBounds(context, lineIndex);
|
|
104
|
-
if (isInsideNamespace) pushDiagnostic(context, {
|
|
105
|
-
code: "PSL_INVALID_NAMESPACE_BLOCK",
|
|
106
|
-
message: "`types` blocks must be declared at the document top level, not inside a namespace block",
|
|
107
|
-
span: createTrimmedLineSpan(context, lineIndex)
|
|
108
|
-
});
|
|
109
|
-
else if (typesBlock !== void 0) pushDiagnostic(context, {
|
|
110
|
-
code: "PSL_INVALID_TYPES_MEMBER",
|
|
111
|
-
message: "Only one top-level `types` block is allowed per document",
|
|
112
|
-
span: createTrimmedLineSpan(context, lineIndex)
|
|
113
|
-
});
|
|
114
|
-
else typesBlock = parseTypesBlock(context, bounds);
|
|
115
|
-
lineIndex = bounds.endLine + 1;
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
const extensionBlockMatch = line.match(/^([A-Za-z_]\w*)\s+([A-Za-z_]\w*)\s*\{$/);
|
|
119
|
-
if (extensionBlockMatch) {
|
|
120
|
-
const keyword = extensionBlockMatch[1] ?? "";
|
|
121
|
-
const blockName = extensionBlockMatch[2] ?? "";
|
|
122
|
-
const descriptor = lookupExtensionBlockDescriptor(pslBlockNamespace, keyword);
|
|
123
|
-
if (descriptor) {
|
|
124
|
-
const bounds = findBlockBounds(context, lineIndex);
|
|
125
|
-
if (blockName.length > 0) {
|
|
126
|
-
const acc = getOrCreateNamespace(currentNamespaceName, createTrimmedLineSpan(context, lineIndex));
|
|
127
|
-
const node = parseExtensionBlock(context, descriptor, blockName, lineIndex, bounds);
|
|
128
|
-
acc.extensionBlocks.push(node);
|
|
129
|
-
}
|
|
130
|
-
lineIndex = bounds.endLine + 1;
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
if (line.includes("{")) {
|
|
135
|
-
pushDiagnostic(context, {
|
|
136
|
-
code: "PSL_UNSUPPORTED_TOP_LEVEL_BLOCK",
|
|
137
|
-
message: `Unsupported top-level block "${line.split(/\s+/)[0] ?? "block"}"`,
|
|
138
|
-
span: createTrimmedLineSpan(context, lineIndex)
|
|
139
|
-
});
|
|
140
|
-
lineIndex = findBlockBounds(context, lineIndex).endLine + 1;
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
pushDiagnostic(context, {
|
|
144
|
-
code: "PSL_UNSUPPORTED_TOP_LEVEL_BLOCK",
|
|
145
|
-
message: `Unsupported top-level declaration "${line}"`,
|
|
146
|
-
span: createTrimmedLineSpan(context, lineIndex)
|
|
147
|
-
});
|
|
148
|
-
lineIndex += 1;
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
parseBody(0, lines.length, UNSPECIFIED_PSL_NAMESPACE_ID, false);
|
|
152
|
-
const allModels = [];
|
|
153
|
-
const allEnums = [];
|
|
154
|
-
const allCompositeTypes = [];
|
|
155
|
-
for (const name of namespaceOrder) {
|
|
156
|
-
const acc = namespacesByName.get(name);
|
|
157
|
-
if (!acc) continue;
|
|
158
|
-
allModels.push(...acc.models);
|
|
159
|
-
allEnums.push(...acc.enums);
|
|
160
|
-
allCompositeTypes.push(...acc.compositeTypes);
|
|
161
|
-
}
|
|
162
|
-
const namedTypeNames = new Set((typesBlock?.declarations ?? []).map((declaration) => declaration.name));
|
|
163
|
-
const modelNames = new Set(allModels.map((model) => model.name));
|
|
164
|
-
const enumNames = new Set(allEnums.map((enumBlock) => enumBlock.name));
|
|
165
|
-
const compositeTypeNames = new Set(allCompositeTypes.map((ct) => ct.name));
|
|
166
|
-
for (const declaration of typesBlock?.declarations ?? []) {
|
|
167
|
-
if (SCALAR_TYPES.has(declaration.name)) {
|
|
168
|
-
pushDiagnostic(context, {
|
|
169
|
-
code: "PSL_INVALID_TYPES_MEMBER",
|
|
170
|
-
message: `Named type "${declaration.name}" conflicts with scalar type "${declaration.name}"`,
|
|
171
|
-
span: declaration.span
|
|
172
|
-
});
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
if (modelNames.has(declaration.name)) {
|
|
176
|
-
pushDiagnostic(context, {
|
|
177
|
-
code: "PSL_INVALID_TYPES_MEMBER",
|
|
178
|
-
message: `Named type "${declaration.name}" conflicts with model name "${declaration.name}"`,
|
|
179
|
-
span: declaration.span
|
|
180
|
-
});
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
if (enumNames.has(declaration.name)) pushDiagnostic(context, {
|
|
184
|
-
code: "PSL_INVALID_TYPES_MEMBER",
|
|
185
|
-
message: `Named type "${declaration.name}" conflicts with enum name "${declaration.name}"`,
|
|
186
|
-
span: declaration.span
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
const documentSpan = {
|
|
190
|
-
start: createPosition(context, 0, 0),
|
|
191
|
-
end: createPosition(context, Math.max(lines.length - 1, 0), (lines[Math.max(lines.length - 1, 0)] ?? "").length)
|
|
192
|
-
};
|
|
193
|
-
const namespaces = [];
|
|
194
|
-
for (const name of namespaceOrder) {
|
|
195
|
-
const acc = namespacesByName.get(name);
|
|
196
|
-
if (!acc) continue;
|
|
197
|
-
if (name === UNSPECIFIED_PSL_NAMESPACE_ID && acc.models.length === 0 && acc.enums.length === 0 && acc.compositeTypes.length === 0 && acc.extensionBlocks.length === 0) continue;
|
|
198
|
-
const normalizedModels = acc.models.map((model) => ({
|
|
199
|
-
...model,
|
|
200
|
-
fields: model.fields.map((field) => {
|
|
201
|
-
if (!namedTypeNames.has(field.typeName)) return field;
|
|
202
|
-
if (field.attributes.some((attribute) => attribute.name === "relation") || modelNames.has(field.typeName) || enumNames.has(field.typeName) || compositeTypeNames.has(field.typeName) || SCALAR_TYPES.has(field.typeName)) return field;
|
|
203
|
-
return {
|
|
204
|
-
...field,
|
|
205
|
-
typeRef: field.typeName
|
|
206
|
-
};
|
|
207
|
-
})
|
|
208
|
-
}));
|
|
209
|
-
namespaces.push(makePslNamespace({
|
|
210
|
-
kind: "namespace",
|
|
211
|
-
name,
|
|
212
|
-
entries: makePslNamespaceEntries(normalizedModels, acc.enums, acc.compositeTypes, acc.extensionBlocks),
|
|
213
|
-
span: acc.span ?? documentSpan
|
|
214
|
-
}));
|
|
215
|
-
}
|
|
216
|
-
if (Object.keys(pslBlockNamespace).length > 0) {
|
|
217
|
-
const codecLookup = input.codecLookup ?? emptyCodecLookup;
|
|
218
|
-
const descriptorByDiscriminator = /* @__PURE__ */ new Map();
|
|
219
|
-
for (const value of Object.values(pslBlockNamespace)) if (isAuthoringPslBlockDescriptor(value)) descriptorByDiscriminator.set(value.discriminator, value);
|
|
220
|
-
for (const ns of namespaces) for (const block of namespacePslExtensionBlocks(ns)) {
|
|
221
|
-
const descriptor = descriptorByDiscriminator.get(block.kind);
|
|
222
|
-
if (descriptor === void 0) continue;
|
|
223
|
-
const blockDiagnostics = validateExtensionBlock(block, descriptor, input.sourceId, codecLookup, {
|
|
224
|
-
ownerNamespace: ns,
|
|
225
|
-
allNamespaces: namespaces
|
|
226
|
-
});
|
|
227
|
-
diagnostics.push(...blockDiagnostics);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
return {
|
|
231
|
-
ast: {
|
|
232
|
-
kind: "document",
|
|
233
|
-
sourceId: input.sourceId,
|
|
234
|
-
namespaces,
|
|
235
|
-
...ifDefined("types", typesBlock),
|
|
236
|
-
span: documentSpan
|
|
237
|
-
},
|
|
238
|
-
diagnostics,
|
|
239
|
-
ok: diagnostics.length === 0
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
function parseModelBlock(context, name, bounds) {
|
|
243
|
-
const fields = [];
|
|
244
|
-
const attributes = [];
|
|
245
|
-
for (let lineIndex = bounds.startLine + 1; lineIndex < bounds.endLine; lineIndex += 1) {
|
|
246
|
-
const line = stripInlineComment(context.lines[lineIndex] ?? "").trim();
|
|
247
|
-
if (line.length === 0) continue;
|
|
248
|
-
if (line.startsWith("@@")) {
|
|
249
|
-
const attribute = parseModelAttribute(context, line, lineIndex);
|
|
250
|
-
if (attribute) attributes.push(attribute);
|
|
251
|
-
continue;
|
|
252
|
-
}
|
|
253
|
-
const field = parseField(context, line, lineIndex);
|
|
254
|
-
if (field) fields.push(field);
|
|
255
|
-
}
|
|
256
|
-
return {
|
|
257
|
-
kind: "model",
|
|
258
|
-
name,
|
|
259
|
-
fields,
|
|
260
|
-
attributes,
|
|
261
|
-
span: createLineRangeSpan(context, bounds.startLine, bounds.endLine)
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
function parseCompositeTypeBlock(context, name, bounds) {
|
|
265
|
-
const fields = [];
|
|
266
|
-
const attributes = [];
|
|
267
|
-
for (let lineIndex = bounds.startLine + 1; lineIndex < bounds.endLine; lineIndex += 1) {
|
|
268
|
-
const line = stripInlineComment(context.lines[lineIndex] ?? "").trim();
|
|
269
|
-
if (line.length === 0) continue;
|
|
270
|
-
if (line.startsWith("@@")) {
|
|
271
|
-
const attribute = parseModelAttribute(context, line, lineIndex);
|
|
272
|
-
if (attribute) attributes.push(attribute);
|
|
273
|
-
continue;
|
|
274
|
-
}
|
|
275
|
-
const field = parseField(context, line, lineIndex);
|
|
276
|
-
if (field) fields.push(field);
|
|
277
|
-
}
|
|
278
|
-
return {
|
|
279
|
-
kind: "compositeType",
|
|
280
|
-
name,
|
|
281
|
-
fields,
|
|
282
|
-
attributes,
|
|
283
|
-
span: createLineRangeSpan(context, bounds.startLine, bounds.endLine)
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
function parseEnumBlock(context, name, bounds) {
|
|
287
|
-
const values = [];
|
|
288
|
-
const attributes = [];
|
|
289
|
-
for (let lineIndex = bounds.startLine + 1; lineIndex < bounds.endLine; lineIndex += 1) {
|
|
290
|
-
const line = stripInlineComment(context.lines[lineIndex] ?? "").trim();
|
|
291
|
-
if (line.length === 0) continue;
|
|
292
|
-
if (line.startsWith("@@")) {
|
|
293
|
-
const attribute = parseEnumAttribute(context, line, lineIndex);
|
|
294
|
-
if (attribute) attributes.push(attribute);
|
|
295
|
-
continue;
|
|
296
|
-
}
|
|
297
|
-
const valueMatch = line.match(/^([A-Za-z_]\w*)(?:\s+@map\(\s*"((?:[^"\\]|\\.)*)"\s*\))?$/);
|
|
298
|
-
if (!valueMatch) {
|
|
299
|
-
pushDiagnostic(context, {
|
|
300
|
-
code: "PSL_INVALID_ENUM_MEMBER",
|
|
301
|
-
message: `Invalid enum value declaration "${line}"`,
|
|
302
|
-
span: createTrimmedLineSpan(context, lineIndex)
|
|
303
|
-
});
|
|
304
|
-
continue;
|
|
305
|
-
}
|
|
306
|
-
const mapName = valueMatch[2] !== void 0 ? unescapePslString(valueMatch[2]) : void 0;
|
|
307
|
-
values.push({
|
|
308
|
-
kind: "enumValue",
|
|
309
|
-
name: valueMatch[1] ?? "",
|
|
310
|
-
...mapName !== void 0 ? { mapName } : {},
|
|
311
|
-
span: createTrimmedLineSpan(context, lineIndex)
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
return {
|
|
315
|
-
kind: "enum",
|
|
316
|
-
name,
|
|
317
|
-
values,
|
|
318
|
-
attributes,
|
|
319
|
-
span: createLineRangeSpan(context, bounds.startLine, bounds.endLine)
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* Decode PSL escape sequences (`\\`, `\"`, `\'`, `\n`, `\r`) inside a
|
|
324
|
-
* quoted-literal body. The argument is the body of the literal with the
|
|
325
|
-
* surrounding quotes already stripped by the caller. Mirrors the inverse
|
|
326
|
-
* helper in `@prisma-next/psl-printer`'s `escapePslString` so a string
|
|
327
|
-
* round-trips parser → printer → parser unchanged.
|
|
328
|
-
*/
|
|
329
|
-
function unescapePslString(value) {
|
|
330
|
-
let result = "";
|
|
331
|
-
for (let i = 0; i < value.length; i++) {
|
|
332
|
-
if (value.charCodeAt(i) !== 92 || i + 1 >= value.length) {
|
|
333
|
-
result += value[i];
|
|
334
|
-
continue;
|
|
335
|
-
}
|
|
336
|
-
const next = value[i + 1];
|
|
337
|
-
if (next === "\\" || next === "\"" || next === "'") result += next;
|
|
338
|
-
else if (next === "n") result += "\n";
|
|
339
|
-
else if (next === "r") result += "\r";
|
|
340
|
-
else {
|
|
341
|
-
result += "\\";
|
|
342
|
-
result += next;
|
|
343
|
-
}
|
|
344
|
-
i++;
|
|
345
|
-
}
|
|
346
|
-
return result;
|
|
347
|
-
}
|
|
348
|
-
function parseTypesBlock(context, bounds) {
|
|
349
|
-
const declarations = [];
|
|
350
|
-
for (let lineIndex = bounds.startLine + 1; lineIndex < bounds.endLine; lineIndex += 1) {
|
|
351
|
-
const raw = context.lines[lineIndex] ?? "";
|
|
352
|
-
const line = stripInlineComment(raw).trim();
|
|
353
|
-
if (line.length === 0) continue;
|
|
354
|
-
const declarationMatch = line.match(/^([A-Za-z_]\w*)\s*=\s*(.+)$/);
|
|
355
|
-
if (!declarationMatch) {
|
|
356
|
-
pushDiagnostic(context, {
|
|
357
|
-
code: "PSL_INVALID_TYPES_MEMBER",
|
|
358
|
-
message: `Invalid types declaration "${line}"`,
|
|
359
|
-
span: createTrimmedLineSpan(context, lineIndex)
|
|
360
|
-
});
|
|
361
|
-
continue;
|
|
362
|
-
}
|
|
363
|
-
const declarationName = declarationMatch[1] ?? "";
|
|
364
|
-
const trimmedStartColumn = firstNonWhitespaceColumn(raw);
|
|
365
|
-
const declarationValue = (declarationMatch[2] ?? "").trim();
|
|
366
|
-
const valueOffset = line.indexOf(declarationValue);
|
|
367
|
-
const declarationValueColumn = trimmedStartColumn + Math.max(valueOffset, 0);
|
|
368
|
-
const typeAndAttributeSplit = splitTypeAndAttributes(declarationValue);
|
|
369
|
-
const typeSource = typeAndAttributeSplit.typeSource.trim();
|
|
370
|
-
const attributeSource = typeAndAttributeSplit.attributeSource.trimStart();
|
|
371
|
-
const leadingAttributeWhitespace = typeAndAttributeSplit.attributeSource.length - attributeSource.length;
|
|
372
|
-
const typeConstructor = parseTypeConstructorCall(context, {
|
|
373
|
-
declarationValue: typeSource,
|
|
374
|
-
lineIndex,
|
|
375
|
-
startColumn: declarationValueColumn,
|
|
376
|
-
invalidCode: "PSL_INVALID_TYPES_MEMBER",
|
|
377
|
-
invalidMessage: (value) => `Invalid types declaration "${value}"`
|
|
378
|
-
});
|
|
379
|
-
if (typeConstructor === "malformed") continue;
|
|
380
|
-
const attributeParse = extractAttributeTokensWithSpans(context, lineIndex, attributeSource, declarationValueColumn + typeAndAttributeSplit.attributeOffset + leadingAttributeWhitespace);
|
|
381
|
-
if (!attributeParse.ok) continue;
|
|
382
|
-
const attributes = attributeParse.tokens.map((token) => parseAttributeToken(context, {
|
|
383
|
-
token: token.text,
|
|
384
|
-
target: "namedType",
|
|
385
|
-
lineIndex,
|
|
386
|
-
span: token.span
|
|
387
|
-
})).filter((attribute) => Boolean(attribute));
|
|
388
|
-
if (typeConstructor) {
|
|
389
|
-
declarations.push({
|
|
390
|
-
kind: "namedType",
|
|
391
|
-
name: declarationName,
|
|
392
|
-
typeConstructor,
|
|
393
|
-
attributes,
|
|
394
|
-
span: createTrimmedLineSpan(context, lineIndex)
|
|
395
|
-
});
|
|
396
|
-
continue;
|
|
397
|
-
}
|
|
398
|
-
const baseTypeMatch = typeSource.match(/^([A-Za-z_]\w*)$/);
|
|
399
|
-
if (!baseTypeMatch) {
|
|
400
|
-
pushDiagnostic(context, {
|
|
401
|
-
code: "PSL_INVALID_TYPES_MEMBER",
|
|
402
|
-
message: `Invalid types declaration "${line}"`,
|
|
403
|
-
span: createTrimmedLineSpan(context, lineIndex)
|
|
404
|
-
});
|
|
405
|
-
continue;
|
|
406
|
-
}
|
|
407
|
-
const baseType = baseTypeMatch[1] ?? "";
|
|
408
|
-
declarations.push({
|
|
409
|
-
kind: "namedType",
|
|
410
|
-
name: declarationName,
|
|
411
|
-
baseType,
|
|
412
|
-
attributes,
|
|
413
|
-
span: createTrimmedLineSpan(context, lineIndex)
|
|
414
|
-
});
|
|
415
|
-
}
|
|
416
|
-
return {
|
|
417
|
-
kind: "types",
|
|
418
|
-
declarations,
|
|
419
|
-
span: createLineRangeSpan(context, bounds.startLine, bounds.endLine)
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
function parseTypeConstructorCall(context, input) {
|
|
423
|
-
const value = input.declarationValue.trim();
|
|
424
|
-
const constructorMatch = value.match(/^([A-Za-z_][A-Za-z0-9_-]*(?:\.[A-Za-z_][A-Za-z0-9_-]*)*)\s*\(/);
|
|
425
|
-
if (!constructorMatch) return;
|
|
426
|
-
const openParen = value.indexOf("(");
|
|
427
|
-
const closeParen = value.lastIndexOf(")");
|
|
428
|
-
if (closeParen !== value.length - 1) {
|
|
429
|
-
pushDiagnostic(context, {
|
|
430
|
-
code: input.invalidCode,
|
|
431
|
-
message: input.invalidMessage(value),
|
|
432
|
-
span: createInlineSpan(context, input.lineIndex, input.startColumn, input.startColumn + value.length)
|
|
433
|
-
});
|
|
434
|
-
return "malformed";
|
|
435
|
-
}
|
|
436
|
-
const constructorPath = constructorMatch[1] ?? "";
|
|
437
|
-
const args = parseArgumentList(context, {
|
|
438
|
-
argsRaw: value.slice(openParen + 1, closeParen),
|
|
439
|
-
argsOffset: input.startColumn + openParen + 1,
|
|
440
|
-
lineIndex: input.lineIndex,
|
|
441
|
-
token: value,
|
|
442
|
-
span: createInlineSpan(context, input.lineIndex, input.startColumn, input.startColumn + value.length),
|
|
443
|
-
invalidCode: input.invalidCode,
|
|
444
|
-
invalidEmptyArgumentMessage: `Invalid empty argument in type constructor "${value}"`,
|
|
445
|
-
invalidNamedArgumentMessage: (part) => `Invalid named argument syntax "${part}" in type constructor "${value}"`
|
|
446
|
-
});
|
|
447
|
-
if (!args) return "malformed";
|
|
448
|
-
return {
|
|
449
|
-
kind: "typeConstructor",
|
|
450
|
-
path: constructorPath.split("."),
|
|
451
|
-
args,
|
|
452
|
-
span: createInlineSpan(context, input.lineIndex, input.startColumn, input.startColumn + value.length)
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
function parseModelAttribute(context, line, lineIndex) {
|
|
456
|
-
const tokenParse = extractAttributeTokensWithSpans(context, lineIndex, line, firstNonWhitespaceColumn(context.lines[lineIndex] ?? ""));
|
|
457
|
-
if (!tokenParse.ok || tokenParse.tokens.length !== 1) {
|
|
458
|
-
pushDiagnostic(context, {
|
|
459
|
-
code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
|
|
460
|
-
message: `Invalid model attribute syntax "${line}"`,
|
|
461
|
-
span: createTrimmedLineSpan(context, lineIndex)
|
|
462
|
-
});
|
|
463
|
-
return;
|
|
464
|
-
}
|
|
465
|
-
const token = tokenParse.tokens[0];
|
|
466
|
-
if (!token) return;
|
|
467
|
-
return parseAttributeToken(context, {
|
|
468
|
-
token: token.text,
|
|
469
|
-
target: "model",
|
|
470
|
-
lineIndex,
|
|
471
|
-
span: token.span
|
|
472
|
-
});
|
|
473
|
-
}
|
|
474
|
-
function parseEnumAttribute(context, line, lineIndex) {
|
|
475
|
-
const tokenParse = extractAttributeTokensWithSpans(context, lineIndex, line, firstNonWhitespaceColumn(context.lines[lineIndex] ?? ""));
|
|
476
|
-
if (!tokenParse.ok || tokenParse.tokens.length !== 1) {
|
|
477
|
-
pushDiagnostic(context, {
|
|
478
|
-
code: "PSL_INVALID_ENUM_MEMBER",
|
|
479
|
-
message: `Invalid enum value declaration "${line}"`,
|
|
480
|
-
span: createTrimmedLineSpan(context, lineIndex)
|
|
481
|
-
});
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
484
|
-
const token = tokenParse.tokens[0];
|
|
485
|
-
if (!token) return;
|
|
486
|
-
const parsed = parseAttributeToken(context, {
|
|
487
|
-
token: token.text,
|
|
488
|
-
target: "enum",
|
|
489
|
-
lineIndex,
|
|
490
|
-
span: token.span
|
|
491
|
-
});
|
|
492
|
-
if (!parsed) return;
|
|
493
|
-
if (parsed.name !== "map") {
|
|
494
|
-
pushDiagnostic(context, {
|
|
495
|
-
code: "PSL_INVALID_ENUM_MEMBER",
|
|
496
|
-
message: `Invalid enum value declaration "${line}"`,
|
|
497
|
-
span: createTrimmedLineSpan(context, lineIndex)
|
|
498
|
-
});
|
|
499
|
-
return;
|
|
500
|
-
}
|
|
501
|
-
return parsed;
|
|
502
|
-
}
|
|
503
|
-
function parseField(context, line, lineIndex) {
|
|
504
|
-
const fieldMatch = line.match(/^([A-Za-z_]\w*)(\s+)(.+)$/);
|
|
505
|
-
if (!fieldMatch) {
|
|
506
|
-
pushDiagnostic(context, {
|
|
507
|
-
code: "PSL_INVALID_MODEL_MEMBER",
|
|
508
|
-
message: `Invalid model member declaration "${line}"`,
|
|
509
|
-
span: createTrimmedLineSpan(context, lineIndex)
|
|
510
|
-
});
|
|
511
|
-
return;
|
|
512
|
-
}
|
|
513
|
-
const fieldName = fieldMatch[1] ?? "";
|
|
514
|
-
const separator = fieldMatch[2] ?? "";
|
|
515
|
-
const typeAndAttributeSplit = splitTypeAndAttributes(fieldMatch[3] ?? "");
|
|
516
|
-
const rawTypeSource = typeAndAttributeSplit.typeSource.trim();
|
|
517
|
-
const attributePart = typeAndAttributeSplit.attributeSource;
|
|
518
|
-
const optional = rawTypeSource.endsWith("?");
|
|
519
|
-
const typeSourceWithoutOptional = optional ? rawTypeSource.slice(0, -1).trimEnd() : rawTypeSource;
|
|
520
|
-
const list = typeSourceWithoutOptional.endsWith("[]");
|
|
521
|
-
const baseTypeSource = list ? typeSourceWithoutOptional.slice(0, -2).trimEnd() : typeSourceWithoutOptional;
|
|
522
|
-
const trimmedStartColumn = firstNonWhitespaceColumn(context.lines[lineIndex] ?? "");
|
|
523
|
-
const typeStartColumn = trimmedStartColumn + fieldName.length + separator.length;
|
|
524
|
-
const typeConstructor = parseTypeConstructorCall(context, {
|
|
525
|
-
declarationValue: baseTypeSource,
|
|
526
|
-
lineIndex,
|
|
527
|
-
startColumn: typeStartColumn,
|
|
528
|
-
invalidCode: "PSL_INVALID_MODEL_MEMBER",
|
|
529
|
-
invalidMessage: (value) => `Invalid field type constructor "${value}"`
|
|
530
|
-
});
|
|
531
|
-
if (typeConstructor === "malformed") return;
|
|
532
|
-
let typeName;
|
|
533
|
-
let typeNamespaceId;
|
|
534
|
-
let typeContractSpaceId;
|
|
535
|
-
if (typeConstructor) typeName = typeConstructor.path.join(".");
|
|
536
|
-
else {
|
|
537
|
-
const colonCount = (baseTypeSource.match(/:/g) ?? []).length;
|
|
538
|
-
if (colonCount > 1) {
|
|
539
|
-
pushDiagnostic(context, {
|
|
540
|
-
code: "PSL_INVALID_MODEL_MEMBER",
|
|
541
|
-
message: `Invalid model member declaration "${line}"`,
|
|
542
|
-
span: createTrimmedLineSpan(context, lineIndex)
|
|
543
|
-
});
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
let typeRefSource;
|
|
547
|
-
if (colonCount === 1) {
|
|
548
|
-
const colonIndex = baseTypeSource.indexOf(":");
|
|
549
|
-
const spaceCandidate = baseTypeSource.slice(0, colonIndex);
|
|
550
|
-
typeRefSource = baseTypeSource.slice(colonIndex + 1);
|
|
551
|
-
if (!/^[A-Za-z_]\w*$/.test(spaceCandidate)) {
|
|
552
|
-
pushDiagnostic(context, {
|
|
553
|
-
code: "PSL_INVALID_MODEL_MEMBER",
|
|
554
|
-
message: `Invalid model member declaration "${line}"`,
|
|
555
|
-
span: createTrimmedLineSpan(context, lineIndex)
|
|
556
|
-
});
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
559
|
-
typeContractSpaceId = spaceCandidate;
|
|
560
|
-
} else typeRefSource = baseTypeSource;
|
|
561
|
-
if ((typeRefSource.match(/\./g) ?? []).length > 1) {
|
|
562
|
-
pushDiagnostic(context, {
|
|
563
|
-
code: "PSL_INVALID_QUALIFIED_TYPE",
|
|
564
|
-
message: `Nested dot-qualified type "${baseTypeSource}" is not supported; use exactly one qualifier segment (e.g. "ns.TypeName")`,
|
|
565
|
-
span: createInlineSpan(context, lineIndex, typeStartColumn, typeStartColumn + baseTypeSource.length)
|
|
566
|
-
});
|
|
567
|
-
return;
|
|
568
|
-
}
|
|
569
|
-
const singleMatch = typeRefSource.match(/^([A-Za-z_]\w*)(?:\.([A-Za-z_]\w*))?$/);
|
|
570
|
-
if (!singleMatch) {
|
|
571
|
-
pushDiagnostic(context, {
|
|
572
|
-
code: "PSL_INVALID_MODEL_MEMBER",
|
|
573
|
-
message: `Invalid model member declaration "${line}"`,
|
|
574
|
-
span: createTrimmedLineSpan(context, lineIndex)
|
|
575
|
-
});
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
|
-
if (singleMatch[2] !== void 0) {
|
|
579
|
-
typeNamespaceId = singleMatch[1];
|
|
580
|
-
typeName = singleMatch[2];
|
|
581
|
-
} else typeName = singleMatch[1] ?? "";
|
|
582
|
-
}
|
|
583
|
-
const attributes = [];
|
|
584
|
-
const attributeSource = attributePart.trimStart();
|
|
585
|
-
const leadingAttributeWhitespace = attributePart.length - attributeSource.length;
|
|
586
|
-
const tokenParse = extractAttributeTokensWithSpans(context, lineIndex, attributeSource, trimmedStartColumn + fieldName.length + separator.length + typeAndAttributeSplit.attributeOffset + leadingAttributeWhitespace);
|
|
587
|
-
if (!tokenParse.ok) return {
|
|
588
|
-
kind: "field",
|
|
589
|
-
name: fieldName,
|
|
590
|
-
typeName,
|
|
591
|
-
...ifDefined("typeNamespaceId", typeNamespaceId),
|
|
592
|
-
...ifDefined("typeContractSpaceId", typeContractSpaceId),
|
|
593
|
-
...ifDefined("typeConstructor", typeConstructor),
|
|
594
|
-
optional,
|
|
595
|
-
list,
|
|
596
|
-
attributes,
|
|
597
|
-
span: createTrimmedLineSpan(context, lineIndex)
|
|
598
|
-
};
|
|
599
|
-
for (const token of tokenParse.tokens) {
|
|
600
|
-
const parsed = parseAttributeToken(context, {
|
|
601
|
-
token: token.text,
|
|
602
|
-
target: "field",
|
|
603
|
-
lineIndex,
|
|
604
|
-
span: token.span
|
|
605
|
-
});
|
|
606
|
-
if (parsed) attributes.push(parsed);
|
|
607
|
-
}
|
|
608
|
-
return {
|
|
609
|
-
kind: "field",
|
|
610
|
-
name: fieldName,
|
|
611
|
-
typeName,
|
|
612
|
-
...ifDefined("typeNamespaceId", typeNamespaceId),
|
|
613
|
-
...ifDefined("typeContractSpaceId", typeContractSpaceId),
|
|
614
|
-
...ifDefined("typeConstructor", typeConstructor),
|
|
615
|
-
optional,
|
|
616
|
-
list,
|
|
617
|
-
attributes,
|
|
618
|
-
span: createTrimmedLineSpan(context, lineIndex)
|
|
619
|
-
};
|
|
620
|
-
}
|
|
621
|
-
function isQuoteEscaped(value, quoteIndex) {
|
|
622
|
-
let backslashCount = 0;
|
|
623
|
-
for (let index = quoteIndex - 1; index >= 0 && value[index] === "\\"; index -= 1) backslashCount += 1;
|
|
624
|
-
return backslashCount % 2 === 1;
|
|
625
|
-
}
|
|
626
|
-
function splitTypeAndAttributes(value) {
|
|
627
|
-
let depthParen = 0;
|
|
628
|
-
let depthBracket = 0;
|
|
629
|
-
let depthBrace = 0;
|
|
630
|
-
let quote = null;
|
|
631
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
632
|
-
const character = value[index] ?? "";
|
|
633
|
-
if (quote) {
|
|
634
|
-
if (character === quote && !isQuoteEscaped(value, index)) quote = null;
|
|
635
|
-
continue;
|
|
636
|
-
}
|
|
637
|
-
if (character === "\"" || character === "'") {
|
|
638
|
-
quote = character;
|
|
639
|
-
continue;
|
|
640
|
-
}
|
|
641
|
-
if (character === "(") {
|
|
642
|
-
depthParen += 1;
|
|
643
|
-
continue;
|
|
644
|
-
}
|
|
645
|
-
if (character === ")") {
|
|
646
|
-
depthParen = Math.max(0, depthParen - 1);
|
|
647
|
-
continue;
|
|
648
|
-
}
|
|
649
|
-
if (character === "[") {
|
|
650
|
-
depthBracket += 1;
|
|
651
|
-
continue;
|
|
652
|
-
}
|
|
653
|
-
if (character === "]") {
|
|
654
|
-
depthBracket = Math.max(0, depthBracket - 1);
|
|
655
|
-
continue;
|
|
656
|
-
}
|
|
657
|
-
if (character === "{") {
|
|
658
|
-
depthBrace += 1;
|
|
659
|
-
continue;
|
|
660
|
-
}
|
|
661
|
-
if (character === "}") {
|
|
662
|
-
depthBrace = Math.max(0, depthBrace - 1);
|
|
663
|
-
continue;
|
|
664
|
-
}
|
|
665
|
-
if (character === "@" && depthParen === 0 && depthBracket === 0 && depthBrace === 0) return {
|
|
666
|
-
typeSource: value.slice(0, index).trimEnd(),
|
|
667
|
-
attributeSource: value.slice(index),
|
|
668
|
-
attributeOffset: index
|
|
669
|
-
};
|
|
670
|
-
}
|
|
671
|
-
return {
|
|
672
|
-
typeSource: value.trimEnd(),
|
|
673
|
-
attributeSource: "",
|
|
674
|
-
attributeOffset: value.length
|
|
675
|
-
};
|
|
676
|
-
}
|
|
677
|
-
function parseAttributeToken(context, input) {
|
|
678
|
-
const expectsBlockPrefix = input.target === "model" || input.target === "enum";
|
|
679
|
-
const targetLabel = input.target === "enum" ? "Enum" : "Model";
|
|
680
|
-
if (expectsBlockPrefix && !input.token.startsWith("@@")) {
|
|
681
|
-
pushDiagnostic(context, {
|
|
682
|
-
code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
|
|
683
|
-
message: `${targetLabel} attribute "${input.token}" must use @@ prefix`,
|
|
684
|
-
span: input.span
|
|
685
|
-
});
|
|
686
|
-
return;
|
|
687
|
-
}
|
|
688
|
-
if (!expectsBlockPrefix && !input.token.startsWith("@")) {
|
|
689
|
-
pushDiagnostic(context, {
|
|
690
|
-
code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
|
|
691
|
-
message: `Attribute "${input.token}" must use @ prefix`,
|
|
692
|
-
span: input.span
|
|
693
|
-
});
|
|
694
|
-
return;
|
|
695
|
-
}
|
|
696
|
-
if (!expectsBlockPrefix && input.token.startsWith("@@")) {
|
|
697
|
-
pushDiagnostic(context, {
|
|
698
|
-
code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
|
|
699
|
-
message: `Attribute "${input.token}" is not valid in ${input.target} context`,
|
|
700
|
-
span: input.span
|
|
701
|
-
});
|
|
702
|
-
return;
|
|
703
|
-
}
|
|
704
|
-
const rawBody = expectsBlockPrefix ? input.token.slice(2) : input.token.slice(1);
|
|
705
|
-
const openParen = rawBody.indexOf("(");
|
|
706
|
-
const closeParen = rawBody.lastIndexOf(")");
|
|
707
|
-
const hasArgs = openParen >= 0 || closeParen >= 0;
|
|
708
|
-
if (openParen >= 0 && closeParen === -1 || openParen === -1 && closeParen >= 0) {
|
|
709
|
-
pushDiagnostic(context, {
|
|
710
|
-
code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
|
|
711
|
-
message: `Invalid attribute syntax "${input.token}"`,
|
|
712
|
-
span: input.span
|
|
713
|
-
});
|
|
714
|
-
return;
|
|
715
|
-
}
|
|
716
|
-
const name = (openParen >= 0 ? rawBody.slice(0, openParen) : rawBody).trim();
|
|
717
|
-
if (!/^[A-Za-z_][A-Za-z0-9_-]*(\.[A-Za-z_][A-Za-z0-9_-]*)*$/.test(name)) {
|
|
718
|
-
pushDiagnostic(context, {
|
|
719
|
-
code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
|
|
720
|
-
message: `Invalid attribute name "${name || input.token}"`,
|
|
721
|
-
span: input.span
|
|
722
|
-
});
|
|
723
|
-
return;
|
|
724
|
-
}
|
|
725
|
-
let args = [];
|
|
726
|
-
if (hasArgs && openParen >= 0 && closeParen >= openParen) {
|
|
727
|
-
if (closeParen !== rawBody.length - 1) {
|
|
728
|
-
pushDiagnostic(context, {
|
|
729
|
-
code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
|
|
730
|
-
message: `Invalid trailing syntax in attribute "${input.token}"`,
|
|
731
|
-
span: input.span
|
|
732
|
-
});
|
|
733
|
-
return;
|
|
734
|
-
}
|
|
735
|
-
const parsedArgs = parseArgumentList(context, {
|
|
736
|
-
argsRaw: rawBody.slice(openParen + 1, closeParen),
|
|
737
|
-
argsOffset: input.span.start.column - 1 + (expectsBlockPrefix ? 2 : 1) + openParen + 1,
|
|
738
|
-
lineIndex: input.lineIndex,
|
|
739
|
-
token: input.token,
|
|
740
|
-
span: input.span,
|
|
741
|
-
invalidCode: "PSL_INVALID_ATTRIBUTE_SYNTAX",
|
|
742
|
-
invalidEmptyArgumentMessage: `Invalid empty argument in attribute "${input.token}"`,
|
|
743
|
-
invalidNamedArgumentMessage: (part) => `Invalid named argument syntax "${part}"`
|
|
744
|
-
});
|
|
745
|
-
if (!parsedArgs) return;
|
|
746
|
-
args = parsedArgs;
|
|
747
|
-
}
|
|
748
|
-
return {
|
|
749
|
-
kind: "attribute",
|
|
750
|
-
target: input.target,
|
|
751
|
-
name,
|
|
752
|
-
args,
|
|
753
|
-
span: input.span
|
|
754
|
-
};
|
|
755
|
-
}
|
|
756
|
-
function parseArgumentList(context, input) {
|
|
757
|
-
if (input.argsRaw.trim().length === 0) return [];
|
|
758
|
-
const parts = splitTopLevelSegments(input.argsRaw, ",");
|
|
759
|
-
const args = [];
|
|
760
|
-
for (const part of parts) {
|
|
761
|
-
const original = part.value;
|
|
762
|
-
const trimmedPart = original.trim();
|
|
763
|
-
if (trimmedPart.length === 0) {
|
|
764
|
-
pushDiagnostic(context, {
|
|
765
|
-
code: input.invalidCode,
|
|
766
|
-
message: input.invalidEmptyArgumentMessage,
|
|
767
|
-
span: input.span
|
|
768
|
-
});
|
|
769
|
-
return;
|
|
770
|
-
}
|
|
771
|
-
const leadingWhitespace = original.length - original.trimStart().length;
|
|
772
|
-
const partStart = input.argsOffset + part.start + leadingWhitespace;
|
|
773
|
-
const partEnd = partStart + trimmedPart.length;
|
|
774
|
-
const partSpan = createInlineSpan(context, input.lineIndex, partStart, partEnd);
|
|
775
|
-
const namedSplit = splitTopLevelSegments(trimmedPart, ":");
|
|
776
|
-
if (namedSplit.length > 1) {
|
|
777
|
-
const first = namedSplit[0];
|
|
778
|
-
if (!first) {
|
|
779
|
-
pushDiagnostic(context, {
|
|
780
|
-
code: input.invalidCode,
|
|
781
|
-
message: input.invalidNamedArgumentMessage(trimmedPart),
|
|
782
|
-
span: partSpan
|
|
783
|
-
});
|
|
784
|
-
return;
|
|
785
|
-
}
|
|
786
|
-
const name = first.value.trim();
|
|
787
|
-
const rawValue = trimmedPart.slice(first.end + 1).trim();
|
|
788
|
-
if (!name || rawValue.length === 0) {
|
|
789
|
-
pushDiagnostic(context, {
|
|
790
|
-
code: input.invalidCode,
|
|
791
|
-
message: input.invalidNamedArgumentMessage(trimmedPart),
|
|
792
|
-
span: partSpan
|
|
793
|
-
});
|
|
794
|
-
return;
|
|
795
|
-
}
|
|
796
|
-
args.push({
|
|
797
|
-
kind: "named",
|
|
798
|
-
name,
|
|
799
|
-
value: normalizeAttributeArgumentValue(rawValue),
|
|
800
|
-
span: partSpan
|
|
801
|
-
});
|
|
802
|
-
continue;
|
|
803
|
-
}
|
|
804
|
-
args.push({
|
|
805
|
-
kind: "positional",
|
|
806
|
-
value: normalizeAttributeArgumentValue(trimmedPart),
|
|
807
|
-
span: partSpan
|
|
808
|
-
});
|
|
809
|
-
}
|
|
810
|
-
return args;
|
|
811
|
-
}
|
|
812
|
-
function normalizeAttributeArgumentValue(value) {
|
|
813
|
-
return value.trim();
|
|
814
|
-
}
|
|
815
|
-
function findBlockBounds(context, startLine) {
|
|
816
|
-
let depth = 0;
|
|
817
|
-
for (let lineIndex = startLine; lineIndex < context.lines.length; lineIndex += 1) {
|
|
818
|
-
const line = stripInlineComment(context.lines[lineIndex] ?? "");
|
|
819
|
-
let quote = null;
|
|
820
|
-
for (let index = 0; index < line.length; index += 1) {
|
|
821
|
-
const character = line[index] ?? "";
|
|
822
|
-
if (quote) {
|
|
823
|
-
if (character === quote && !isQuoteEscaped(line, index)) quote = null;
|
|
824
|
-
continue;
|
|
825
|
-
}
|
|
826
|
-
if (character === "\"" || character === "'") {
|
|
827
|
-
quote = character;
|
|
828
|
-
continue;
|
|
829
|
-
}
|
|
830
|
-
if (character === "{") depth += 1;
|
|
831
|
-
if (character === "}") {
|
|
832
|
-
depth -= 1;
|
|
833
|
-
if (depth === 0) return {
|
|
834
|
-
startLine,
|
|
835
|
-
endLine: lineIndex,
|
|
836
|
-
closed: true
|
|
837
|
-
};
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
pushDiagnostic(context, {
|
|
842
|
-
code: "PSL_UNTERMINATED_BLOCK",
|
|
843
|
-
message: "Unterminated block declaration",
|
|
844
|
-
span: createTrimmedLineSpan(context, startLine)
|
|
845
|
-
});
|
|
846
|
-
return {
|
|
847
|
-
startLine,
|
|
848
|
-
endLine: context.lines.length - 1,
|
|
849
|
-
closed: false
|
|
850
|
-
};
|
|
851
|
-
}
|
|
852
|
-
function splitTopLevelSegments(value, separator) {
|
|
853
|
-
const parts = [];
|
|
854
|
-
let depthParen = 0;
|
|
855
|
-
let depthBracket = 0;
|
|
856
|
-
let depthBrace = 0;
|
|
857
|
-
let quote = null;
|
|
858
|
-
let start = 0;
|
|
859
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
860
|
-
const character = value[index] ?? "";
|
|
861
|
-
if (quote) {
|
|
862
|
-
if (character === quote && !isQuoteEscaped(value, index)) quote = null;
|
|
863
|
-
continue;
|
|
864
|
-
}
|
|
865
|
-
if (character === "\"" || character === "'") {
|
|
866
|
-
quote = character;
|
|
867
|
-
continue;
|
|
868
|
-
}
|
|
869
|
-
if (character === "(") {
|
|
870
|
-
depthParen += 1;
|
|
871
|
-
continue;
|
|
872
|
-
}
|
|
873
|
-
if (character === ")") {
|
|
874
|
-
depthParen = Math.max(0, depthParen - 1);
|
|
875
|
-
continue;
|
|
876
|
-
}
|
|
877
|
-
if (character === "[") {
|
|
878
|
-
depthBracket += 1;
|
|
879
|
-
continue;
|
|
880
|
-
}
|
|
881
|
-
if (character === "]") {
|
|
882
|
-
depthBracket = Math.max(0, depthBracket - 1);
|
|
883
|
-
continue;
|
|
884
|
-
}
|
|
885
|
-
if (character === "{") {
|
|
886
|
-
depthBrace += 1;
|
|
887
|
-
continue;
|
|
888
|
-
}
|
|
889
|
-
if (character === "}") {
|
|
890
|
-
depthBrace = Math.max(0, depthBrace - 1);
|
|
891
|
-
continue;
|
|
892
|
-
}
|
|
893
|
-
if (character === separator && depthParen === 0 && depthBracket === 0 && depthBrace === 0) {
|
|
894
|
-
parts.push({
|
|
895
|
-
value: value.slice(start, index),
|
|
896
|
-
start,
|
|
897
|
-
end: index
|
|
898
|
-
});
|
|
899
|
-
start = index + 1;
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
parts.push({
|
|
903
|
-
value: value.slice(start),
|
|
904
|
-
start,
|
|
905
|
-
end: value.length
|
|
906
|
-
});
|
|
907
|
-
return parts;
|
|
908
|
-
}
|
|
909
|
-
function extractAttributeTokensWithSpans(context, lineIndex, value, startColumn) {
|
|
910
|
-
const tokens = [];
|
|
911
|
-
let index = 0;
|
|
912
|
-
while (index < value.length) {
|
|
913
|
-
while (index < value.length && /\s/.test(value[index] ?? "")) index += 1;
|
|
914
|
-
if (index >= value.length) break;
|
|
915
|
-
if (value[index] !== "@") {
|
|
916
|
-
pushDiagnostic(context, {
|
|
917
|
-
code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
|
|
918
|
-
message: `Invalid attribute syntax "${value.trim()}"`,
|
|
919
|
-
span: createInlineSpan(context, lineIndex, startColumn + index, startColumn + value.length)
|
|
920
|
-
});
|
|
921
|
-
return {
|
|
922
|
-
ok: false,
|
|
923
|
-
tokens
|
|
924
|
-
};
|
|
925
|
-
}
|
|
926
|
-
const start = index;
|
|
927
|
-
index += 1;
|
|
928
|
-
if (value[index] === "@") index += 1;
|
|
929
|
-
const nameStart = index;
|
|
930
|
-
while (index < value.length && /[A-Za-z0-9_.-]/.test(value[index] ?? "")) index += 1;
|
|
931
|
-
if (index === nameStart) {
|
|
932
|
-
pushDiagnostic(context, {
|
|
933
|
-
code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
|
|
934
|
-
message: `Invalid attribute syntax "${value.slice(start).trim()}"`,
|
|
935
|
-
span: createInlineSpan(context, lineIndex, startColumn + start, startColumn + value.length)
|
|
936
|
-
});
|
|
937
|
-
return {
|
|
938
|
-
ok: false,
|
|
939
|
-
tokens
|
|
940
|
-
};
|
|
941
|
-
}
|
|
942
|
-
if (value[index] === "(") {
|
|
943
|
-
let depth = 0;
|
|
944
|
-
let quote = null;
|
|
945
|
-
while (index < value.length) {
|
|
946
|
-
const char = value[index] ?? "";
|
|
947
|
-
if (quote) {
|
|
948
|
-
if (char === quote && !isQuoteEscaped(value, index)) quote = null;
|
|
949
|
-
index += 1;
|
|
950
|
-
continue;
|
|
951
|
-
}
|
|
952
|
-
if (char === "\"" || char === "'") {
|
|
953
|
-
quote = char;
|
|
954
|
-
index += 1;
|
|
955
|
-
continue;
|
|
956
|
-
}
|
|
957
|
-
if (char === "(") depth += 1;
|
|
958
|
-
else if (char === ")") {
|
|
959
|
-
depth -= 1;
|
|
960
|
-
if (depth === 0) {
|
|
961
|
-
index += 1;
|
|
962
|
-
break;
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
index += 1;
|
|
966
|
-
}
|
|
967
|
-
if (depth !== 0) {
|
|
968
|
-
pushDiagnostic(context, {
|
|
969
|
-
code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
|
|
970
|
-
message: `Unterminated attribute argument list in "${value.slice(start).trim()}"`,
|
|
971
|
-
span: createInlineSpan(context, lineIndex, startColumn + start, startColumn + value.length)
|
|
972
|
-
});
|
|
973
|
-
return {
|
|
974
|
-
ok: false,
|
|
975
|
-
tokens
|
|
976
|
-
};
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
const tokenText = value.slice(start, index).trim();
|
|
980
|
-
tokens.push({
|
|
981
|
-
text: tokenText,
|
|
982
|
-
span: createInlineSpan(context, lineIndex, startColumn + start, startColumn + index)
|
|
983
|
-
});
|
|
984
|
-
while (index < value.length && /\s/.test(value[index] ?? "")) index += 1;
|
|
985
|
-
if (index < value.length && value[index] !== "@") break;
|
|
986
|
-
}
|
|
987
|
-
if (index < value.length && value[index] !== "@") {
|
|
988
|
-
pushDiagnostic(context, {
|
|
989
|
-
code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
|
|
990
|
-
message: `Invalid attribute syntax "${value.trim()}"`,
|
|
991
|
-
span: createInlineSpan(context, lineIndex, startColumn + index, startColumn + value.length)
|
|
992
|
-
});
|
|
993
|
-
return {
|
|
994
|
-
ok: false,
|
|
995
|
-
tokens
|
|
996
|
-
};
|
|
997
|
-
}
|
|
998
|
-
return {
|
|
999
|
-
ok: true,
|
|
1000
|
-
tokens
|
|
1001
|
-
};
|
|
1002
|
-
}
|
|
1003
|
-
function stripInlineComment(line) {
|
|
1004
|
-
let quote = null;
|
|
1005
|
-
for (let index = 0; index < line.length - 1; index += 1) {
|
|
1006
|
-
const current = line[index] ?? "";
|
|
1007
|
-
const next = line[index + 1] ?? "";
|
|
1008
|
-
if (quote) {
|
|
1009
|
-
if (current === quote && !isQuoteEscaped(line, index)) quote = null;
|
|
1010
|
-
continue;
|
|
1011
|
-
}
|
|
1012
|
-
if (current === "\"" || current === "'") {
|
|
1013
|
-
quote = current;
|
|
1014
|
-
continue;
|
|
1015
|
-
}
|
|
1016
|
-
if (current === "/" && next === "/") return line.slice(0, index);
|
|
1017
|
-
}
|
|
1018
|
-
return line;
|
|
1019
|
-
}
|
|
1020
|
-
function computeLineOffsets(schema) {
|
|
1021
|
-
const offsets = [0];
|
|
1022
|
-
for (let index = 0; index < schema.length; index += 1) if (schema[index] === "\n") offsets.push(index + 1);
|
|
1023
|
-
return offsets;
|
|
1024
|
-
}
|
|
1025
|
-
function firstNonWhitespaceColumn(line) {
|
|
1026
|
-
const first = line.search(/\S/);
|
|
1027
|
-
return first === -1 ? 0 : first;
|
|
1028
|
-
}
|
|
1029
|
-
function createInlineSpan(context, lineIndex, startColumn, endColumn) {
|
|
1030
|
-
return {
|
|
1031
|
-
start: createPosition(context, lineIndex, startColumn),
|
|
1032
|
-
end: createPosition(context, lineIndex, endColumn)
|
|
1033
|
-
};
|
|
1034
|
-
}
|
|
1035
|
-
function createTrimmedLineSpan(context, lineIndex) {
|
|
1036
|
-
const line = context.lines[lineIndex] ?? "";
|
|
1037
|
-
return {
|
|
1038
|
-
start: createPosition(context, lineIndex, firstNonWhitespaceColumn(line)),
|
|
1039
|
-
end: createPosition(context, lineIndex, line.length)
|
|
1040
|
-
};
|
|
1041
|
-
}
|
|
1042
|
-
function createLineRangeSpan(context, startLine, endLine) {
|
|
1043
|
-
const startLineText = context.lines[startLine] ?? "";
|
|
1044
|
-
const endLineText = context.lines[endLine] ?? "";
|
|
1045
|
-
return {
|
|
1046
|
-
start: createPosition(context, startLine, firstNonWhitespaceColumn(startLineText)),
|
|
1047
|
-
end: createPosition(context, endLine, endLineText.length)
|
|
1048
|
-
};
|
|
1049
|
-
}
|
|
1050
|
-
function createPosition(context, lineIndex, columnIndex) {
|
|
1051
|
-
const clampedLineIndex = Math.max(0, Math.min(lineIndex, context.lineOffsets.length - 1));
|
|
1052
|
-
const lineText = context.lines[clampedLineIndex] ?? "";
|
|
1053
|
-
const clampedColumnIndex = Math.max(0, Math.min(columnIndex, lineText.length));
|
|
1054
|
-
return {
|
|
1055
|
-
offset: (context.lineOffsets[clampedLineIndex] ?? 0) + clampedColumnIndex,
|
|
1056
|
-
line: clampedLineIndex + 1,
|
|
1057
|
-
column: clampedColumnIndex + 1
|
|
1058
|
-
};
|
|
1059
|
-
}
|
|
1060
|
-
function pushDiagnostic(context, diagnostic) {
|
|
1061
|
-
context.diagnostics.push({
|
|
1062
|
-
...diagnostic,
|
|
1063
|
-
sourceId: context.sourceId
|
|
1064
|
-
});
|
|
1065
|
-
}
|
|
1066
|
-
function lookupExtensionBlockDescriptor(namespace, keyword) {
|
|
1067
|
-
if (!Object.hasOwn(namespace, keyword)) return;
|
|
1068
|
-
const value = namespace[keyword];
|
|
1069
|
-
return isAuthoringPslBlockDescriptor(value) ? value : void 0;
|
|
1070
|
-
}
|
|
1071
|
-
/**
|
|
1072
|
-
* Reads an extension block body generically, driven by the descriptor's
|
|
1073
|
-
* `parameters` map. Each `key = <rhs>` line in the body is matched against
|
|
1074
|
-
* a declared parameter and the RHS is captured per the parameter's kind:
|
|
1075
|
-
*
|
|
1076
|
-
* - `ref` → the bareword identifier token (`PslExtensionBlockParamRef`)
|
|
1077
|
-
* - `value` → the raw RHS text verbatim (`PslExtensionBlockParamScalarValue`)
|
|
1078
|
-
* - `option` → the chosen bareword token (`PslExtensionBlockParamOption`)
|
|
1079
|
-
* - `list` → bracketed `[a, b, …]` list, each element captured per `of`
|
|
1080
|
-
* (`PslExtensionBlockParamList`)
|
|
1081
|
-
*
|
|
1082
|
-
* No validation runs here (unknown key, missing required, codec acceptance,
|
|
1083
|
-
* option-in-set, ref resolution). A body line that is not `key = value`
|
|
1084
|
-
* shaped emits a `PSL_INVALID_EXTENSION_BLOCK_MEMBER` parse diagnostic.
|
|
1085
|
-
*/
|
|
1086
|
-
function parseExtensionBlock(context, descriptor, blockName, keywordLineIndex, bounds) {
|
|
1087
|
-
const parameters = {};
|
|
1088
|
-
for (let lineIndex = bounds.startLine + 1; lineIndex < bounds.endLine; lineIndex += 1) {
|
|
1089
|
-
const line = stripInlineComment(context.lines[lineIndex] ?? "").trim();
|
|
1090
|
-
if (line.length === 0) continue;
|
|
1091
|
-
const assignMatch = line.match(/^([A-Za-z_]\w*)\s*=\s*(.+)$/);
|
|
1092
|
-
if (!assignMatch) {
|
|
1093
|
-
pushDiagnostic(context, {
|
|
1094
|
-
code: "PSL_INVALID_EXTENSION_BLOCK_MEMBER",
|
|
1095
|
-
message: `Invalid extension block body line "${line}"; expected "key = value"`,
|
|
1096
|
-
span: createTrimmedLineSpan(context, lineIndex)
|
|
1097
|
-
});
|
|
1098
|
-
continue;
|
|
1099
|
-
}
|
|
1100
|
-
const key = assignMatch[1] ?? "";
|
|
1101
|
-
const rhsRaw = (assignMatch[2] ?? "").trim();
|
|
1102
|
-
const paramDescriptor = descriptor.parameters[key];
|
|
1103
|
-
if (paramDescriptor === void 0) {
|
|
1104
|
-
parameters[key] = {
|
|
1105
|
-
kind: "value",
|
|
1106
|
-
raw: rhsRaw,
|
|
1107
|
-
span: createTrimmedLineSpan(context, lineIndex)
|
|
1108
|
-
};
|
|
1109
|
-
continue;
|
|
1110
|
-
}
|
|
1111
|
-
const captured = captureParamRhs(context, lineIndex, rhsRaw, paramDescriptor);
|
|
1112
|
-
if (captured !== void 0) parameters[key] = captured;
|
|
1113
|
-
}
|
|
1114
|
-
return {
|
|
1115
|
-
kind: descriptor.discriminator,
|
|
1116
|
-
name: blockName,
|
|
1117
|
-
parameters,
|
|
1118
|
-
span: createLineRangeSpan(context, keywordLineIndex, bounds.endLine)
|
|
1119
|
-
};
|
|
1120
|
-
}
|
|
1121
|
-
/**
|
|
1122
|
-
* Captures a single parameter RHS string according to the declared parameter
|
|
1123
|
-
* kind. Returns `undefined` and pushes a diagnostic only if the syntax is
|
|
1124
|
-
* genuinely malformed (e.g. a `list` with no opening bracket). Value
|
|
1125
|
-
* acceptance (codec, option-in-set, ref resolution) is handled by the validator.
|
|
1126
|
-
*/
|
|
1127
|
-
function captureParamRhs(context, lineIndex, rhs, param) {
|
|
1128
|
-
const span = createTrimmedLineSpan(context, lineIndex);
|
|
1129
|
-
switch (param.kind) {
|
|
1130
|
-
case "ref": return {
|
|
1131
|
-
kind: "ref",
|
|
1132
|
-
identifier: rhs,
|
|
1133
|
-
span
|
|
1134
|
-
};
|
|
1135
|
-
case "value": return {
|
|
1136
|
-
kind: "value",
|
|
1137
|
-
raw: rhs,
|
|
1138
|
-
span
|
|
1139
|
-
};
|
|
1140
|
-
case "option": return {
|
|
1141
|
-
kind: "option",
|
|
1142
|
-
token: rhs,
|
|
1143
|
-
span
|
|
1144
|
-
};
|
|
1145
|
-
case "list": {
|
|
1146
|
-
if (!rhs.startsWith("[") || !rhs.endsWith("]")) {
|
|
1147
|
-
pushDiagnostic(context, {
|
|
1148
|
-
code: "PSL_INVALID_EXTENSION_BLOCK_MEMBER",
|
|
1149
|
-
message: `Expected a bracketed list "[…]" for list parameter, got "${rhs}"`,
|
|
1150
|
-
span
|
|
1151
|
-
});
|
|
1152
|
-
return;
|
|
1153
|
-
}
|
|
1154
|
-
const inner = rhs.slice(1, -1).trim();
|
|
1155
|
-
const items = [];
|
|
1156
|
-
if (inner.length > 0) {
|
|
1157
|
-
const segments = splitTopLevelSegments(inner, ",");
|
|
1158
|
-
for (const segment of segments) {
|
|
1159
|
-
const itemRhs = segment.value.trim();
|
|
1160
|
-
if (itemRhs.length === 0) continue;
|
|
1161
|
-
const item = captureParamRhs(context, lineIndex, itemRhs, param.of);
|
|
1162
|
-
if (item !== void 0) items.push(item);
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
return {
|
|
1166
|
-
kind: "list",
|
|
1167
|
-
items,
|
|
1168
|
-
span
|
|
1169
|
-
};
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
}
|
|
1173
|
-
//#endregion
|
|
1174
|
-
export { parsePslDocument as t };
|
|
1175
|
-
|
|
1176
|
-
//# sourceMappingURL=parser-Cw_zV0M5.mjs.map
|