@birdcc/parser 0.0.1-alpha.0 → 0.0.1-alpha.1

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.
@@ -1,84 +0,0 @@
1
- import type { Node as SyntaxNode } from "web-tree-sitter";
2
- import type { BirdDeclaration, ParseIssue } from "../types.js";
3
- import {
4
- parseDefineDeclaration,
5
- parseIncludeDeclaration,
6
- parseRouterIdDeclaration,
7
- parseTableDeclaration,
8
- parseTemplateDeclaration,
9
- } from "./basic.js";
10
- import { parseFilterDeclaration, parseFunctionDeclaration } from "./filter.js";
11
- import { parseProtocolDeclaration } from "./protocol.js";
12
- import {
13
- parseRouterIdFromStatement,
14
- parseTableFromStatement,
15
- } from "./top-level.js";
16
-
17
- export const parseDeclarations = (
18
- rootNode: SyntaxNode,
19
- source: string,
20
- issues: ParseIssue[],
21
- ): BirdDeclaration[] => {
22
- const declarations: BirdDeclaration[] = [];
23
-
24
- for (const child of rootNode.namedChildren) {
25
- if (child.type === "include_declaration") {
26
- declarations.push(parseIncludeDeclaration(child, source, issues));
27
- continue;
28
- }
29
-
30
- if (child.type === "define_declaration") {
31
- declarations.push(parseDefineDeclaration(child, source, issues));
32
- continue;
33
- }
34
-
35
- if (child.type === "router_id_declaration") {
36
- declarations.push(parseRouterIdDeclaration(child, source, issues));
37
- continue;
38
- }
39
-
40
- if (child.type === "table_declaration") {
41
- declarations.push(parseTableDeclaration(child, source, issues));
42
- continue;
43
- }
44
-
45
- if (child.type === "protocol_declaration") {
46
- declarations.push(parseProtocolDeclaration(child, source, issues));
47
- continue;
48
- }
49
-
50
- if (child.type === "template_declaration") {
51
- declarations.push(parseTemplateDeclaration(child, source, issues));
52
- continue;
53
- }
54
-
55
- if (child.type === "filter_declaration") {
56
- declarations.push(parseFilterDeclaration(child, source, issues));
57
- continue;
58
- }
59
-
60
- if (child.type === "function_declaration") {
61
- declarations.push(parseFunctionDeclaration(child, source, issues));
62
- continue;
63
- }
64
-
65
- if (child.type === "top_level_statement") {
66
- const routerFromTopLevel = parseRouterIdFromStatement(
67
- child,
68
- source,
69
- issues,
70
- );
71
- if (routerFromTopLevel) {
72
- declarations.push(routerFromTopLevel);
73
- continue;
74
- }
75
-
76
- const tableFromTopLevel = parseTableFromStatement(child, source, issues);
77
- if (tableFromTopLevel) {
78
- declarations.push(tableFromTopLevel);
79
- }
80
- }
81
- }
82
-
83
- return declarations;
84
- };
@@ -1,597 +0,0 @@
1
- import type { Node as SyntaxNode } from "web-tree-sitter";
2
- import type {
3
- ChannelEntry,
4
- ExportStatement,
5
- ImportStatement,
6
- ParseIssue,
7
- ProtocolStatement,
8
- } from "../types.js";
9
- import { pushMissingFieldIssue } from "../issues.js";
10
- import { isPresentNode, mergeRanges, textOf, toRange } from "../tree.js";
11
- import {
12
- CHANNEL_DIRECTIONS,
13
- PROTOCOL_STATEMENT_TYPES,
14
- type ProtocolDeclaration,
15
- isIpLiteralCandidate,
16
- normalizeChannelType,
17
- protocolTypeTextAndRange,
18
- protocolStatementNodesOf,
19
- } from "./shared.js";
20
-
21
- // Keep API near parseProtocolStatements and channel fallback behavior.
22
- const parseImportExportNode = (
23
- statementNode: SyntaxNode,
24
- source: string,
25
- ): ImportStatement | ExportStatement => {
26
- const statementRange = toRange(statementNode, source);
27
- const clauseNode = statementNode.childForFieldName("clause");
28
- const isImport = statementNode.type === "import_statement";
29
-
30
- const base = {
31
- kind: isImport ? ("import" as const) : ("export" as const),
32
- ...statementRange,
33
- };
34
-
35
- if (!isPresentNode(clauseNode) || clauseNode.type === "all_clause") {
36
- return {
37
- ...base,
38
- mode: "all",
39
- };
40
- }
41
-
42
- if (clauseNode.type === "none_clause") {
43
- return {
44
- ...base,
45
- mode: "none",
46
- };
47
- }
48
-
49
- if (
50
- clauseNode.type === "filter_name_clause" ||
51
- clauseNode.type === "filter_block_clause"
52
- ) {
53
- const filterNameNode = clauseNode.childForFieldName("filter_name");
54
-
55
- return {
56
- ...base,
57
- mode: "filter",
58
- filterName: isPresentNode(filterNameNode)
59
- ? textOf(filterNameNode, source)
60
- : undefined,
61
- filterNameRange: isPresentNode(filterNameNode)
62
- ? toRange(filterNameNode, source)
63
- : undefined,
64
- };
65
- }
66
-
67
- if (clauseNode.type === "where_clause") {
68
- const whereExpressionNode =
69
- clauseNode.childForFieldName("where_expression");
70
-
71
- return {
72
- ...base,
73
- mode: "where",
74
- whereExpression: isPresentNode(whereExpressionNode)
75
- ? textOf(whereExpressionNode, source)
76
- : undefined,
77
- whereExpressionRange: isPresentNode(whereExpressionNode)
78
- ? toRange(whereExpressionNode, source)
79
- : undefined,
80
- clauseText: textOf(clauseNode, source),
81
- };
82
- }
83
-
84
- const clauseText = textOf(clauseNode, source).trim();
85
- const lowered = clauseText.toLowerCase();
86
-
87
- if (lowered === "none" || lowered.startsWith("none ")) {
88
- return {
89
- ...base,
90
- mode: "none",
91
- clauseText,
92
- };
93
- }
94
-
95
- if (lowered.startsWith("where ")) {
96
- return {
97
- ...base,
98
- mode: "where",
99
- whereExpression: clauseText.slice("where ".length).trim(),
100
- clauseText,
101
- };
102
- }
103
-
104
- if (lowered.startsWith("filter ")) {
105
- const maybeName = clauseText.slice("filter ".length).trim();
106
- return {
107
- ...base,
108
- mode: "filter",
109
- filterName:
110
- maybeName.length > 0 && !maybeName.startsWith("{")
111
- ? maybeName
112
- : undefined,
113
- clauseText,
114
- };
115
- }
116
-
117
- return {
118
- ...base,
119
- mode: "other",
120
- clauseText,
121
- };
122
- };
123
-
124
- const parseChannelEntries = (
125
- channelBodyNode: SyntaxNode,
126
- source: string,
127
- ): ChannelEntry[] => {
128
- const entries: ChannelEntry[] = [];
129
- const namedChildren = channelBodyNode.namedChildren;
130
-
131
- for (let index = 0; index < namedChildren.length; index += 1) {
132
- const entryNode = namedChildren[index];
133
- if (!entryNode) {
134
- continue;
135
- }
136
-
137
- const entryRange = toRange(entryNode, source);
138
-
139
- if (entryNode.type === "channel_table_statement") {
140
- const tableNameNode = entryNode.childForFieldName("table_name");
141
- entries.push({
142
- kind: "table",
143
- tableName: isPresentNode(tableNameNode)
144
- ? textOf(tableNameNode, source)
145
- : "",
146
- tableNameRange: isPresentNode(tableNameNode)
147
- ? toRange(tableNameNode, source)
148
- : entryRange,
149
- ...entryRange,
150
- });
151
- continue;
152
- }
153
-
154
- if (
155
- entryNode.type === "identifier" &&
156
- textOf(entryNode, source).toLowerCase() === "table" &&
157
- namedChildren[index + 1]?.type === "identifier"
158
- ) {
159
- const tableNameNode = namedChildren[index + 1];
160
- const tableRange = mergeRanges(
161
- entryRange,
162
- toRange(tableNameNode, source),
163
- );
164
- entries.push({
165
- kind: "table",
166
- tableName: textOf(tableNameNode, source),
167
- tableNameRange: toRange(tableNameNode, source),
168
- ...tableRange,
169
- });
170
- index += 1;
171
- continue;
172
- }
173
-
174
- if (
175
- entryNode.type === "import_statement" ||
176
- entryNode.type === "export_statement"
177
- ) {
178
- const statement = parseImportExportNode(entryNode, source);
179
- const clauseText = statement.clauseText?.toLowerCase() ?? "";
180
-
181
- if (
182
- statement.mode === "other" &&
183
- (clauseText.startsWith("limit ") ||
184
- clauseText.startsWith("keep filtered "))
185
- ) {
186
- if (clauseText.startsWith("keep filtered ")) {
187
- entries.push({
188
- kind: "keep-filtered",
189
- value: (statement.clauseText ?? "")
190
- .slice("keep filtered ".length)
191
- .trim(),
192
- valueRange: entryRange,
193
- ...entryRange,
194
- });
195
- } else {
196
- const payload = (statement.clauseText ?? "")
197
- .slice("limit ".length)
198
- .trim();
199
- const actionMarker = " action ";
200
- const actionIndex = payload.toLowerCase().indexOf(actionMarker);
201
- const limitValue =
202
- actionIndex === -1 ? payload : payload.slice(0, actionIndex).trim();
203
- const limitAction =
204
- actionIndex === -1
205
- ? undefined
206
- : payload.slice(actionIndex + actionMarker.length).trim();
207
-
208
- entries.push({
209
- kind: "limit",
210
- direction: statement.kind === "export" ? "export" : "import",
211
- value: limitValue,
212
- valueRange: entryRange,
213
- action: limitAction,
214
- actionRange: limitAction ? entryRange : undefined,
215
- ...entryRange,
216
- });
217
- }
218
- continue;
219
- }
220
-
221
- if (statement.kind === "import") {
222
- entries.push({
223
- kind: "import",
224
- mode: statement.mode,
225
- filterName: statement.filterName,
226
- filterNameRange: statement.filterNameRange,
227
- whereExpression: statement.whereExpression,
228
- whereExpressionRange: statement.whereExpressionRange,
229
- clauseText: statement.clauseText,
230
- ...entryRange,
231
- });
232
- } else {
233
- entries.push({
234
- kind: "export",
235
- mode: statement.mode,
236
- filterName: statement.filterName,
237
- filterNameRange: statement.filterNameRange,
238
- whereExpression: statement.whereExpression,
239
- whereExpressionRange: statement.whereExpressionRange,
240
- clauseText: statement.clauseText,
241
- ...entryRange,
242
- });
243
- }
244
- continue;
245
- }
246
-
247
- if (entryNode.type === "channel_limit_statement") {
248
- const directionNode = entryNode.childForFieldName("direction");
249
- const limitValueNode = entryNode.childForFieldName("limit_value");
250
- const limitActionNode = entryNode.childForFieldName("limit_action");
251
-
252
- const directionText = isPresentNode(directionNode)
253
- ? textOf(directionNode, source).toLowerCase()
254
- : "import";
255
- const direction = CHANNEL_DIRECTIONS.has(directionText)
256
- ? (directionText as "import" | "receive" | "export")
257
- : "import";
258
-
259
- entries.push({
260
- kind: "limit",
261
- direction,
262
- value: isPresentNode(limitValueNode)
263
- ? textOf(limitValueNode, source)
264
- : "",
265
- valueRange: isPresentNode(limitValueNode)
266
- ? toRange(limitValueNode, source)
267
- : entryRange,
268
- action: isPresentNode(limitActionNode)
269
- ? textOf(limitActionNode, source)
270
- : undefined,
271
- actionRange: isPresentNode(limitActionNode)
272
- ? toRange(limitActionNode, source)
273
- : undefined,
274
- ...entryRange,
275
- });
276
- continue;
277
- }
278
-
279
- if (entryNode.type === "channel_debug_statement") {
280
- const debugClauseNode = entryNode.childForFieldName("debug_clause");
281
- entries.push({
282
- kind: "debug",
283
- clauseText: isPresentNode(debugClauseNode)
284
- ? textOf(debugClauseNode, source)
285
- : textOf(entryNode, source),
286
- ...entryRange,
287
- });
288
- continue;
289
- }
290
-
291
- if (
292
- entryNode.type === "identifier" &&
293
- textOf(entryNode, source).toLowerCase() === "debug" &&
294
- namedChildren[index + 1]
295
- ) {
296
- const clauseNode = namedChildren[index + 1];
297
- const debugRange = mergeRanges(entryRange, toRange(clauseNode, source));
298
- entries.push({
299
- kind: "debug",
300
- clauseText: textOf(clauseNode, source),
301
- ...debugRange,
302
- });
303
- index += 1;
304
- continue;
305
- }
306
-
307
- if (entryNode.type === "channel_keep_filtered_statement") {
308
- const switchValueNode = entryNode.childForFieldName("switch_value");
309
- entries.push({
310
- kind: "keep-filtered",
311
- value: isPresentNode(switchValueNode)
312
- ? textOf(switchValueNode, source)
313
- : "",
314
- valueRange: isPresentNode(switchValueNode)
315
- ? toRange(switchValueNode, source)
316
- : entryRange,
317
- ...entryRange,
318
- });
319
- continue;
320
- }
321
-
322
- entries.push({
323
- kind: "other",
324
- text: textOf(entryNode, source),
325
- ...entryRange,
326
- });
327
- }
328
-
329
- return entries;
330
- };
331
-
332
- export const parseProtocolStatements = (
333
- blockNode: SyntaxNode,
334
- source: string,
335
- issues: ParseIssue[],
336
- ): ProtocolStatement[] => {
337
- const statements: ProtocolStatement[] = [];
338
- const nodes = protocolStatementNodesOf(blockNode);
339
- const childNodes = blockNode.namedChildren;
340
- const fallbackChannelIndices = new Set<number>();
341
-
342
- for (const statementNode of nodes) {
343
- const statementRange = toRange(statementNode, source);
344
-
345
- if (statementNode.type === "local_as_statement") {
346
- const asnNode = statementNode.childForFieldName("asn");
347
- if (!isPresentNode(asnNode)) {
348
- pushMissingFieldIssue(
349
- issues,
350
- statementNode,
351
- "Missing ASN in local as statement",
352
- source,
353
- );
354
- }
355
-
356
- statements.push({
357
- kind: "local-as",
358
- asn: isPresentNode(asnNode) ? textOf(asnNode, source) : "",
359
- asnRange: isPresentNode(asnNode)
360
- ? toRange(asnNode, source)
361
- : statementRange,
362
- ...statementRange,
363
- });
364
- continue;
365
- }
366
-
367
- if (statementNode.type === "neighbor_statement") {
368
- const addressNode = statementNode.childForFieldName("address");
369
- const asnNode = statementNode.childForFieldName("asn");
370
-
371
- if (!isPresentNode(addressNode)) {
372
- pushMissingFieldIssue(
373
- issues,
374
- statementNode,
375
- "Missing neighbor address",
376
- source,
377
- );
378
- }
379
-
380
- const addressText = isPresentNode(addressNode)
381
- ? textOf(addressNode, source)
382
- : "";
383
- const addressKind =
384
- isPresentNode(addressNode) && isIpLiteralCandidate(addressText)
385
- ? "ip"
386
- : "other";
387
-
388
- statements.push({
389
- kind: "neighbor",
390
- address: addressText,
391
- addressRange: isPresentNode(addressNode)
392
- ? toRange(addressNode, source)
393
- : statementRange,
394
- addressKind,
395
- asn: isPresentNode(asnNode) ? textOf(asnNode, source) : undefined,
396
- asnRange: isPresentNode(asnNode) ? toRange(asnNode, source) : undefined,
397
- ...statementRange,
398
- });
399
- continue;
400
- }
401
-
402
- if (
403
- statementNode.type === "import_statement" ||
404
- statementNode.type === "export_statement"
405
- ) {
406
- statements.push(parseImportExportNode(statementNode, source));
407
- continue;
408
- }
409
-
410
- if (statementNode.type === "channel_statement") {
411
- const channelTypeNode = statementNode.childForFieldName("channel_type");
412
- const channelBodyNode = statementNode.childForFieldName("body");
413
- const channelTypeText = isPresentNode(channelTypeNode)
414
- ? textOf(channelTypeNode, source)
415
- : "";
416
-
417
- statements.push({
418
- kind: "channel",
419
- channelType: normalizeChannelType(channelTypeText),
420
- channelTypeRange: isPresentNode(channelTypeNode)
421
- ? toRange(channelTypeNode, source)
422
- : statementRange,
423
- entries: isPresentNode(channelBodyNode)
424
- ? parseChannelEntries(channelBodyNode, source)
425
- : [],
426
- ...statementRange,
427
- });
428
- continue;
429
- }
430
-
431
- if (statementNode.type === "expression_statement") {
432
- statements.push({
433
- kind: "other",
434
- text: textOf(statementNode, source),
435
- ...statementRange,
436
- });
437
- continue;
438
- }
439
- }
440
-
441
- for (let index = 0; index < childNodes.length - 1; index += 1) {
442
- const maybeChannelTypeNode = childNodes[index];
443
- const maybeChannelBodyNode = childNodes[index + 1];
444
- if (!maybeChannelTypeNode || !maybeChannelBodyNode) {
445
- continue;
446
- }
447
-
448
- if (
449
- maybeChannelTypeNode.type !== "identifier" ||
450
- maybeChannelBodyNode.type !== "block"
451
- ) {
452
- continue;
453
- }
454
-
455
- const channelTypeText = textOf(maybeChannelTypeNode, source);
456
- const channelType = normalizeChannelType(channelTypeText);
457
- if (channelType === "unknown") {
458
- continue;
459
- }
460
-
461
- const channelRange = mergeRanges(
462
- toRange(maybeChannelTypeNode, source),
463
- toRange(maybeChannelBodyNode, source),
464
- );
465
-
466
- statements.push({
467
- kind: "channel",
468
- channelType,
469
- channelTypeRange: toRange(maybeChannelTypeNode, source),
470
- entries: parseChannelEntries(maybeChannelBodyNode, source),
471
- ...channelRange,
472
- });
473
-
474
- fallbackChannelIndices.add(index);
475
- fallbackChannelIndices.add(index + 1);
476
- index += 1;
477
- }
478
-
479
- for (let index = 0; index < childNodes.length; index += 1) {
480
- const currentNode = childNodes[index];
481
-
482
- if (
483
- PROTOCOL_STATEMENT_TYPES.has(currentNode.type) ||
484
- fallbackChannelIndices.has(index)
485
- ) {
486
- continue;
487
- }
488
-
489
- let endIndex = index;
490
-
491
- while (endIndex + 1 < childNodes.length) {
492
- const nextNode = childNodes[endIndex + 1];
493
- if (
494
- PROTOCOL_STATEMENT_TYPES.has(nextNode.type) ||
495
- fallbackChannelIndices.has(endIndex + 1)
496
- ) {
497
- break;
498
- }
499
-
500
- endIndex += 1;
501
- }
502
-
503
- const lastNode = childNodes[endIndex];
504
- const text = source.slice(currentNode.startIndex, lastNode.endIndex).trim();
505
-
506
- if (text.length > 0) {
507
- statements.push({
508
- kind: "other",
509
- text,
510
- ...mergeRanges(toRange(currentNode, source), toRange(lastNode, source)),
511
- });
512
- }
513
-
514
- index = endIndex;
515
- }
516
-
517
- return statements;
518
- };
519
-
520
- export const parseProtocolDeclaration = (
521
- declarationNode: SyntaxNode,
522
- source: string,
523
- issues: ParseIssue[],
524
- ): ProtocolDeclaration => {
525
- const declarationRange = toRange(declarationNode, source);
526
- const protocolTypeNode = declarationNode.childForFieldName("protocol_type");
527
- const protocolVariantNode =
528
- declarationNode.childForFieldName("protocol_variant");
529
- const nameNode = declarationNode.childForFieldName("name");
530
- const fromTemplateNode = declarationNode.childForFieldName("from_template");
531
- const bodyNode = declarationNode.childForFieldName("body");
532
- const hasFromKeyword = declarationNode.children.some(
533
- (entry) => entry.type === "from",
534
- );
535
-
536
- if (!isPresentNode(protocolTypeNode)) {
537
- pushMissingFieldIssue(
538
- issues,
539
- declarationNode,
540
- "Missing protocol type for protocol declaration",
541
- source,
542
- );
543
- }
544
-
545
- if (!isPresentNode(nameNode)) {
546
- pushMissingFieldIssue(
547
- issues,
548
- declarationNode,
549
- "Missing name for protocol declaration",
550
- source,
551
- );
552
- }
553
-
554
- if (hasFromKeyword && !isPresentNode(fromTemplateNode)) {
555
- pushMissingFieldIssue(
556
- issues,
557
- declarationNode,
558
- "Missing template name after from clause",
559
- source,
560
- );
561
- }
562
-
563
- if (!isPresentNode(bodyNode)) {
564
- issues.push({
565
- code: "syntax/unbalanced-brace",
566
- message: "Missing '{' for protocol declaration",
567
- ...declarationRange,
568
- });
569
- }
570
-
571
- const { protocolType, protocolTypeRange } = protocolTypeTextAndRange(
572
- protocolTypeNode,
573
- protocolVariantNode,
574
- source,
575
- declarationRange,
576
- );
577
-
578
- return {
579
- kind: "protocol",
580
- protocolType,
581
- protocolTypeRange,
582
- name: isPresentNode(nameNode) ? textOf(nameNode, source) : "",
583
- nameRange: isPresentNode(nameNode)
584
- ? toRange(nameNode, source)
585
- : declarationRange,
586
- fromTemplate: isPresentNode(fromTemplateNode)
587
- ? textOf(fromTemplateNode, source)
588
- : undefined,
589
- fromTemplateRange: isPresentNode(fromTemplateNode)
590
- ? toRange(fromTemplateNode, source)
591
- : undefined,
592
- statements: isPresentNode(bodyNode)
593
- ? parseProtocolStatements(bodyNode, source, issues)
594
- : [],
595
- ...declarationRange,
596
- };
597
- };