skir 0.0.1 → 0.0.2

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 (115) hide show
  1. package/dist/casing.d.ts +1 -4
  2. package/dist/casing.d.ts.map +1 -1
  3. package/dist/casing.js +0 -29
  4. package/dist/casing.js.map +1 -1
  5. package/dist/casing.test.js +2 -13
  6. package/dist/casing.test.js.map +1 -1
  7. package/dist/command_line_parser.d.ts +3 -3
  8. package/dist/command_line_parser.d.ts.map +1 -1
  9. package/dist/command_line_parser.js +35 -38
  10. package/dist/command_line_parser.js.map +1 -1
  11. package/dist/command_line_parser.test.js +73 -78
  12. package/dist/command_line_parser.test.js.map +1 -1
  13. package/dist/compatibility_checker.d.ts +9 -3
  14. package/dist/compatibility_checker.d.ts.map +1 -1
  15. package/dist/compatibility_checker.js +17 -4
  16. package/dist/compatibility_checker.js.map +1 -1
  17. package/dist/compatibility_checker.test.js +55 -1
  18. package/dist/compatibility_checker.test.js.map +1 -1
  19. package/dist/compiler.js +34 -17
  20. package/dist/compiler.js.map +1 -1
  21. package/dist/config.d.ts +5 -35
  22. package/dist/config.d.ts.map +1 -1
  23. package/dist/definition_finder.d.ts +1 -1
  24. package/dist/definition_finder.d.ts.map +1 -1
  25. package/dist/doc_comment_parser.d.ts +3 -0
  26. package/dist/doc_comment_parser.d.ts.map +1 -0
  27. package/dist/doc_comment_parser.js +219 -0
  28. package/dist/doc_comment_parser.js.map +1 -0
  29. package/dist/doc_comment_parser.test.d.ts +2 -0
  30. package/dist/doc_comment_parser.test.d.ts.map +1 -0
  31. package/dist/doc_comment_parser.test.js +494 -0
  32. package/dist/doc_comment_parser.test.js.map +1 -0
  33. package/dist/error_renderer.d.ts +1 -1
  34. package/dist/error_renderer.d.ts.map +1 -1
  35. package/dist/error_renderer.js +84 -65
  36. package/dist/error_renderer.js.map +1 -1
  37. package/dist/formatter.d.ts +15 -2
  38. package/dist/formatter.d.ts.map +1 -1
  39. package/dist/formatter.js +191 -234
  40. package/dist/formatter.js.map +1 -1
  41. package/dist/formatter.test.js +322 -88
  42. package/dist/formatter.test.js.map +1 -1
  43. package/dist/index.d.ts +1 -5
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +1 -4
  46. package/dist/index.js.map +1 -1
  47. package/dist/language_server.js +1 -1
  48. package/dist/language_server.js.map +1 -1
  49. package/dist/literals.d.ts +1 -2
  50. package/dist/literals.d.ts.map +1 -1
  51. package/dist/literals.js +1 -12
  52. package/dist/literals.js.map +1 -1
  53. package/dist/literals.test.js +1 -4
  54. package/dist/literals.test.js.map +1 -1
  55. package/dist/module_set.d.ts +3 -7
  56. package/dist/module_set.d.ts.map +1 -1
  57. package/dist/module_set.js +205 -51
  58. package/dist/module_set.js.map +1 -1
  59. package/dist/module_set.test.js +595 -28
  60. package/dist/module_set.test.js.map +1 -1
  61. package/dist/parser.d.ts +3 -4
  62. package/dist/parser.d.ts.map +1 -1
  63. package/dist/parser.js +185 -92
  64. package/dist/parser.js.map +1 -1
  65. package/dist/parser.test.js +243 -15
  66. package/dist/parser.test.js.map +1 -1
  67. package/dist/project_initializer.d.ts +2 -0
  68. package/dist/project_initializer.d.ts.map +1 -0
  69. package/dist/project_initializer.js +30 -0
  70. package/dist/project_initializer.js.map +1 -0
  71. package/dist/snapshotter.d.ts +3 -0
  72. package/dist/snapshotter.d.ts.map +1 -1
  73. package/dist/snapshotter.js +43 -6
  74. package/dist/snapshotter.js.map +1 -1
  75. package/dist/tokenizer.d.ts +8 -2
  76. package/dist/tokenizer.d.ts.map +1 -1
  77. package/dist/tokenizer.js +26 -20
  78. package/dist/tokenizer.js.map +1 -1
  79. package/dist/tokenizer.test.js +285 -269
  80. package/dist/tokenizer.test.js.map +1 -1
  81. package/package.json +7 -5
  82. package/src/casing.ts +1 -36
  83. package/src/command_line_parser.ts +42 -48
  84. package/src/compatibility_checker.ts +29 -7
  85. package/src/compiler.ts +35 -18
  86. package/src/definition_finder.ts +1 -1
  87. package/src/doc_comment_parser.ts +246 -0
  88. package/src/error_renderer.ts +90 -66
  89. package/src/formatter.ts +249 -238
  90. package/src/index.ts +0 -6
  91. package/src/language_server.ts +8 -8
  92. package/src/literals.ts +5 -14
  93. package/src/module_set.ts +259 -79
  94. package/src/parser.ts +213 -98
  95. package/src/project_initializer.ts +39 -0
  96. package/src/snapshotter.ts +46 -5
  97. package/src/tokenizer.ts +47 -25
  98. package/dist/encoding.d.ts +0 -2
  99. package/dist/encoding.d.ts.map +0 -1
  100. package/dist/encoding.js +0 -38
  101. package/dist/encoding.js.map +0 -1
  102. package/dist/encoding.test.d.ts +0 -2
  103. package/dist/encoding.test.d.ts.map +0 -1
  104. package/dist/encoding.test.js +0 -23
  105. package/dist/encoding.test.js.map +0 -1
  106. package/dist/index.test.d.ts +0 -2
  107. package/dist/index.test.d.ts.map +0 -1
  108. package/dist/index.test.js +0 -14
  109. package/dist/index.test.js.map +0 -1
  110. package/dist/types.d.ts +0 -375
  111. package/dist/types.d.ts.map +0 -1
  112. package/dist/types.js +0 -2
  113. package/dist/types.js.map +0 -1
  114. package/src/encoding.ts +0 -32
  115. package/src/types.ts +0 -518
package/src/parser.ts CHANGED
@@ -1,6 +1,6 @@
1
- import * as casing from "./casing.js";
2
1
  import type {
3
2
  Declaration,
3
+ Doc,
4
4
  ErrorSink,
5
5
  FieldPath,
6
6
  Import,
@@ -16,7 +16,6 @@ import type {
16
16
  MutableRecordLevelDeclaration,
17
17
  MutableRecordLocation,
18
18
  MutableValue,
19
- Numbering,
20
19
  Primitive,
21
20
  Record,
22
21
  Removed,
@@ -26,18 +25,19 @@ import type {
26
25
  UnresolvedArrayType,
27
26
  UnresolvedRecordRef,
28
27
  UnresolvedType,
29
- } from "./types.js";
28
+ } from "skir-internal";
29
+ import { convertCase, simpleHash } from "skir-internal";
30
+ import * as casing from "./casing.js";
31
+ import { parseDocComments } from "./doc_comment_parser.js";
32
+ import { ModuleTokens } from "./tokenizer.js";
30
33
 
31
34
  /** Runs syntactic analysis on a module. */
32
- export function parseModule(
33
- tokens: readonly Token[],
34
- modulePath: string,
35
- sourceCode: string,
36
- ): Result<MutableModule> {
35
+ export function parseModule(moduleTokens: ModuleTokens): Result<MutableModule> {
36
+ const { tokens, modulePath, sourceCode } = moduleTokens;
37
37
  const errors: SkirError[] = [];
38
38
  const it = new TokenIterator(tokens, errors);
39
39
  const declarations = parseDeclarations(it, "module");
40
- it.expectThenMove([""]);
40
+ it.expectThenNext([""]);
41
41
  // Create a mappinng from names to declarations, and check for duplicates.
42
42
  const nameToDeclaration: { [name: string]: MutableModuleLevelDeclaration } =
43
43
  {};
@@ -53,7 +53,7 @@ export function parseModule(
53
53
  if (name in nameToDeclaration) {
54
54
  errors.push({
55
55
  token: nameToken,
56
- message: `Duplicate identifier "${name}"`,
56
+ message: `Duplicate identifier '${name}'`,
57
57
  });
58
58
  } else {
59
59
  nameToDeclaration[name] = declaration;
@@ -103,11 +103,19 @@ function parseDeclarations(
103
103
  t === "" || (parentNode !== "module" && t === "}");
104
104
  // Returns true if the token may be the last token of a valid statement.
105
105
  const isLastToken = (t: string): boolean => t === "}" || t === ";";
106
- while (!isEndToken(it.peek())) {
106
+ while (!isEndToken(it.current)) {
107
107
  const startIndex = it.index;
108
108
  const declaration = parseDeclaration(it, parentNode);
109
109
  if (declaration !== null) {
110
110
  result.push(declaration);
111
+ if (declaration.kind === "method") {
112
+ if (declaration.inlineRequestRecord) {
113
+ result.push(declaration.inlineRequestRecord);
114
+ }
115
+ if (declaration.inlineResponseRecord) {
116
+ result.push(declaration.inlineResponseRecord);
117
+ }
118
+ }
111
119
  continue;
112
120
  }
113
121
  // We have an invalid statement. An error was already registered. Perhaps
@@ -118,18 +126,18 @@ function parseDeclarations(
118
126
  const noTokenWasConsumed = it.index === startIndex;
119
127
  if (noTokenWasConsumed) {
120
128
  it.next();
121
- if (isLastToken(it.peekBack())) {
129
+ if (isLastToken(it.previous)) {
122
130
  // For example: two semicolons in a row.
123
131
  continue;
124
132
  }
125
133
  }
126
134
  if (
127
135
  noTokenWasConsumed ||
128
- (it.peek() !== "" && !isLastToken(it.peekBack()))
136
+ (it.current !== "" && !isLastToken(it.previous))
129
137
  ) {
130
138
  let nestedLevel = 0;
131
139
  while (true) {
132
- const token = it.peek();
140
+ const token = it.current;
133
141
  if (token === "") {
134
142
  break;
135
143
  }
@@ -152,6 +160,7 @@ function parseDeclaration(
152
160
  it: TokenIterator,
153
161
  parentNode: "module" | "struct" | "enum",
154
162
  ): MutableDeclaration | null {
163
+ const doc = parseDoc(it);
155
164
  let recordType: "struct" | "enum" = "enum";
156
165
  const parentIsRoot = parentNode === "module";
157
166
  const expected = [
@@ -163,23 +172,23 @@ function parseDeclaration(
163
172
  /*5:*/ parentIsRoot ? "method" : null,
164
173
  /*6:*/ parentIsRoot ? "const" : null,
165
174
  ];
166
- const match = it.expectThenMove(expected);
175
+ const match = it.expectThenNext(expected);
167
176
  switch (match.case) {
168
177
  case 0:
169
178
  recordType = "struct";
170
179
  // Falls through.
171
180
  case 1:
172
- return parseRecord(it, recordType);
181
+ return parseRecord(it, recordType, doc);
173
182
  case 2:
174
183
  return parseRemoved(it, match.token);
175
184
  case 3:
176
- return parseField(it, match.token, parentNode as "struct" | "enum");
185
+ return parseField(it, match.token, doc, parentNode as "struct" | "enum");
177
186
  case 4:
178
187
  return parseImport(it);
179
188
  case 5:
180
- return parseMethod(it);
189
+ return parseMethod(it, doc);
181
190
  case 6:
182
- return parseConstant(it);
191
+ return parseConstant(it, doc);
183
192
  default:
184
193
  return null;
185
194
  }
@@ -189,6 +198,7 @@ class RecordBuilder {
189
198
  constructor(
190
199
  private readonly recordName: Token,
191
200
  private readonly recordType: "struct" | "enum",
201
+ private readonly doc: Doc,
192
202
  private readonly stableId: number | null,
193
203
  private readonly errors: ErrorSink,
194
204
  ) {}
@@ -253,7 +263,7 @@ class RecordBuilder {
253
263
  if (name in this.nameToDeclaration) {
254
264
  this.errors.push({
255
265
  token: nameToken,
256
- message: `Duplicate identifier "${name}"`,
266
+ message: `Duplicate identifier '${name}'`,
257
267
  });
258
268
  return;
259
269
  }
@@ -326,11 +336,11 @@ class RecordBuilder {
326
336
  key: key,
327
337
  name: this.recordName,
328
338
  recordType: this.recordType,
339
+ doc: this.doc,
329
340
  nameToDeclaration: this.nameToDeclaration,
330
341
  declarations: Object.values(this.nameToDeclaration),
331
342
  fields: fields,
332
343
  nestedRecords: nestedRecords,
333
- numbering: this.numbering,
334
344
  removedNumbers: this.removedNumbers.sort(),
335
345
  recordNumber: this.stableId,
336
346
  numSlots: numSlots,
@@ -345,40 +355,79 @@ class RecordBuilder {
345
355
  private removedNumbers: number[] = [];
346
356
  }
347
357
 
358
+ type Numbering =
359
+ // The record does not have fields .
360
+ | ""
361
+ // Field numbers are not explicit in the schema.
362
+ | "implicit"
363
+ // Field numbers are explicit in the schema.
364
+ | "explicit"
365
+ // The record has both fields with implicit and explicit numbering.
366
+ | "broken";
367
+
368
+ interface InlineRecordContext {
369
+ context: "field" | "method-request" | "method-response";
370
+ /** Name of the field or method. */
371
+ originalName: Token;
372
+ }
373
+
348
374
  function parseRecord(
349
375
  it: TokenIterator,
350
376
  recordType: "struct" | "enum",
377
+ doc: Doc,
378
+ inlineContext?: InlineRecordContext,
351
379
  ): MutableRecord | null {
352
380
  // A struct or an enum.
353
- const nameMatch = it.expectThenMove([TOKEN_IS_IDENTIFIER]);
354
- if (nameMatch.case < 0) {
355
- return null;
381
+ let nameToken: Token;
382
+ if (inlineContext) {
383
+ const { originalName } = inlineContext;
384
+ let transformedName = convertCase(originalName.text, "UpperCamel");
385
+ if (inlineContext.context === "method-request") {
386
+ transformedName += "Request";
387
+ } else if (inlineContext.context === "method-response") {
388
+ transformedName += "Response";
389
+ }
390
+ nameToken = {
391
+ ...originalName,
392
+ text: transformedName,
393
+ };
394
+ } else {
395
+ // Read the name.
396
+ const nameMatch = it.expectThenNext([TOKEN_IS_IDENTIFIER]);
397
+ if (nameMatch.case < 0) {
398
+ return null;
399
+ }
400
+ casing.validate(nameMatch.token, "UpperCamel", it.errors);
401
+ nameToken = nameMatch.token;
356
402
  }
357
- casing.validate(nameMatch.token, "UpperCamel", it.errors);
358
403
  let stableId: number | null = null;
359
- if (it.peek() === "(") {
404
+ if (it.current === "(") {
360
405
  it.next();
361
406
  stableId = parseUint32(it);
362
407
  if (stableId < 0) {
363
408
  return null;
364
409
  }
365
- if (it.expectThenMove([")"]).case < 0) {
410
+ if (it.expectThenNext([")"]).case < 0) {
366
411
  return null;
367
412
  }
368
413
  }
369
- if (it.expectThenMove(["{"]).case < 0) {
414
+ if (it.expectThenNext(["{"]).case < 0) {
370
415
  return null;
371
416
  }
372
417
  const declarations = parseDeclarations(it, recordType);
373
- it.expectThenMove(["}"]);
418
+ it.expectThenNext(["}"]);
374
419
  const builder = new RecordBuilder(
375
- nameMatch.token,
420
+ nameToken,
376
421
  recordType,
422
+ doc,
377
423
  stableId,
378
424
  it.errors,
379
425
  );
380
426
  for (const declaration of declarations) {
381
427
  builder.addDeclaration(declaration);
428
+ if (declaration.kind === "field" && declaration.inlineRecord) {
429
+ builder.addDeclaration(declaration.inlineRecord);
430
+ }
382
431
  }
383
432
  return builder.build();
384
433
  }
@@ -386,12 +435,13 @@ function parseRecord(
386
435
  function parseField(
387
436
  it: TokenIterator,
388
437
  name: Token,
438
+ doc: Doc,
389
439
  recordType: "struct" | "enum",
390
440
  ): MutableField | null {
391
441
  // May only be undefined if the type is an enum.
392
- let type: UnresolvedType | undefined = undefined;
442
+ let type: UnresolvedType | undefined;
443
+ let inlineRecord: MutableRecord | undefined;
393
444
  let number = -1;
394
-
395
445
  while (true) {
396
446
  const typeAllowed = type === undefined && number < 0;
397
447
  const endAllowed = type !== undefined || recordType === "enum";
@@ -401,10 +451,16 @@ function parseField(
401
451
  /*1:*/ numberAllowed ? "=" : null,
402
452
  /*2:*/ endAllowed ? ";" : null,
403
453
  ];
404
- const match = it.expectThenMove(expected);
454
+ const match = it.expectThenNext(expected);
405
455
  switch (match.case) {
406
456
  case 0: {
407
- type = parseType(it);
457
+ const inlineContext: InlineRecordContext = {
458
+ context: "field",
459
+ originalName: name,
460
+ };
461
+ const typeOrInlineRecord = parseTypeOrInlineRecord(it, inlineContext);
462
+ type = typeOrInlineRecord.type;
463
+ inlineRecord = typeOrInlineRecord.inlineRecord;
408
464
  if (type === undefined) {
409
465
  return null;
410
466
  }
@@ -431,11 +487,13 @@ function parseField(
431
487
  kind: "field",
432
488
  name: name,
433
489
  number: number,
490
+ doc: doc,
434
491
  unresolvedType: type,
435
492
  // Will be populated at a later stage.
436
493
  type: undefined,
437
494
  // Will be populated at a later stage.
438
495
  isRecursive: false,
496
+ inlineRecord: inlineRecord,
439
497
  };
440
498
  }
441
499
  case -1:
@@ -456,8 +514,38 @@ const PRIMITIVE_TYPES: ReadonlySet<string> = new Set<Primitive>([
456
514
  "bytes",
457
515
  ]);
458
516
 
517
+ function parseTypeOrInlineRecord(
518
+ it: TokenIterator,
519
+ inlineContext: InlineRecordContext,
520
+ ): {
521
+ type: UnresolvedType | undefined;
522
+ inlineRecord: MutableRecord | undefined;
523
+ } {
524
+ if (it.current === "struct" || it.current === "enum") {
525
+ const recordType = it.current as "struct" | "enum";
526
+ it.next();
527
+ const inlineRecord = parseRecord(it, recordType, EMPTY_DOC, inlineContext);
528
+ const type: UnresolvedRecordRef | undefined = inlineRecord
529
+ ? {
530
+ kind: "record",
531
+ nameParts: [inlineRecord.name],
532
+ absolute: false,
533
+ }
534
+ : undefined;
535
+ return {
536
+ type: type,
537
+ inlineRecord: inlineRecord ? inlineRecord : undefined,
538
+ };
539
+ } else {
540
+ return {
541
+ type: parseType(it),
542
+ inlineRecord: undefined,
543
+ };
544
+ }
545
+ }
546
+
459
547
  function parseType(it: TokenIterator): UnresolvedType | undefined {
460
- const match = it.expectThenMove([
548
+ const match = it.expectThenNext([
461
549
  /*0:*/ "[",
462
550
  /*1:*/ TOKEN_IS_IDENTIFIER,
463
551
  /*2:*/ ".",
@@ -490,7 +578,7 @@ function parseType(it: TokenIterator): UnresolvedType | undefined {
490
578
  if (value === undefined) {
491
579
  return undefined;
492
580
  }
493
- if (it.peek() === "?") {
581
+ if (it.current === "?") {
494
582
  it.next();
495
583
  return { kind: "optional", other: value };
496
584
  } else {
@@ -506,7 +594,7 @@ function parseArrayType(it: TokenIterator): UnresolvedArrayType | undefined {
506
594
  let key: FieldPath | undefined = undefined;
507
595
  while (true) {
508
596
  const keyAllowed = !key && item.kind === "record";
509
- const match = it.expectThenMove([
597
+ const match = it.expectThenNext([
510
598
  /*0:*/ keyAllowed ? "|" : null,
511
599
  /*1:*/ "]",
512
600
  ]);
@@ -533,12 +621,12 @@ function parseFieldPath(
533
621
  ): FieldPath | undefined {
534
622
  const fieldNames: Token[] = [];
535
623
  while (true) {
536
- const match = it.expectThenMove([TOKEN_IS_IDENTIFIER]);
624
+ const match = it.expectThenNext([TOKEN_IS_IDENTIFIER]);
537
625
  if (match.case < 0) {
538
626
  return undefined;
539
627
  }
540
628
  fieldNames.push(match.token);
541
- if (it.peek() === ".") {
629
+ if (it.current === ".") {
542
630
  it.next();
543
631
  } else {
544
632
  break;
@@ -563,7 +651,7 @@ function parseRecordRef(
563
651
  const absolute = nameOrDot.text === ".";
564
652
  const nameParts: Token[] = [];
565
653
  if (nameOrDot.text === ".") {
566
- const match = it.expectThenMove([TOKEN_IS_IDENTIFIER]);
654
+ const match = it.expectThenNext([TOKEN_IS_IDENTIFIER]);
567
655
  if (match.case < 0) {
568
656
  return undefined;
569
657
  }
@@ -571,9 +659,9 @@ function parseRecordRef(
571
659
  } else {
572
660
  nameParts.push(nameOrDot);
573
661
  }
574
- while (it.peek() === ".") {
662
+ while (it.current === ".") {
575
663
  it.next();
576
- const match = it.expectThenMove([TOKEN_IS_IDENTIFIER]);
664
+ const match = it.expectThenNext([TOKEN_IS_IDENTIFIER]);
577
665
  if (match.case < 0) {
578
666
  return undefined;
579
667
  }
@@ -583,7 +671,7 @@ function parseRecordRef(
583
671
  }
584
672
 
585
673
  function parseUint32(it: TokenIterator): number {
586
- const match = it.expectThenMove([TOKEN_IS_POSITIVE_INT]);
674
+ const match = it.expectThenNext([TOKEN_IS_POSITIVE_INT]);
587
675
  if (match.case < 0) {
588
676
  return -1;
589
677
  }
@@ -600,8 +688,8 @@ function parseUint32(it: TokenIterator): number {
600
688
  }
601
689
  }
602
690
 
603
- // Parses the "removed" declaration.
604
- // Assumes the current token is the token after "removed".
691
+ // Parses the 'removed' declaration.
692
+ // Assumes the current token is the token after 'removed'.
605
693
  function parseRemoved(it: TokenIterator, removedToken: Token): Removed | null {
606
694
  const numbers: number[] = [];
607
695
  // The 5 states are:
@@ -621,7 +709,7 @@ function parseRemoved(it: TokenIterator, removedToken: Token): Removed | null {
621
709
  /*2:*/ expect === "?" || expect === "," || expect === ".." ? ";" : null,
622
710
  /*3:*/ expect === ".." ? ".." : null,
623
711
  ];
624
- const match = it.expectThenMove(expected);
712
+ const match = it.expectThenNext(expected);
625
713
  switch (match.case) {
626
714
  case 0: {
627
715
  // A comma.
@@ -684,7 +772,7 @@ function parseRemoved(it: TokenIterator, removedToken: Token): Removed | null {
684
772
  }
685
773
 
686
774
  function parseImport(it: TokenIterator): Import | ImportAlias | null {
687
- const tokenMatch = it.expectThenMove(["*", TOKEN_IS_IDENTIFIER]);
775
+ const tokenMatch = it.expectThenNext(["*", TOKEN_IS_IDENTIFIER]);
688
776
  switch (tokenMatch.case) {
689
777
  case 0:
690
778
  return parseImportAs(it);
@@ -696,18 +784,18 @@ function parseImport(it: TokenIterator): Import | ImportAlias | null {
696
784
  }
697
785
 
698
786
  function parseImportAs(it: TokenIterator): ImportAlias | null {
699
- if (it.expectThenMove(["as"]).case < 0) return null;
700
- const aliasMatch = it.expectThenMove([TOKEN_IS_IDENTIFIER]);
787
+ if (it.expectThenNext(["as"]).case < 0) return null;
788
+ const aliasMatch = it.expectThenNext([TOKEN_IS_IDENTIFIER]);
701
789
  if (aliasMatch.case < 0) {
702
790
  return null;
703
791
  }
704
792
  casing.validate(aliasMatch.token, "lower_underscore", it.errors);
705
- if (it.expectThenMove(["from"]).case < 0) return null;
706
- const modulePathMatch = it.expectThenMove([TOKEN_IS_STRING_LITERAL]);
793
+ if (it.expectThenNext(["from"]).case < 0) return null;
794
+ const modulePathMatch = it.expectThenNext([TOKEN_IS_STRING_LITERAL]);
707
795
  if (modulePathMatch.case < 0) {
708
796
  return null;
709
797
  }
710
- it.expectThenMove([";"]);
798
+ it.expectThenNext([";"]);
711
799
  const modulePath = modulePathMatch.token;
712
800
  return {
713
801
  kind: "import-alias",
@@ -721,20 +809,20 @@ function parseImportGivenNames(
721
809
  it: TokenIterator,
722
810
  ): Import | null {
723
811
  const importedNames = [firstName];
724
- while (it.peek() === ",") {
812
+ while (it.current === ",") {
725
813
  it.next();
726
- const nameMatch = it.expectThenMove([TOKEN_IS_IDENTIFIER]);
814
+ const nameMatch = it.expectThenNext([TOKEN_IS_IDENTIFIER]);
727
815
  if (nameMatch.case < 0) {
728
816
  return null;
729
817
  }
730
818
  importedNames.push(nameMatch.token);
731
819
  }
732
- if (it.expectThenMove(["from"]).case < 0) return null;
733
- const modulePathMatch = it.expectThenMove([TOKEN_IS_STRING_LITERAL]);
820
+ if (it.expectThenNext(["from"]).case < 0) return null;
821
+ const modulePathMatch = it.expectThenNext([TOKEN_IS_STRING_LITERAL]);
734
822
  if (modulePathMatch.case < 0) {
735
823
  return null;
736
824
  }
737
- it.expectThenMove([";"]);
825
+ it.expectThenNext([";"]);
738
826
  const modulePath = modulePathMatch.token;
739
827
  return {
740
828
  kind: "import",
@@ -743,35 +831,44 @@ function parseImportGivenNames(
743
831
  };
744
832
  }
745
833
 
746
- function parseMethod(it: TokenIterator): MutableMethod | null {
747
- const nameMatch = it.expectThenMove([TOKEN_IS_IDENTIFIER]);
834
+ function parseMethod(it: TokenIterator, doc: Doc): MutableMethod | null {
835
+ const nameMatch = it.expectThenNext([TOKEN_IS_IDENTIFIER]);
748
836
  if (nameMatch.case < 0) {
749
837
  return null;
750
838
  }
751
- casing.validate(nameMatch.token, "UpperCamel", it.errors);
752
- if (it.expectThenMove(["("]).case < 0) {
839
+ const name = nameMatch.token;
840
+ casing.validate(name, "UpperCamel", it.errors);
841
+ if (it.expectThenNext(["("]).case < 0) {
753
842
  return null;
754
843
  }
755
- const requestType = parseType(it);
844
+ const requestTypeOrInlineRecord = parseTypeOrInlineRecord(it, {
845
+ context: "method-request",
846
+ originalName: name,
847
+ });
848
+ const requestType = requestTypeOrInlineRecord.type;
756
849
  if (!requestType) {
757
850
  return null;
758
851
  }
759
- if (it.expectThenMove([")"]).case < 0 || it.expectThenMove([":"]).case < 0) {
852
+ if (it.expectThenNext([")"]).case < 0 || it.expectThenNext([":"]).case < 0) {
760
853
  return null;
761
854
  }
762
- const responseType = parseType(it);
855
+ const responseTypeOrInlineRecord = parseTypeOrInlineRecord(it, {
856
+ context: "method-response",
857
+ originalName: name,
858
+ });
859
+ const responseType = responseTypeOrInlineRecord.type;
763
860
  if (!responseType) {
764
861
  return null;
765
862
  }
766
863
 
767
- const explicitNumber = it.expectThenMove(["=", ";"]).case === 0;
864
+ const explicitNumber = it.expectThenNext(["=", ";"]).case === 0;
768
865
  let number: number;
769
866
  if (explicitNumber) {
770
867
  number = parseUint32(it);
771
868
  if (number < 0) {
772
869
  return null;
773
870
  }
774
- it.expectThenMove([";"]);
871
+ it.expectThenNext([";"]);
775
872
  } else {
776
873
  const methodName = nameMatch.token.text;
777
874
  const { modulePath } = nameMatch.token.line;
@@ -781,6 +878,7 @@ function parseMethod(it: TokenIterator): MutableMethod | null {
781
878
  return {
782
879
  kind: "method",
783
880
  name: nameMatch.token,
881
+ doc: doc,
784
882
  unresolvedRequestType: requestType,
785
883
  unresolvedResponseType: responseType,
786
884
  // Will be populated at a later stage.
@@ -789,33 +887,36 @@ function parseMethod(it: TokenIterator): MutableMethod | null {
789
887
  responseType: undefined,
790
888
  number: number,
791
889
  hasExplicitNumber: explicitNumber,
890
+ inlineRequestRecord: requestTypeOrInlineRecord.inlineRecord,
891
+ inlineResponseRecord: responseTypeOrInlineRecord.inlineRecord,
792
892
  };
793
893
  }
794
894
 
795
- function parseConstant(it: TokenIterator): MutableConstant | null {
796
- const nameMatch = it.expectThenMove([TOKEN_IS_IDENTIFIER]);
895
+ function parseConstant(it: TokenIterator, doc: Doc): MutableConstant | null {
896
+ const nameMatch = it.expectThenNext([TOKEN_IS_IDENTIFIER]);
797
897
  if (nameMatch.case < 0) {
798
898
  return null;
799
899
  }
800
900
  casing.validate(nameMatch.token, "UPPER_UNDERSCORE", it.errors);
801
- if (it.expectThenMove([":"]).case < 0) {
901
+ if (it.expectThenNext([":"]).case < 0) {
802
902
  return null;
803
903
  }
804
904
  const type = parseType(it);
805
905
  if (!type) {
806
906
  return null;
807
907
  }
808
- if (it.expectThenMove(["="]).case < 0) {
908
+ if (it.expectThenNext(["="]).case < 0) {
809
909
  return null;
810
910
  }
811
911
  const value = parseValue(it);
812
912
  if (value === null) {
813
913
  return null;
814
914
  }
815
- it.expectThenMove([";"]);
915
+ it.expectThenNext([";"]);
816
916
  return {
817
917
  kind: "constant",
818
918
  name: nameMatch.token,
919
+ doc: doc,
819
920
  unresolvedType: type,
820
921
  type: undefined,
821
922
  value: value,
@@ -834,7 +935,7 @@ function parseValue(it: TokenIterator): MutableValue | null {
834
935
  /*6:*/ TOKEN_IS_NUMBER,
835
936
  /*7:*/ TOKEN_IS_STRING_LITERAL,
836
937
  ];
837
- const match = it.expectThenMove(expected);
938
+ const match = it.expectThenNext(expected);
838
939
  switch (match.case) {
839
940
  case 0:
840
941
  case 1: {
@@ -882,17 +983,17 @@ function parseObjectValue(
882
983
  const closingToken = partial ? "|}" : "}";
883
984
  const entries: { [f: string]: MutableObjectEntry } = {};
884
985
  while (true) {
885
- if (it.peek() === closingToken) {
986
+ if (it.current === closingToken) {
886
987
  it.next();
887
988
  return entries;
888
989
  }
889
- const fieldNameMatch = it.expectThenMove([TOKEN_IS_IDENTIFIER]);
990
+ const fieldNameMatch = it.expectThenNext([TOKEN_IS_IDENTIFIER]);
890
991
  if (fieldNameMatch.case < 0) {
891
992
  return null;
892
993
  }
893
994
  const fieldNameToken = fieldNameMatch.token;
894
995
  const fieldName = fieldNameMatch.token.text;
895
- if (it.expectThenMove([":"]).case < 0) {
996
+ if (it.expectThenNext([":"]).case < 0) {
896
997
  return null;
897
998
  }
898
999
  const value = parseValue(it);
@@ -909,7 +1010,7 @@ function parseObjectValue(
909
1010
  name: fieldNameToken,
910
1011
  value: value,
911
1012
  };
912
- const endMatch = it.expectThenMove([",", closingToken]);
1013
+ const endMatch = it.expectThenNext([",", closingToken]);
913
1014
  if (endMatch.case < 0) {
914
1015
  return null;
915
1016
  }
@@ -920,7 +1021,7 @@ function parseObjectValue(
920
1021
  }
921
1022
 
922
1023
  function parseArrayValue(it: TokenIterator): MutableValue[] | null {
923
- if (it.peek() === "]") {
1024
+ if (it.current === "]") {
924
1025
  it.next();
925
1026
  return [];
926
1027
  }
@@ -931,20 +1032,35 @@ function parseArrayValue(it: TokenIterator): MutableValue[] | null {
931
1032
  return null;
932
1033
  }
933
1034
  items.push(item);
934
- const match = it.expectThenMove([",", "]"]);
1035
+ const match = it.expectThenNext([",", "]"]);
935
1036
  if (match.case < 0) {
936
1037
  return null;
937
1038
  }
938
1039
  if (match.token.text === "]") {
939
1040
  return items;
940
1041
  }
941
- if (it.peek() === "]") {
1042
+ if (it.current === "]") {
942
1043
  it.next();
943
1044
  return items;
944
1045
  }
945
1046
  }
946
1047
  }
947
1048
 
1049
+ function parseDoc(it: TokenIterator): Doc {
1050
+ const docComments: Token[] = [];
1051
+ while (it.current.startsWith("///")) {
1052
+ docComments.push(it.currentToken);
1053
+ it.next();
1054
+ }
1055
+ const result = parseDocComments(docComments);
1056
+ result.errors.forEach((e) => it.errors.push(e));
1057
+ return result.result;
1058
+ }
1059
+
1060
+ const EMPTY_DOC: Doc = {
1061
+ pieces: [],
1062
+ };
1063
+
948
1064
  abstract class TokenPredicate {
949
1065
  abstract matches(token: string): boolean;
950
1066
  abstract what(): string;
@@ -1016,10 +1132,18 @@ class TokenIterator {
1016
1132
  //
1017
1133
  // If the current token matches any predicate, i.e. if the index is not -1,
1018
1134
  // moves to the next token before returning. Otherwise, registers an error.
1019
- expectThenMove(
1135
+ expectThenNext(
1020
1136
  expected: ReadonlyArray<string | TokenPredicate | null>,
1021
1137
  ): TokenMatch {
1022
- const token = this.tokens[this.tokenIndex]!;
1138
+ let token = this.tokens[this.tokenIndex]!;
1139
+ while (token.text.startsWith("///")) {
1140
+ this.errors.push({
1141
+ token: token,
1142
+ message: "Doc comments can only precede declarations",
1143
+ });
1144
+ ++this.tokenIndex;
1145
+ token = this.tokens[this.tokenIndex]!;
1146
+ }
1023
1147
  for (let i = 0; i < expected.length; ++i) {
1024
1148
  const e = expected[i];
1025
1149
  if (e === null) {
@@ -1044,7 +1168,7 @@ class TokenIterator {
1044
1168
  if (e === null) {
1045
1169
  continue;
1046
1170
  }
1047
- expectedParts.push(e instanceof TokenPredicate ? e.what() : `"${e}"`);
1171
+ expectedParts.push(e instanceof TokenPredicate ? e.what() : `'${e}'`);
1048
1172
  }
1049
1173
  const expectedMsg =
1050
1174
  expectedParts.length === 1
@@ -1062,11 +1186,15 @@ class TokenIterator {
1062
1186
  };
1063
1187
  }
1064
1188
 
1065
- peek(): string {
1066
- return this.tokens[this.tokenIndex]!.text;
1189
+ get currentToken(): Token {
1190
+ return this.tokens[this.tokenIndex]!;
1067
1191
  }
1068
1192
 
1069
- peekBack(): string {
1193
+ get current(): string {
1194
+ return this.currentToken.text;
1195
+ }
1196
+
1197
+ get previous(): string {
1070
1198
  return this.tokens[this.tokenIndex - 1]!.text;
1071
1199
  }
1072
1200
 
@@ -1081,19 +1209,6 @@ class TokenIterator {
1081
1209
  private tokenIndex = 0;
1082
1210
  }
1083
1211
 
1084
- /** Returns a uint32 hash of the given string. */
1085
- export function simpleHash(input: string): number {
1086
- // From https://stackoverflow.com/questions/6122571/simple-non-secure-hash-function-for-javascript
1087
- let hash = 0;
1088
- for (let i = 0; i < input.length; i++) {
1089
- const char = input.charCodeAt(i);
1090
- hash = (hash << 5) - hash + char;
1091
- hash |= 0;
1092
- }
1093
- // Signed int32 to unsigned int32.
1094
- return hash >>> 0;
1095
- }
1096
-
1097
1212
  function collectModuleRecords(
1098
1213
  declarations: readonly Declaration[],
1099
1214
  ): MutableRecordLocation[] {