Skip to content

Commit fce5c3c

Browse files
committed
feat: add syntax parsing helpers
1 parent 952b573 commit fce5c3c

1 file changed

Lines changed: 147 additions & 0 deletions

File tree

src/analysis/syntax.ts

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { childForField } from "../cpp-ast.js";
2+
import type { SyntaxNode } from "tree-sitter";
3+
4+
export interface ParsedCall {
5+
readonly calleeName: string;
6+
readonly baseNode: SyntaxNode | undefined;
7+
readonly methodName: string | undefined;
8+
readonly arguments: readonly SyntaxNode[];
9+
}
10+
11+
export function parseCallExpressionNode(
12+
node: SyntaxNode,
13+
): ParsedCall | undefined {
14+
const functionNode = childForField(node, "function");
15+
const argumentListNode = node.namedChildren.find(
16+
(child) => child.type === "argument_list",
17+
);
18+
if (functionNode === undefined || argumentListNode === undefined) {
19+
return undefined;
20+
}
21+
22+
const argumentsList = [...argumentListNode.namedChildren];
23+
const fieldParts =
24+
functionNode.type === "field_expression"
25+
? parseFieldExpression(functionNode)
26+
: undefined;
27+
if (fieldParts !== undefined) {
28+
return {
29+
calleeName: functionNode.text,
30+
baseNode: fieldParts.baseNode,
31+
methodName: fieldParts.fieldName,
32+
arguments: argumentsList,
33+
};
34+
}
35+
36+
return {
37+
calleeName: functionNode.text,
38+
baseNode: undefined,
39+
methodName: undefined,
40+
arguments: argumentsList,
41+
};
42+
}
43+
44+
export function extractStringLiteral(value: string): string | undefined {
45+
const trimmedValue = value.trim();
46+
if (trimmedValue.startsWith('"') && trimmedValue.endsWith('"')) {
47+
return trimmedValue.slice(1, trimmedValue.length - 1);
48+
}
49+
return undefined;
50+
}
51+
52+
export function parseFieldExpression(node: SyntaxNode):
53+
| {
54+
readonly baseNode: SyntaxNode;
55+
readonly fieldName: string;
56+
readonly operator: string;
57+
}
58+
| undefined {
59+
if (node.type !== "field_expression") {
60+
return undefined;
61+
}
62+
63+
const baseNode = childForField(node, "argument");
64+
const fieldNode = childForField(node, "field");
65+
const operatorNode = childForField(node, "operator");
66+
if (
67+
baseNode === undefined ||
68+
fieldNode === undefined ||
69+
operatorNode === undefined
70+
) {
71+
return undefined;
72+
}
73+
74+
return {
75+
baseNode,
76+
fieldName: fieldNode.text,
77+
operator: operatorNode.text,
78+
};
79+
}
80+
81+
export function extractLoopAliasName(statement: SyntaxNode): string | undefined {
82+
return statement.namedChildren
83+
.filter((child) => child.type !== "compound_statement" && child.type !== "field_expression")
84+
.map((child) => extractDeclaratorName(child))
85+
.find((name): name is string => name !== undefined);
86+
}
87+
88+
export function extractSubscriptIndexNode(
89+
node: SyntaxNode,
90+
): SyntaxNode | undefined {
91+
const argListNode = node.namedChildren.find(
92+
(child) => child.type === "subscript_argument_list",
93+
);
94+
return argListNode?.namedChildren[0];
95+
}
96+
97+
export function extractIdentifierName(node: SyntaxNode): string | undefined {
98+
const normalizedNode = unwrapExpressionNode(node);
99+
if (normalizedNode.type === "identifier") {
100+
return normalizedNode.text;
101+
}
102+
return undefined;
103+
}
104+
105+
export function extractDeclaratorName(
106+
node: SyntaxNode | undefined,
107+
): string | undefined {
108+
if (node === undefined) {
109+
return undefined;
110+
}
111+
112+
if (node.type === "identifier" || node.type === "field_identifier") {
113+
return node.text;
114+
}
115+
116+
const nestedDeclarator = childForField(node, "declarator");
117+
if (nestedDeclarator !== undefined) {
118+
return extractDeclaratorName(nestedDeclarator);
119+
}
120+
121+
for (const child of node.namedChildren) {
122+
const name = extractDeclaratorName(child);
123+
if (name !== undefined) {
124+
return name;
125+
}
126+
}
127+
128+
return undefined;
129+
}
130+
131+
export function unwrapExpressionNode(node: SyntaxNode): SyntaxNode {
132+
let current = node;
133+
while (
134+
(current.type === "parenthesized_expression" ||
135+
current.type === "reference_expression" ||
136+
current.type === "qualified_identifier") &&
137+
current.namedChildren.length > 0
138+
) {
139+
current =
140+
current.namedChildren[current.namedChildren.length - 1] ?? current;
141+
}
142+
return current;
143+
}
144+
145+
export function isConstantIdentifier(value: string): boolean {
146+
return value.length > 0 && /^[A-Z0-9_]+$/.test(value);
147+
}

0 commit comments

Comments
 (0)