skir 1.2.3 → 1.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +1 -1
  2. package/dist/config_parser.d.ts.map +1 -1
  3. package/dist/module_collector.d.ts.map +1 -1
  4. package/dist/parser.d.ts.map +1 -1
  5. package/package.json +6 -4
  6. package/src/config_parser.ts +4 -1
  7. package/src/module_collector.ts +18 -20
  8. package/src/parser.ts +12 -0
  9. package/src/project_initializer.ts +18 -0
  10. package/dist/casing.d.ts +0 -5
  11. package/dist/casing.js +0 -23
  12. package/dist/casing.js.map +0 -1
  13. package/dist/command_line_parser.d.ts +0 -36
  14. package/dist/command_line_parser.d.ts.map +0 -1
  15. package/dist/command_line_parser.js +0 -240
  16. package/dist/command_line_parser.js.map +0 -1
  17. package/dist/compatibility_checker.d.ts +0 -74
  18. package/dist/compatibility_checker.js +0 -331
  19. package/dist/compatibility_checker.js.map +0 -1
  20. package/dist/compiler.d.ts +0 -3
  21. package/dist/compiler.js +0 -406
  22. package/dist/compiler.js.map +0 -1
  23. package/dist/completion_helper.d.ts +0 -27
  24. package/dist/completion_helper.js +0 -101
  25. package/dist/completion_helper.js.map +0 -1
  26. package/dist/config.d.ts +0 -18
  27. package/dist/config.js +0 -19
  28. package/dist/config.js.map +0 -1
  29. package/dist/config_parser.d.ts +0 -34
  30. package/dist/config_parser.js +0 -214
  31. package/dist/config_parser.js.map +0 -1
  32. package/dist/definition_finder.d.ts +0 -16
  33. package/dist/definition_finder.js +0 -375
  34. package/dist/definition_finder.js.map +0 -1
  35. package/dist/dependency_manager.d.ts +0 -14
  36. package/dist/dependency_manager.js +0 -109
  37. package/dist/dependency_manager.js.map +0 -1
  38. package/dist/doc_comment_parser.d.ts +0 -6
  39. package/dist/doc_comment_parser.js +0 -269
  40. package/dist/doc_comment_parser.js.map +0 -1
  41. package/dist/doc_reference_resolver.d.ts +0 -5
  42. package/dist/doc_reference_resolver.js +0 -232
  43. package/dist/doc_reference_resolver.js.map +0 -1
  44. package/dist/error_renderer.d.ts +0 -24
  45. package/dist/error_renderer.js +0 -326
  46. package/dist/error_renderer.js.map +0 -1
  47. package/dist/exit_error.d.ts +0 -8
  48. package/dist/exit_error.d.ts.map +0 -1
  49. package/dist/exit_error.js +0 -8
  50. package/dist/exit_error.js.map +0 -1
  51. package/dist/expected_names.d.ts +0 -11
  52. package/dist/expected_names.js +0 -34
  53. package/dist/expected_names.js.map +0 -1
  54. package/dist/formatter.d.ts +0 -28
  55. package/dist/formatter.js +0 -338
  56. package/dist/formatter.js.map +0 -1
  57. package/dist/get_dependencies_flow.d.ts +0 -40
  58. package/dist/get_dependencies_flow.js +0 -177
  59. package/dist/get_dependencies_flow.js.map +0 -1
  60. package/dist/import_block_formatter.d.ts +0 -3
  61. package/dist/import_block_formatter.js +0 -36
  62. package/dist/import_block_formatter.js.map +0 -1
  63. package/dist/index.d.ts +0 -2
  64. package/dist/index.d.ts.map +0 -1
  65. package/dist/index.js +0 -2
  66. package/dist/index.js.map +0 -1
  67. package/dist/io.d.ts +0 -23
  68. package/dist/io.d.ts.map +0 -1
  69. package/dist/io.js +0 -58
  70. package/dist/io.js.map +0 -1
  71. package/dist/literals.d.ts +0 -12
  72. package/dist/literals.js +0 -96
  73. package/dist/literals.js.map +0 -1
  74. package/dist/module_collector.d.ts +0 -9
  75. package/dist/module_collector.js +0 -75
  76. package/dist/module_collector.js.map +0 -1
  77. package/dist/module_set.d.ts +0 -51
  78. package/dist/module_set.js +0 -1331
  79. package/dist/module_set.js.map +0 -1
  80. package/dist/package_downloader.d.ts +0 -4
  81. package/dist/package_downloader.js +0 -256
  82. package/dist/package_downloader.js.map +0 -1
  83. package/dist/package_types.d.ts +0 -30
  84. package/dist/package_types.d.ts.map +0 -1
  85. package/dist/package_types.js +0 -2
  86. package/dist/package_types.js.map +0 -1
  87. package/dist/parser.d.ts +0 -7
  88. package/dist/parser.js +0 -1323
  89. package/dist/parser.js.map +0 -1
  90. package/dist/project_initializer.d.ts +0 -2
  91. package/dist/project_initializer.d.ts.map +0 -1
  92. package/dist/project_initializer.js +0 -189
  93. package/dist/project_initializer.js.map +0 -1
  94. package/dist/snapshotter.d.ts +0 -16
  95. package/dist/snapshotter.js +0 -263
  96. package/dist/snapshotter.js.map +0 -1
  97. package/dist/tokenizer.d.ts +0 -18
  98. package/dist/tokenizer.js +0 -244
  99. package/dist/tokenizer.js.map +0 -1
package/dist/parser.js DELETED
@@ -1,1323 +0,0 @@
1
- import { convertCase, unquoteAndUnescape } from "skir-internal";
2
- import * as Casing from "./casing.js";
3
- import { mergeDocs } from "./doc_comment_parser.js";
4
- /** Runs syntactic analysis on a module. */
5
- export function parseModule(moduleTokens, mode) {
6
- const { modulePath, sourceCode } = moduleTokens;
7
- const errors = [];
8
- const it = new TokenIterator(moduleTokens, mode, errors);
9
- const maybeBrokenDeclarations = parseDeclarations(it, "module");
10
- const brokenConstants = maybeBrokenDeclarations.filter((d) => d.kind === "broken-constant");
11
- const brokenMethods = maybeBrokenDeclarations.filter((d) => d.kind === "broken-method");
12
- const declarations = maybeBrokenDeclarations.filter((d) => d.kind !== "broken-constant" && d.kind !== "broken-method");
13
- it.expectThenNext([""]);
14
- // Create a mappinng from names to declarations, and check for duplicates.
15
- const nameToDeclaration = {};
16
- const pathToImports = new Map();
17
- for (const declaration of declarations) {
18
- let nameTokens;
19
- let otherModulePath;
20
- let importDeclaration;
21
- if (declaration.kind === "import") {
22
- nameTokens = declaration.importedNames;
23
- otherModulePath = resolveModulePath(declaration.modulePath, modulePath, errors);
24
- importDeclaration = declaration;
25
- }
26
- else if (declaration.kind === "import-alias") {
27
- nameTokens = [declaration.name];
28
- otherModulePath = resolveModulePath(declaration.modulePath, modulePath, errors);
29
- importDeclaration = declaration;
30
- }
31
- else {
32
- nameTokens = [declaration.name];
33
- }
34
- if (otherModulePath) {
35
- let imports = pathToImports.get(otherModulePath);
36
- if (!imports) {
37
- imports = [];
38
- pathToImports.set(otherModulePath, imports);
39
- }
40
- imports.push(importDeclaration);
41
- importDeclaration.resolvedModulePath = otherModulePath;
42
- }
43
- for (const nameToken of nameTokens) {
44
- const name = nameToken.text;
45
- if (name in nameToDeclaration) {
46
- errors.push({
47
- token: nameToken,
48
- message: `Duplicate identifier '${name}'`,
49
- });
50
- }
51
- else {
52
- nameToDeclaration[name] = declaration;
53
- }
54
- }
55
- }
56
- const importBlockRange = validateImportBlock(declarations, moduleTokens, errors);
57
- const pathToImportedNames = {};
58
- for (const [path, imports] of pathToImports) {
59
- const importsNoAlias = imports.filter((i) => i.kind === "import");
60
- const importsWithAlias = imports.filter((i) => i.kind === "import-alias");
61
- if (importsNoAlias.length && importsWithAlias.length) {
62
- for (const importNoAlias of importsNoAlias) {
63
- errors.push({
64
- token: importNoAlias.modulePath,
65
- message: "Module already imported with an alias",
66
- });
67
- }
68
- continue;
69
- }
70
- if (importsWithAlias.length >= 2) {
71
- for (const importWithAlias of importsWithAlias.slice(1)) {
72
- errors.push({
73
- token: importWithAlias.modulePath,
74
- message: "Module already imported with a different alias",
75
- });
76
- }
77
- continue;
78
- }
79
- if (importsNoAlias.length) {
80
- const names = new Set();
81
- for (const importNoAlias of importsNoAlias) {
82
- for (const importedName of importNoAlias.importedNames) {
83
- names.add(importedName.text);
84
- }
85
- }
86
- pathToImportedNames[path] = {
87
- kind: "some",
88
- names: names,
89
- };
90
- }
91
- else {
92
- const alias = importsWithAlias[0].name.text;
93
- pathToImportedNames[path] = {
94
- kind: "all",
95
- alias: alias,
96
- };
97
- }
98
- }
99
- const methods = declarations.filter((d) => d.kind === "method");
100
- const constants = declarations.filter((d) => d.kind === "constant");
101
- return {
102
- result: {
103
- kind: "module",
104
- path: modulePath,
105
- sourceCode: sourceCode,
106
- nameToDeclaration: nameToDeclaration,
107
- declarations: declarations,
108
- importBlockRange: importBlockRange,
109
- records: collectModuleRecords(declarations),
110
- pathToImportedNames: pathToImportedNames,
111
- methods: methods,
112
- brokenMethods: brokenMethods,
113
- constants: constants,
114
- brokenConstants: brokenConstants,
115
- },
116
- errors: errors,
117
- };
118
- }
119
- function parseDeclarations(it, parentNode) {
120
- const result = [];
121
- // Returns true on a next token if it indicates that the statement is over.
122
- const isEndToken = (t) => t === "" || (parentNode !== "module" && t === "}");
123
- // Returns true if the token may be the last token of a valid statement.
124
- const isLastToken = (t) => t === "}" || t === ";";
125
- while (!isEndToken(it.current)) {
126
- const startIndex = it.index;
127
- const declaration = parseDeclaration(it, parentNode);
128
- if (declaration !== null) {
129
- result.push(declaration);
130
- if (declaration.kind === "method" ||
131
- declaration.kind === "broken-method") {
132
- if (declaration.inlineRequestRecord) {
133
- result.push(declaration.inlineRequestRecord);
134
- }
135
- if (declaration.inlineResponseRecord) {
136
- result.push(declaration.inlineResponseRecord);
137
- }
138
- }
139
- }
140
- else {
141
- // We have an invalid statement. An error was already registered. Perhaps
142
- // the statement was parsed entirely but was incorrect (`removed 1, 1;`), or
143
- // zero tokens were consumed (`a`), or a few tokens were consumed but did
144
- // not form a statement. We want to recover from whichever scenario to avoid
145
- // showing unhelpful extra error messages.
146
- const noTokenWasConsumed = it.index === startIndex;
147
- if (noTokenWasConsumed) {
148
- it.next();
149
- if (isLastToken(it.previous)) {
150
- // For example: two semicolons in a row.
151
- continue;
152
- }
153
- }
154
- if (noTokenWasConsumed ||
155
- (it.current !== "" && !isLastToken(it.previous))) {
156
- let nestedLevel = 0;
157
- while (true) {
158
- const token = it.current;
159
- if (token === "") {
160
- break;
161
- }
162
- it.next();
163
- if (token === "{") {
164
- ++nestedLevel;
165
- }
166
- else if (token === "}") {
167
- --nestedLevel;
168
- }
169
- if (nestedLevel <= 0 && isLastToken(token)) {
170
- break;
171
- }
172
- }
173
- }
174
- }
175
- }
176
- return result;
177
- }
178
- function parseDeclaration(it, parentNode) {
179
- const doc = collectDoc(it);
180
- let recordType = "enum";
181
- const parentIsRoot = parentNode === "module";
182
- const expected = [
183
- /*0:*/ "struct",
184
- /*1:*/ "enum",
185
- /*2:*/ parentIsRoot ? null : "removed",
186
- /*3:*/ parentIsRoot ? null : TOKEN_IS_IDENTIFIER,
187
- /*4:*/ parentIsRoot ? "import" : null,
188
- /*5:*/ parentIsRoot ? "method" : null,
189
- /*6:*/ parentIsRoot ? "const" : null,
190
- ];
191
- const match = it.expectThenNext(expected);
192
- switch (match.case) {
193
- case 0:
194
- recordType = "struct";
195
- // Falls through.
196
- case 1:
197
- return parseRecord(it, recordType, doc);
198
- case 2:
199
- return parseRemoved(it, match.token);
200
- case 3:
201
- return parseField(it, match.token, doc, parentNode);
202
- case 4:
203
- return parseImport(it, match.token);
204
- case 5:
205
- return parseMethod(it, doc);
206
- case 6:
207
- return parseConstant(it, doc);
208
- default:
209
- return null;
210
- }
211
- }
212
- class RecordBuilder {
213
- constructor(recordName, recordType, doc, stableId, inlineContext, errors) {
214
- this.recordName = recordName;
215
- this.recordType = recordType;
216
- this.doc = doc;
217
- this.stableId = stableId;
218
- this.inlineContext = inlineContext;
219
- this.errors = errors;
220
- this.nameToDeclaration = {};
221
- this.numbers = new Set();
222
- this.numbering = "";
223
- this.removedNumbers = [];
224
- }
225
- addDeclaration(declaration) {
226
- if (this.numbering === "broken") {
227
- return;
228
- }
229
- let nameToken;
230
- let errorToken;
231
- let numbers = [];
232
- let newNumbering = this.numbering;
233
- // Unless explicitly specified, the number assigned to the first field of an
234
- // enum is 1.
235
- const nextImplicitNumber = this.numbers.size + (this.recordType === "enum" ? 1 : 0);
236
- switch (declaration.kind) {
237
- case "field": {
238
- nameToken = declaration.name;
239
- errorToken = nameToken;
240
- newNumbering = declaration.number < 0 ? "implicit" : "explicit";
241
- if (declaration.number < 0) {
242
- declaration = { ...declaration, number: nextImplicitNumber };
243
- }
244
- numbers = [declaration.number];
245
- break;
246
- }
247
- case "record": {
248
- nameToken = declaration.name;
249
- errorToken = nameToken;
250
- break;
251
- }
252
- case "removed": {
253
- errorToken = declaration.removedToken;
254
- if (declaration.numbers.length) {
255
- newNumbering = "explicit";
256
- numbers = declaration.numbers;
257
- }
258
- else {
259
- newNumbering = "implicit";
260
- numbers = [nextImplicitNumber];
261
- }
262
- }
263
- }
264
- // Make sure we're not mixing implicit and explicit numbering.
265
- if (this.numbering === "") {
266
- this.numbering = newNumbering;
267
- }
268
- else if (this.numbering !== newNumbering) {
269
- this.errors.push({
270
- token: errorToken,
271
- message: "Cannot mix implicit and explicit numbering",
272
- });
273
- this.numbering = "broken";
274
- }
275
- // Register the record/field name and make sure it's unique.
276
- if (nameToken !== undefined) {
277
- const name = nameToken.text;
278
- if (name in this.nameToDeclaration) {
279
- this.errors.push({
280
- token: nameToken,
281
- message: `Duplicate identifier '${name}'`,
282
- });
283
- return;
284
- }
285
- this.nameToDeclaration[name] = declaration;
286
- }
287
- // Register the field number and make sure it's unique.
288
- for (const number of numbers) {
289
- if (this.numbers.has(number)) {
290
- this.errors.push({
291
- token: errorToken,
292
- message: `Duplicate field number ${number}`,
293
- });
294
- this.numbering = "broken";
295
- return;
296
- }
297
- else if (number === 0 && this.recordType === "enum") {
298
- this.errors.push({
299
- token: errorToken,
300
- message: "Number 0 is reserved for UNKNOWN field",
301
- });
302
- return;
303
- }
304
- this.numbers.add(number);
305
- }
306
- // Register the removed field numbers.
307
- if (declaration.kind === "removed") {
308
- this.removedNumbers.push(...numbers);
309
- }
310
- }
311
- build() {
312
- const isStruct = this.recordType === "struct";
313
- // If the record is a struct, make sure that all field numbers are
314
- // consecutive starting from 0. The fields of an enum, on the other hand,
315
- // can be sparse.
316
- if (isStruct) {
317
- for (let i = 0; i < this.numbers.size; ++i) {
318
- if (this.numbers.has(i)) {
319
- continue;
320
- }
321
- this.errors.push({
322
- token: this.recordName,
323
- message: `Missing field number ${i}`,
324
- });
325
- break;
326
- }
327
- }
328
- const declarations = Object.values(this.nameToDeclaration);
329
- const fields = declarations.filter((d) => d.kind === "field");
330
- const nestedRecords = declarations.filter((d) => d.kind === "record");
331
- const { recordName } = this;
332
- let key = `${recordName.line.modulePath}:${recordName.position}`;
333
- if (this.inlineContext) {
334
- key += `:${this.inlineContext.context}`;
335
- }
336
- const numSlots = isStruct && fields.length
337
- ? Math.max(...fields.map((f) => f.number)) + 1
338
- : 0;
339
- const numSlotsInclRemovedNumbers = isStruct ? this.numbers.size : 0;
340
- return {
341
- kind: "record",
342
- key: key,
343
- name: this.recordName,
344
- recordType: this.recordType,
345
- doc: this.doc,
346
- nameToDeclaration: this.nameToDeclaration,
347
- declarations: Object.values(this.nameToDeclaration),
348
- fields: fields,
349
- nestedRecords: nestedRecords,
350
- removedNumbers: this.removedNumbers.sort(),
351
- recordNumber: this.stableId,
352
- numSlots: numSlots,
353
- numSlotsInclRemovedNumbers: numSlotsInclRemovedNumbers,
354
- };
355
- }
356
- }
357
- function parseRecord(it, recordType, doc, inlineContext) {
358
- // A struct or an enum.
359
- let nameToken;
360
- if (inlineContext) {
361
- const { originalName } = inlineContext;
362
- let transformedName = convertCase(originalName.text, "UpperCamel");
363
- if (inlineContext.context === "method-request") {
364
- transformedName += "Request";
365
- }
366
- else if (inlineContext.context === "method-response") {
367
- transformedName += "Response";
368
- }
369
- nameToken = {
370
- ...originalName,
371
- text: transformedName,
372
- };
373
- }
374
- else {
375
- // Read the name.
376
- const nameMatch = it.expectThenNext([TOKEN_IS_IDENTIFIER]);
377
- if (nameMatch.case < 0) {
378
- return null;
379
- }
380
- Casing.validate(nameMatch.token, "UpperCamel", it.errors);
381
- nameToken = nameMatch.token;
382
- }
383
- let stableId = null;
384
- if (it.current === "(") {
385
- it.next();
386
- stableId = parseUint32(it, "?");
387
- if (stableId === -2) {
388
- return null;
389
- }
390
- if (it.expectThenNext([")"]).case < 0) {
391
- return null;
392
- }
393
- }
394
- if (it.expectThenNext(["{"]).case < 0) {
395
- return null;
396
- }
397
- const declarations = parseDeclarations(it, recordType);
398
- it.expectThenNext(["}"]);
399
- const builder = new RecordBuilder(nameToken, recordType, doc, stableId, inlineContext, it.errors);
400
- for (const declaration of declarations) {
401
- builder.addDeclaration(declaration);
402
- if (declaration.kind === "field" && declaration.inlineRecord) {
403
- builder.addDeclaration(declaration.inlineRecord);
404
- }
405
- }
406
- return builder.build();
407
- }
408
- function parseField(it, name, doc, recordType) {
409
- // May only be undefined if the type is an enum.
410
- let type;
411
- let inlineRecord;
412
- let number = -1;
413
- const makeField = () => {
414
- return {
415
- kind: "field",
416
- name: name,
417
- number: number,
418
- doc: doc,
419
- unresolvedType: type,
420
- // Will be populated at a later stage.
421
- type: undefined,
422
- // Will be populated at a later stage.
423
- isRecursive: false,
424
- inlineRecord: inlineRecord,
425
- };
426
- };
427
- while (true) {
428
- const typeAllowed = type === undefined && number < 0;
429
- const endAllowed = type !== undefined || recordType === "enum";
430
- const numberAllowed = number < 0 && endAllowed;
431
- const expected = [
432
- /*0:*/ typeAllowed ? ":" : null,
433
- /*1:*/ numberAllowed ? "=" : null,
434
- /*2:*/ endAllowed ? ";" : null,
435
- ];
436
- const match = it.expectThenNext(expected);
437
- switch (match.case) {
438
- case 0: {
439
- const inlineContext = {
440
- context: "field",
441
- originalName: name,
442
- };
443
- const typeOrInlineRecord = parseTypeOrInlineRecord(it, inlineContext);
444
- type = typeOrInlineRecord.type;
445
- inlineRecord = typeOrInlineRecord.inlineRecord;
446
- if (type === undefined) {
447
- return null;
448
- }
449
- break;
450
- }
451
- case 1: {
452
- number = parseUint32(it);
453
- if (number < 0) {
454
- return null;
455
- }
456
- break;
457
- }
458
- case 2: {
459
- const expectedCasing = type ? "lower_underscore" : "UPPER_UNDERSCORE";
460
- Casing.validate(name, expectedCasing, it.errors);
461
- if (recordType === "enum" && name.text === "UNKNOWN") {
462
- it.errors.push({
463
- token: name,
464
- message: `Cannot name field of enum: UNKNOWN`,
465
- });
466
- }
467
- return makeField();
468
- }
469
- case -1: {
470
- if (endAllowed) {
471
- return makeField();
472
- }
473
- else {
474
- return null;
475
- }
476
- }
477
- }
478
- }
479
- }
480
- const PRIMITIVE_TYPES = new Set([
481
- "bool",
482
- "int32",
483
- "int64",
484
- "hash64",
485
- "float32",
486
- "float64",
487
- "timestamp",
488
- "string",
489
- "bytes",
490
- ]);
491
- function parseTypeOrInlineRecord(it, inlineContext) {
492
- if (it.current === "struct" || it.current === "enum") {
493
- const recordType = it.current;
494
- it.next();
495
- const inlineRecord = parseRecord(it, recordType, EMPTY_DOC, inlineContext);
496
- const type = inlineRecord
497
- ? {
498
- kind: "record",
499
- nameParts: [inlineRecord.name],
500
- absolute: false,
501
- }
502
- : undefined;
503
- return {
504
- type: type,
505
- inlineRecord: inlineRecord ? inlineRecord : undefined,
506
- };
507
- }
508
- else {
509
- return {
510
- type: parseType(it),
511
- inlineRecord: undefined,
512
- };
513
- }
514
- }
515
- function parseType(it) {
516
- const match = it.expectThenNext([
517
- /*0:*/ "[",
518
- /*1:*/ TOKEN_IS_IDENTIFIER,
519
- /*2:*/ ".",
520
- ]);
521
- let value;
522
- switch (match.case) {
523
- case 0: {
524
- // Left square bracket.
525
- value = parseArrayType(it);
526
- break;
527
- }
528
- case 1:
529
- // An identifier.
530
- if (PRIMITIVE_TYPES.has(match.token.text)) {
531
- value = {
532
- kind: "primitive",
533
- primitive: match.token.text,
534
- };
535
- break;
536
- }
537
- // Falls through.
538
- case 2: {
539
- // Dot.
540
- value = parseRecordRef(it, match.token);
541
- break;
542
- }
543
- default:
544
- return undefined;
545
- }
546
- if (value === undefined) {
547
- return undefined;
548
- }
549
- if (it.current === "?") {
550
- it.next();
551
- return { kind: "optional", other: value };
552
- }
553
- else {
554
- return value;
555
- }
556
- }
557
- function parseArrayType(it) {
558
- const item = parseType(it);
559
- if (item === undefined) {
560
- return undefined;
561
- }
562
- let key = undefined;
563
- while (true) {
564
- const keyAllowed = !key && item.kind === "record";
565
- const match = it.expectThenNext([
566
- /*0:*/ keyAllowed ? "|" : null,
567
- /*1:*/ "]",
568
- ]);
569
- switch (match.case) {
570
- case 0: {
571
- // '|'
572
- key = parseFieldPath(it, match.token);
573
- if (key === null) {
574
- return undefined;
575
- }
576
- break;
577
- }
578
- case 1:
579
- return { kind: "array", item: item, key: key };
580
- default:
581
- return undefined;
582
- }
583
- }
584
- }
585
- function parseFieldPath(it, pipeToken) {
586
- const fieldNames = [];
587
- while (true) {
588
- const match = it.expectThenNext([TOKEN_IS_IDENTIFIER]);
589
- if (match.case < 0) {
590
- return undefined;
591
- }
592
- fieldNames.push(match.token);
593
- if (it.current === ".") {
594
- it.next();
595
- }
596
- else {
597
- break;
598
- }
599
- }
600
- const path = fieldNames.map((name) => ({
601
- name: name,
602
- }));
603
- return {
604
- pipeToken: pipeToken,
605
- path,
606
- // Just because we need to provide a value.
607
- // The correct value will be populated at a later stage.
608
- keyType: { kind: "primitive", primitive: "bool" },
609
- };
610
- }
611
- function parseRecordRef(it, nameOrDot) {
612
- const absolute = nameOrDot.text === ".";
613
- const nameParts = [];
614
- if (nameOrDot.text === ".") {
615
- const match = it.expectThenNext([TOKEN_IS_IDENTIFIER]);
616
- if (match.case < 0) {
617
- return undefined;
618
- }
619
- nameParts.push(match.token);
620
- }
621
- else {
622
- nameParts.push(nameOrDot);
623
- }
624
- while (it.current === ".") {
625
- it.next();
626
- const match = it.expectThenNext([TOKEN_IS_IDENTIFIER]);
627
- if (match.case < 0) {
628
- return undefined;
629
- }
630
- nameParts.push(match.token);
631
- }
632
- return { kind: "record", nameParts: nameParts, absolute: absolute };
633
- }
634
- function parseUint32(it, maybeQuestionMark) {
635
- if (maybeQuestionMark && it.mode === "lenient" && it.current === "?") {
636
- it.next();
637
- return -1;
638
- }
639
- const match = it.expectThenNext([TOKEN_IS_POSITIVE_INT]);
640
- if (match.case < 0) {
641
- return -2;
642
- }
643
- const { text } = match.token;
644
- const valueAsBigInt = BigInt(text);
645
- if (valueAsBigInt < BigInt(2 ** 32)) {
646
- return +text;
647
- }
648
- else {
649
- it.errors.push({
650
- token: match.token,
651
- message: "Value out of uint32 range",
652
- });
653
- return -2;
654
- }
655
- }
656
- // Parses the 'removed' declaration.
657
- // Assumes the current token is the token after 'removed'.
658
- function parseRemoved(it, removedToken) {
659
- const numbers = [];
660
- // The 5 states are:
661
- // · '?': expect a number or a semicolon
662
- // · ',': expect a comma or a semicolon
663
- // · '..': expect a comma, a semicolon or a '..'
664
- // · '0': expect a single number or the lower bound of a range
665
- // · '1': expect the upper bound of a range
666
- let expect = "?";
667
- let lowerBound;
668
- loop: while (true) {
669
- const endAllowed = expect === "?" || expect === "," || expect === "..";
670
- const expected = [
671
- /*0:*/ expect === "," || expect === ".." ? "," : null,
672
- /*1:*/ expect === "?" || expect === "0" || expect === "1"
673
- ? TOKEN_IS_POSITIVE_INT
674
- : null,
675
- /*2:*/ endAllowed ? ";" : null,
676
- /*3:*/ expect === ".." ? ".." : null,
677
- ];
678
- const match = it.expectThenNext(expected);
679
- switch (match.case) {
680
- case 0: {
681
- // A comma.
682
- expect = "0";
683
- break;
684
- }
685
- case 1: {
686
- // A number.
687
- const number = +match.token.text;
688
- if (lowerBound === undefined) {
689
- expect = "..";
690
- numbers.push(number);
691
- }
692
- else {
693
- expect = ",";
694
- if (number <= lowerBound) {
695
- it.errors.push({
696
- token: removedToken,
697
- message: "Upper bound must be greater than lower bound",
698
- });
699
- }
700
- for (let n = lowerBound; n <= number; ++n) {
701
- numbers.push(n);
702
- }
703
- lowerBound = undefined;
704
- }
705
- break;
706
- }
707
- case 2: {
708
- // A semicolon.
709
- break loop;
710
- }
711
- case 3: {
712
- // A '..'
713
- expect = "1";
714
- lowerBound = numbers.pop();
715
- break;
716
- }
717
- case -1:
718
- if (endAllowed) {
719
- break loop;
720
- }
721
- else {
722
- return null;
723
- }
724
- }
725
- }
726
- // Make sure we don't have a duplicate number.
727
- const seenNumbers = new Set();
728
- for (const number of numbers) {
729
- if (seenNumbers.has(number)) {
730
- it.errors.push({
731
- token: removedToken,
732
- message: `Duplicate field number ${number}`,
733
- });
734
- return null;
735
- }
736
- seenNumbers.add(number);
737
- }
738
- return {
739
- kind: "removed",
740
- removedToken: removedToken,
741
- numbers: numbers,
742
- };
743
- }
744
- function parseImport(it, importToken) {
745
- const tokenMatch = it.expectThenNext(["*", "{", TOKEN_IS_IDENTIFIER]);
746
- switch (tokenMatch.case) {
747
- case 0:
748
- return parseImportAs(it, importToken);
749
- case 1:
750
- return parseImportGivenNames(it, importToken);
751
- case 2:
752
- // TODO: drop this case once the curly bracket syntax becomes mandatory.
753
- return parseImportGivenNames(it, importToken, tokenMatch.token);
754
- default:
755
- return null;
756
- }
757
- }
758
- function parseImportAs(it, importToken) {
759
- if (it.expectThenNext(["as"]).case < 0)
760
- return null;
761
- const aliasMatch = it.expectThenNext([TOKEN_IS_IDENTIFIER]);
762
- if (aliasMatch.case < 0) {
763
- return null;
764
- }
765
- Casing.validate(aliasMatch.token, "lower_underscore", it.errors);
766
- if (it.expectThenNext(["from"]).case < 0)
767
- return null;
768
- const modulePathMatch = it.expectThenNext([TOKEN_IS_STRING_LITERAL]);
769
- if (modulePathMatch.case < 0) {
770
- return null;
771
- }
772
- const range = {
773
- start: importToken.position,
774
- end: it.currentToken.position + it.current.length,
775
- };
776
- it.expectThenNext([";"]);
777
- const modulePath = modulePathMatch.token;
778
- return {
779
- kind: "import-alias",
780
- importToken: importToken,
781
- name: aliasMatch.token,
782
- range: range,
783
- modulePath: modulePath,
784
- };
785
- }
786
- function parseImportGivenNames(it, importToken,
787
- // TODO: remove this parameter once the curly bracket syntax becomes mandatory
788
- firstName) {
789
- const importedNames = firstName ? [firstName] : [];
790
- if (firstName) {
791
- // Old syntax
792
- while (it.current === ",") {
793
- it.next();
794
- const nameMatch = it.expectThenNext([TOKEN_IS_IDENTIFIER]);
795
- if (nameMatch.case < 0) {
796
- return null;
797
- }
798
- importedNames.push(nameMatch.token);
799
- }
800
- }
801
- else {
802
- // New syntax with curly brackets
803
- while (true) {
804
- let match = it.expectThenNext([TOKEN_IS_IDENTIFIER, "}"]);
805
- if (match.case === 0) {
806
- importedNames.push(match.token);
807
- }
808
- else if (match.case === 1) {
809
- break;
810
- }
811
- else {
812
- return null;
813
- }
814
- match = it.expectThenNext([",", "}"]);
815
- if (match.case === 1) {
816
- break;
817
- }
818
- else if (match.case < 0) {
819
- return null;
820
- }
821
- }
822
- }
823
- if (it.expectThenNext(["from"]).case < 0)
824
- return null;
825
- const modulePathMatch = it.expectThenNext([TOKEN_IS_STRING_LITERAL]);
826
- if (modulePathMatch.case < 0) {
827
- return null;
828
- }
829
- const range = {
830
- start: importToken.position,
831
- end: it.currentToken.position + it.current.length,
832
- };
833
- it.expectThenNext([";"]);
834
- const modulePath = modulePathMatch.token;
835
- return {
836
- kind: "import",
837
- importToken: importToken,
838
- importedNames: importedNames,
839
- range: range,
840
- modulePath: modulePath,
841
- };
842
- }
843
- function parseMethod(it, doc) {
844
- const nameMatch = it.expectThenNext([TOKEN_IS_IDENTIFIER]);
845
- if (nameMatch.case < 0) {
846
- return null;
847
- }
848
- const name = nameMatch.token;
849
- Casing.validate(name, "UpperCamel", it.errors);
850
- if (it.expectThenNext(["("]).case < 0) {
851
- return null;
852
- }
853
- const requestTypeOrInlineRecord = parseTypeOrInlineRecord(it, {
854
- context: "method-request",
855
- originalName: name,
856
- });
857
- const requestType = requestTypeOrInlineRecord.type;
858
- if (!requestType) {
859
- return null;
860
- }
861
- if (it.expectThenNext([")"]).case < 0) {
862
- return null;
863
- }
864
- if (it.expectThenNext([":"]).case < 0) {
865
- return {
866
- kind: "broken-method",
867
- unresolvedRequestType: requestType,
868
- inlineRequestRecord: requestTypeOrInlineRecord.inlineRecord,
869
- unresolvedResponseType: undefined,
870
- inlineResponseRecord: undefined,
871
- };
872
- }
873
- const responseTypeOrInlineRecord = parseTypeOrInlineRecord(it, {
874
- context: "method-response",
875
- originalName: name,
876
- });
877
- const responseType = responseTypeOrInlineRecord.type;
878
- if (!responseType) {
879
- return null;
880
- }
881
- if (it.expectThenNext(["="]).case < 0) {
882
- return {
883
- kind: "broken-method",
884
- unresolvedRequestType: requestType,
885
- inlineRequestRecord: requestTypeOrInlineRecord.inlineRecord,
886
- unresolvedResponseType: responseType,
887
- inlineResponseRecord: responseTypeOrInlineRecord.inlineRecord,
888
- };
889
- }
890
- const number = parseUint32(it, "?");
891
- if (number === -2) {
892
- return null;
893
- }
894
- it.expectThenNext([";"]);
895
- return {
896
- kind: "method",
897
- name: nameMatch.token,
898
- doc: doc,
899
- unresolvedRequestType: requestType,
900
- unresolvedResponseType: responseType,
901
- // Will be populated at a later stage.
902
- requestType: undefined,
903
- // Will be populated at a later stage.
904
- responseType: undefined,
905
- number: number,
906
- inlineRequestRecord: requestTypeOrInlineRecord.inlineRecord,
907
- inlineResponseRecord: responseTypeOrInlineRecord.inlineRecord,
908
- };
909
- }
910
- function parseConstant(it, doc) {
911
- const nameMatch = it.expectThenNext([TOKEN_IS_IDENTIFIER]);
912
- if (nameMatch.case < 0) {
913
- return null;
914
- }
915
- Casing.validate(nameMatch.token, "UPPER_UNDERSCORE", it.errors);
916
- if (it.expectThenNext([":"]).case < 0) {
917
- return null;
918
- }
919
- const type = parseType(it);
920
- if (!type) {
921
- return null;
922
- }
923
- if (it.expectThenNext(["="]).case < 0) {
924
- return {
925
- kind: "broken-constant",
926
- unresolvedType: type,
927
- };
928
- }
929
- const value = parseValue(it);
930
- if (value === null) {
931
- return null;
932
- }
933
- it.expectThenNext([";"]);
934
- return {
935
- kind: "constant",
936
- name: nameMatch.token,
937
- doc: doc,
938
- unresolvedType: type,
939
- type: undefined,
940
- value: value,
941
- valueAsDenseJson: undefined,
942
- };
943
- }
944
- function parseValue(it) {
945
- const expected = [
946
- /*0:*/ "{",
947
- /*1:*/ "{|",
948
- /*2:*/ "[",
949
- /*3:*/ "false",
950
- /*4:*/ "true",
951
- /*5:*/ "null",
952
- /*6:*/ TOKEN_IS_NUMBER,
953
- /*7:*/ TOKEN_IS_STRING_LITERAL,
954
- ];
955
- const match = it.expectThenNext(expected);
956
- switch (match.case) {
957
- case 0:
958
- case 1: {
959
- const partial = match.case === 1;
960
- return parseObjectValue(match.token, it, partial);
961
- }
962
- case 2: {
963
- const items = parseArrayValue(it);
964
- if (items === null) {
965
- return null;
966
- }
967
- return {
968
- kind: "array",
969
- token: match.token,
970
- items: items,
971
- };
972
- }
973
- case 3:
974
- case 4:
975
- case 5:
976
- case 6:
977
- case 7:
978
- return {
979
- kind: "literal",
980
- token: match.token,
981
- };
982
- default:
983
- return null;
984
- }
985
- }
986
- function parseObjectValue(objectToken, it, partial) {
987
- const closingToken = partial ? "|}" : "}";
988
- const entries = {};
989
- const orphanNames = [];
990
- const makeObjectValue = () => ({
991
- kind: "object",
992
- token: objectToken,
993
- entries: entries,
994
- orphanNames: orphanNames,
995
- partial: partial,
996
- });
997
- while (true) {
998
- if (it.current === closingToken) {
999
- it.next();
1000
- return makeObjectValue();
1001
- }
1002
- const fieldNameMatch = it.expectThenNext([TOKEN_IS_IDENTIFIER]);
1003
- if (fieldNameMatch.case < 0) {
1004
- return null;
1005
- }
1006
- const fieldNameToken = fieldNameMatch.token;
1007
- const fieldName = fieldNameMatch.token.text;
1008
- if (it.expectThenNext([":"]).case < 0) {
1009
- orphanNames.push(fieldNameToken);
1010
- continue;
1011
- }
1012
- if (fieldName in entries) {
1013
- it.errors.push({
1014
- token: fieldNameMatch.token,
1015
- message: "Duplicate field",
1016
- });
1017
- }
1018
- const value = parseValue(it);
1019
- if (value === null) {
1020
- return null;
1021
- }
1022
- entries[fieldName] = {
1023
- name: fieldNameToken,
1024
- value: value,
1025
- };
1026
- const endMatch = it.expectThenNext([",", closingToken]);
1027
- if (endMatch.case < 0) {
1028
- return null;
1029
- }
1030
- if (endMatch.token.text === closingToken) {
1031
- return makeObjectValue();
1032
- }
1033
- }
1034
- }
1035
- function parseArrayValue(it) {
1036
- if (it.current === "]") {
1037
- it.next();
1038
- return [];
1039
- }
1040
- const items = [];
1041
- while (true) {
1042
- const item = parseValue(it);
1043
- if (item === null) {
1044
- return null;
1045
- }
1046
- items.push(item);
1047
- const match = it.expectThenNext([",", "]"]);
1048
- if (match.case < 0) {
1049
- return null;
1050
- }
1051
- if (match.token.text === "]") {
1052
- return items;
1053
- }
1054
- if (it.current === "]") {
1055
- it.next();
1056
- return items;
1057
- }
1058
- }
1059
- }
1060
- function collectDoc(it) {
1061
- const { moduleTokens } = it;
1062
- const docComments = [];
1063
- while (it.current.startsWith("///")) {
1064
- docComments.push(it.currentToken);
1065
- it.next();
1066
- }
1067
- return mergeDocs(docComments.map((c) => moduleTokens.posToDoc[c.position] ?? EMPTY_DOC));
1068
- }
1069
- const EMPTY_DOC = Object.freeze({
1070
- text: "",
1071
- pieces: Object.freeze([]),
1072
- });
1073
- class TokenPredicate {
1074
- }
1075
- class TokenIsIdentifier extends TokenPredicate {
1076
- matches(token) {
1077
- // "..." is the autocompletion placeholder
1078
- return /^\w/.test(token) || token === "...";
1079
- }
1080
- what() {
1081
- return "identifier";
1082
- }
1083
- }
1084
- const TOKEN_IS_IDENTIFIER = new TokenIsIdentifier();
1085
- class TokenIsPositiveInt extends TokenPredicate {
1086
- matches(token) {
1087
- return /^[0-9]+$/.test(token);
1088
- }
1089
- what() {
1090
- return "positive integer";
1091
- }
1092
- }
1093
- const TOKEN_IS_POSITIVE_INT = new TokenIsPositiveInt();
1094
- class TokenIsNumber extends TokenPredicate {
1095
- matches(token) {
1096
- return /^[0-9-]/.test(token);
1097
- }
1098
- what() {
1099
- return "number";
1100
- }
1101
- }
1102
- const TOKEN_IS_NUMBER = new TokenIsNumber();
1103
- class TokenIsStringLiteral extends TokenPredicate {
1104
- matches(token) {
1105
- return /^["']/.test(token);
1106
- }
1107
- what() {
1108
- return "string literal";
1109
- }
1110
- }
1111
- const TOKEN_IS_STRING_LITERAL = new TokenIsStringLiteral();
1112
- class TokenIterator {
1113
- constructor(moduleTokens, mode, errors) {
1114
- this.moduleTokens = moduleTokens;
1115
- this.mode = mode;
1116
- this.errors = errors;
1117
- this.tokenIndex = 0;
1118
- this.tokens = moduleTokens.tokens;
1119
- }
1120
- // Returns both:
1121
- // · the index of the first predicate matching the current token, or -1 if
1122
- // there is none
1123
- // · the current token (before the move)
1124
- //
1125
- // If the current token matches any predicate, i.e. if the index is not -1,
1126
- // moves to the next token before returning. Otherwise, registers an error.
1127
- expectThenNext(expected) {
1128
- let token = this.tokens[this.tokenIndex];
1129
- while (token.text.startsWith("///")) {
1130
- this.errors.push({
1131
- token: token,
1132
- message: "Doc comments can only precede declarations",
1133
- });
1134
- ++this.tokenIndex;
1135
- token = this.tokens[this.tokenIndex];
1136
- }
1137
- for (let i = 0; i < expected.length; ++i) {
1138
- const e = expected[i];
1139
- if (e === null) {
1140
- continue;
1141
- }
1142
- const match = e instanceof TokenPredicate ? e.matches(token.text) : token.text === e;
1143
- if (!match) {
1144
- continue;
1145
- }
1146
- ++this.tokenIndex;
1147
- return {
1148
- case: i,
1149
- token: token,
1150
- };
1151
- }
1152
- // No match: register an error.
1153
- const expectedParts = [];
1154
- for (let i = 0; i < expected.length; ++i) {
1155
- const e = expected[i];
1156
- if (e === null) {
1157
- continue;
1158
- }
1159
- expectedParts.push(e instanceof TokenPredicate ? e.what() : `'${e}'`);
1160
- }
1161
- const expectedMsg = expectedParts.length === 1
1162
- ? expectedParts[0]
1163
- : `one of: ${expectedParts.join(", ")}`;
1164
- this.errors.push({
1165
- token: token,
1166
- expected: expectedMsg,
1167
- });
1168
- return {
1169
- case: -1,
1170
- token: token,
1171
- };
1172
- }
1173
- get currentToken() {
1174
- return this.tokens[this.tokenIndex];
1175
- }
1176
- get current() {
1177
- return this.currentToken.text;
1178
- }
1179
- get previous() {
1180
- return this.tokens[this.tokenIndex - 1].text;
1181
- }
1182
- next() {
1183
- ++this.tokenIndex;
1184
- }
1185
- get index() {
1186
- return this.tokenIndex;
1187
- }
1188
- }
1189
- function collectModuleRecords(declarations) {
1190
- const result = [];
1191
- const collect = (declarations, ancestors) => {
1192
- for (const record of declarations) {
1193
- if (record.kind !== "record")
1194
- continue;
1195
- const updatedRecordAncestors = ancestors.concat([record]);
1196
- const modulePath = record.name.line.modulePath;
1197
- const recordLocation = {
1198
- kind: "record-location",
1199
- record: record,
1200
- recordAncestors: updatedRecordAncestors,
1201
- modulePath: modulePath,
1202
- };
1203
- // We want depth-first.
1204
- collect(record.declarations, updatedRecordAncestors);
1205
- result.push(recordLocation);
1206
- }
1207
- };
1208
- collect(declarations, []);
1209
- return result;
1210
- }
1211
- function validateImportBlock(declarations, moduleTokens, errors) {
1212
- let state = "no-import-seen";
1213
- let startPosition;
1214
- let endPosition = -1;
1215
- let anyError = false;
1216
- for (const declaration of declarations) {
1217
- if (declaration.kind === "import" || declaration.kind === "import-alias") {
1218
- if (state === "no-import-seen") {
1219
- state = "first-legal-import-seen";
1220
- startPosition = declaration.range.start;
1221
- }
1222
- else if (state === "last-legal-import-seen") {
1223
- errors.push({
1224
- token: declaration.importToken,
1225
- message: "Import declarations must be grouped together at the top",
1226
- });
1227
- anyError = true;
1228
- }
1229
- endPosition = Math.max(endPosition, declaration.range.end);
1230
- }
1231
- else {
1232
- // Not an import
1233
- state = "last-legal-import-seen";
1234
- }
1235
- }
1236
- if (startPosition === undefined || anyError) {
1237
- return null;
1238
- }
1239
- const range = {
1240
- start: startPosition,
1241
- end: endPosition,
1242
- };
1243
- // Look for comments within `range`.
1244
- {
1245
- let tokens = moduleTokens.tokensWithComments;
1246
- tokens = tokens.slice(tokens.findIndex((t) => t.position >= range.start));
1247
- tokens = tokens.slice(0, tokens.findIndex((t) => t.position >= range.end));
1248
- for (const token of tokens) {
1249
- if (token.text.startsWith("//")) {
1250
- errors.push({
1251
- token: token,
1252
- message: "Comments not allowed within import block",
1253
- });
1254
- anyError = true;
1255
- }
1256
- }
1257
- }
1258
- return anyError ? null : range;
1259
- }
1260
- /** Extracts the "@{...}/{...}/" package prefix from a module path. */
1261
- export function extractPackagePrefix(modulePath) {
1262
- const match = modulePath.match(/^(@[^/]+\/[^/]+\/)/);
1263
- return match?.at(1) ?? "";
1264
- }
1265
- function posixDirname(p) {
1266
- const i = p.lastIndexOf("/");
1267
- return i >= 0 ? p.slice(0, i) : "";
1268
- }
1269
- function posixNormalize(p) {
1270
- const parts = p.split("/");
1271
- const result = [];
1272
- for (const part of parts) {
1273
- if (part === "..") {
1274
- if (result.length > 0 && result[result.length - 1] !== "..") {
1275
- result.pop();
1276
- }
1277
- else {
1278
- result.push("..");
1279
- }
1280
- }
1281
- else if (part !== "." && part !== "") {
1282
- result.push(part);
1283
- }
1284
- }
1285
- return result.length === 0 ? "." : result.join("/");
1286
- }
1287
- function resolveModulePath(pathToken, originModulePath, errors) {
1288
- let modulePath = unquoteAndUnescape(pathToken.text);
1289
- if (/\\/.test(modulePath)) {
1290
- errors.push({
1291
- token: pathToken,
1292
- message: "Replace backslash with slash",
1293
- });
1294
- return undefined;
1295
- }
1296
- if (modulePath.startsWith("./") || modulePath.startsWith("../")) {
1297
- if (modulePath.includes("/@")) {
1298
- errors.push({
1299
- token: pathToken,
1300
- message: "Use absolute path",
1301
- });
1302
- return undefined;
1303
- }
1304
- // This is a relative path from the module. Let's transform it into a
1305
- // relative path from root.
1306
- modulePath = posixNormalize(posixDirname(originModulePath) + "/" + modulePath);
1307
- }
1308
- else if (originModulePath.startsWith("@") && !modulePath.startsWith("@")) {
1309
- const packagePrefix = extractPackagePrefix(originModulePath);
1310
- modulePath = packagePrefix + modulePath;
1311
- }
1312
- // "a/./b/../c" => "a/c"
1313
- modulePath = posixNormalize(modulePath);
1314
- if (modulePath.startsWith(`../`)) {
1315
- errors.push({
1316
- token: pathToken,
1317
- message: "Module path must point to a file within skir-src",
1318
- });
1319
- return undefined;
1320
- }
1321
- return modulePath;
1322
- }
1323
- //# sourceMappingURL=parser.js.map