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.
- package/dist/casing.d.ts +1 -4
- package/dist/casing.d.ts.map +1 -1
- package/dist/casing.js +0 -29
- package/dist/casing.js.map +1 -1
- package/dist/casing.test.js +2 -13
- package/dist/casing.test.js.map +1 -1
- package/dist/command_line_parser.d.ts +3 -3
- package/dist/command_line_parser.d.ts.map +1 -1
- package/dist/command_line_parser.js +35 -38
- package/dist/command_line_parser.js.map +1 -1
- package/dist/command_line_parser.test.js +73 -78
- package/dist/command_line_parser.test.js.map +1 -1
- package/dist/compatibility_checker.d.ts +9 -3
- package/dist/compatibility_checker.d.ts.map +1 -1
- package/dist/compatibility_checker.js +17 -4
- package/dist/compatibility_checker.js.map +1 -1
- package/dist/compatibility_checker.test.js +55 -1
- package/dist/compatibility_checker.test.js.map +1 -1
- package/dist/compiler.js +34 -17
- package/dist/compiler.js.map +1 -1
- package/dist/config.d.ts +5 -35
- package/dist/config.d.ts.map +1 -1
- package/dist/definition_finder.d.ts +1 -1
- package/dist/definition_finder.d.ts.map +1 -1
- package/dist/doc_comment_parser.d.ts +3 -0
- package/dist/doc_comment_parser.d.ts.map +1 -0
- package/dist/doc_comment_parser.js +219 -0
- package/dist/doc_comment_parser.js.map +1 -0
- package/dist/doc_comment_parser.test.d.ts +2 -0
- package/dist/doc_comment_parser.test.d.ts.map +1 -0
- package/dist/doc_comment_parser.test.js +494 -0
- package/dist/doc_comment_parser.test.js.map +1 -0
- package/dist/error_renderer.d.ts +1 -1
- package/dist/error_renderer.d.ts.map +1 -1
- package/dist/error_renderer.js +84 -65
- package/dist/error_renderer.js.map +1 -1
- package/dist/formatter.d.ts +15 -2
- package/dist/formatter.d.ts.map +1 -1
- package/dist/formatter.js +191 -234
- package/dist/formatter.js.map +1 -1
- package/dist/formatter.test.js +322 -88
- package/dist/formatter.test.js.map +1 -1
- package/dist/index.d.ts +1 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -4
- package/dist/index.js.map +1 -1
- package/dist/language_server.js +1 -1
- package/dist/language_server.js.map +1 -1
- package/dist/literals.d.ts +1 -2
- package/dist/literals.d.ts.map +1 -1
- package/dist/literals.js +1 -12
- package/dist/literals.js.map +1 -1
- package/dist/literals.test.js +1 -4
- package/dist/literals.test.js.map +1 -1
- package/dist/module_set.d.ts +3 -7
- package/dist/module_set.d.ts.map +1 -1
- package/dist/module_set.js +205 -51
- package/dist/module_set.js.map +1 -1
- package/dist/module_set.test.js +595 -28
- package/dist/module_set.test.js.map +1 -1
- package/dist/parser.d.ts +3 -4
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +185 -92
- package/dist/parser.js.map +1 -1
- package/dist/parser.test.js +243 -15
- package/dist/parser.test.js.map +1 -1
- package/dist/project_initializer.d.ts +2 -0
- package/dist/project_initializer.d.ts.map +1 -0
- package/dist/project_initializer.js +30 -0
- package/dist/project_initializer.js.map +1 -0
- package/dist/snapshotter.d.ts +3 -0
- package/dist/snapshotter.d.ts.map +1 -1
- package/dist/snapshotter.js +43 -6
- package/dist/snapshotter.js.map +1 -1
- package/dist/tokenizer.d.ts +8 -2
- package/dist/tokenizer.d.ts.map +1 -1
- package/dist/tokenizer.js +26 -20
- package/dist/tokenizer.js.map +1 -1
- package/dist/tokenizer.test.js +285 -269
- package/dist/tokenizer.test.js.map +1 -1
- package/package.json +7 -5
- package/src/casing.ts +1 -36
- package/src/command_line_parser.ts +42 -48
- package/src/compatibility_checker.ts +29 -7
- package/src/compiler.ts +35 -18
- package/src/definition_finder.ts +1 -1
- package/src/doc_comment_parser.ts +246 -0
- package/src/error_renderer.ts +90 -66
- package/src/formatter.ts +249 -238
- package/src/index.ts +0 -6
- package/src/language_server.ts +8 -8
- package/src/literals.ts +5 -14
- package/src/module_set.ts +259 -79
- package/src/parser.ts +213 -98
- package/src/project_initializer.ts +39 -0
- package/src/snapshotter.ts +46 -5
- package/src/tokenizer.ts +47 -25
- package/dist/encoding.d.ts +0 -2
- package/dist/encoding.d.ts.map +0 -1
- package/dist/encoding.js +0 -38
- package/dist/encoding.js.map +0 -1
- package/dist/encoding.test.d.ts +0 -2
- package/dist/encoding.test.d.ts.map +0 -1
- package/dist/encoding.test.js +0 -23
- package/dist/encoding.test.js.map +0 -1
- package/dist/index.test.d.ts +0 -2
- package/dist/index.test.d.ts.map +0 -1
- package/dist/index.test.js +0 -14
- package/dist/index.test.js.map +0 -1
- package/dist/types.d.ts +0 -375
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/src/encoding.ts +0 -32
- 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 "
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
136
|
+
(it.current !== "" && !isLastToken(it.previous))
|
|
129
137
|
) {
|
|
130
138
|
let nestedLevel = 0;
|
|
131
139
|
while (true) {
|
|
132
|
-
const token = it.
|
|
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.
|
|
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
|
|
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
|
-
|
|
354
|
-
if (
|
|
355
|
-
|
|
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.
|
|
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.
|
|
410
|
+
if (it.expectThenNext([")"]).case < 0) {
|
|
366
411
|
return null;
|
|
367
412
|
}
|
|
368
413
|
}
|
|
369
|
-
if (it.
|
|
414
|
+
if (it.expectThenNext(["{"]).case < 0) {
|
|
370
415
|
return null;
|
|
371
416
|
}
|
|
372
417
|
const declarations = parseDeclarations(it, recordType);
|
|
373
|
-
it.
|
|
418
|
+
it.expectThenNext(["}"]);
|
|
374
419
|
const builder = new RecordBuilder(
|
|
375
|
-
|
|
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
|
|
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.
|
|
454
|
+
const match = it.expectThenNext(expected);
|
|
405
455
|
switch (match.case) {
|
|
406
456
|
case 0: {
|
|
407
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
662
|
+
while (it.current === ".") {
|
|
575
663
|
it.next();
|
|
576
|
-
const match = it.
|
|
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.
|
|
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
|
|
604
|
-
// Assumes the current token is the token after
|
|
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.
|
|
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.
|
|
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.
|
|
700
|
-
const aliasMatch = it.
|
|
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.
|
|
706
|
-
const modulePathMatch = it.
|
|
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.
|
|
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.
|
|
812
|
+
while (it.current === ",") {
|
|
725
813
|
it.next();
|
|
726
|
-
const nameMatch = it.
|
|
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.
|
|
733
|
-
const modulePathMatch = it.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
752
|
-
|
|
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
|
|
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.
|
|
852
|
+
if (it.expectThenNext([")"]).case < 0 || it.expectThenNext([":"]).case < 0) {
|
|
760
853
|
return null;
|
|
761
854
|
}
|
|
762
|
-
const
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
986
|
+
if (it.current === closingToken) {
|
|
886
987
|
it.next();
|
|
887
988
|
return entries;
|
|
888
989
|
}
|
|
889
|
-
const fieldNameMatch = it.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
1135
|
+
expectThenNext(
|
|
1020
1136
|
expected: ReadonlyArray<string | TokenPredicate | null>,
|
|
1021
1137
|
): TokenMatch {
|
|
1022
|
-
|
|
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() : `
|
|
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
|
-
|
|
1066
|
-
return this.tokens[this.tokenIndex]
|
|
1189
|
+
get currentToken(): Token {
|
|
1190
|
+
return this.tokens[this.tokenIndex]!;
|
|
1067
1191
|
}
|
|
1068
1192
|
|
|
1069
|
-
|
|
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[] {
|