gobject_ast/parser/
mod.rs1mod expression;
2mod gobject;
3mod statement;
4mod top_level;
5
6use std::{
7 fs,
8 path::{Path, PathBuf},
9};
10
11use anyhow::{Context, Result};
12use ignore::WalkBuilder;
13use tree_sitter::{Node, Parser as TSParser};
14
15use crate::model::*;
16
17pub struct Parser {
18 parser: TSParser,
19 current_file: Option<std::path::PathBuf>,
20}
21
22impl Parser {
23 pub fn new() -> Result<Self> {
24 let mut parser = TSParser::new();
25 parser
26 .set_language(&tree_sitter_c_gobject::LANGUAGE.into())
27 .context("Failed to load C grammar")?;
28
29 Ok(Self {
30 parser,
31 current_file: None,
32 })
33 }
34
35 fn node_location(&self, node: Node) -> SourceLocation {
37 SourceLocation::new(
38 node.start_position().row + 1,
39 node.start_position().column + 1,
40 node.start_byte(),
41 node.end_byte(),
42 )
43 }
44
45 fn is_expression_node(node: &Node) -> bool {
47 matches!(
48 node.kind(),
49 "call_expression"
50 | "assignment_expression"
51 | "binary_expression"
52 | "unary_expression"
53 | "pointer_expression"
54 | "parenthesized_expression"
55 | "identifier"
56 | "field_expression"
57 | "string_literal"
58 | "number_literal"
59 | "null"
60 | "NULL"
61 | "true"
62 | "TRUE"
63 | "false"
64 | "FALSE"
65 | "cast_expression"
66 | "conditional_expression"
67 | "sizeof_expression"
68 | "alignof_expression"
69 | "subscript_expression"
70 | "initializer_list"
71 | "char_literal"
72 | "update_expression"
73 | "concatenated_string"
74 | "compound_literal_expression"
75 | "comma_expression"
76 | "offsetof_expression"
77 | "gnu_asm_expression"
78 | "compound_statement"
79 | "comment"
80 )
81 }
82
83 pub fn parse_directory(&mut self, path: &Path) -> Result<Project> {
84 let mut project = Project::new();
85
86 for entry in WalkBuilder::new(path)
89 .hidden(false)
90 .git_ignore(true)
91 .git_global(true)
92 .git_exclude(true)
93 .require_git(false)
94 .build()
95 .filter_map(std::result::Result::ok)
96 .filter(|e| {
97 e.path()
98 .extension()
99 .is_some_and(|ext| ext == "h" || ext == "c")
100 })
101 {
102 let (file_path, model) = self.parse_file_to_model(entry.path())?;
103 project.files.insert(file_path, model);
104 }
105
106 Ok(project)
107 }
108
109 pub fn parse_file(&mut self, path: &Path) -> Result<Project> {
110 let (path, model) = self.parse_file_to_model(path)?;
111 let mut project = Project::new();
112 project.files.insert(path, model);
113 Ok(project)
114 }
115
116 pub fn parse_file_to_model(&mut self, path: &Path) -> Result<(PathBuf, FileModel)> {
117 let _file_span = tracing::warn_span!("file", path = %path.display()).entered();
118 self.current_file = Some(path.to_path_buf());
119 let source = fs::read(path)?;
120 let tree = self
121 .parser
122 .parse(&source, None)
123 .context("Failed to parse file")?;
124
125 let mut file_model = FileModel::new(path.to_path_buf());
126
127 self.visit_node(tree.root_node(), &source, &mut file_model);
128
129 file_model.source = source;
130 file_model.resolve_gobject_types();
131
132 Ok((path.to_path_buf(), file_model))
133 }
134
135 fn find_export_macros_in_declaration<'a>(
136 &self,
137 decl_node: Node,
138 source: &'a [u8],
139 ) -> Vec<&'a str> {
140 let mut result = Vec::new();
141
142 let mut cursor = decl_node.walk();
145
146 for child in decl_node.children(&mut cursor) {
147 if child.kind() == "macro_modifier" {
148 let text = std::str::from_utf8(&source[child.byte_range()]).unwrap_or("");
149 result.push(text.trim());
150 }
151 }
152
153 result
154 }
155
156 fn extract_gobject_from_macro_modifier(
159 &self,
160 node: Node,
161 source: &[u8],
162 ) -> Option<GObjectType> {
163 let export_macros: Vec<String> = {
165 let mut cursor = node.walk();
166 node.children(&mut cursor)
167 .filter(|c| c.kind() == "gobject_export_macro")
168 .filter_map(|c| std::str::from_utf8(&source[c.byte_range()]).ok())
169 .map(|s| s.trim().to_owned())
170 .filter(|s| !s.is_empty())
171 .collect()
172 };
173
174 let full_text = std::str::from_utf8(&source[node.byte_range()]).ok()?;
176 let macro_name = full_text.split('(').next()?.trim();
177 let macro_name = macro_name.split_whitespace().last()?;
179
180 let mut gobject_type =
181 self.extract_gobject_from_identifier(node, node, source, macro_name)?;
182 gobject_type.export_macros = export_macros;
183 Some(gobject_type)
184 }
185
186 fn visit_node(&self, node: Node, source: &[u8], file_model: &mut FileModel) {
187 if let Some(item) = self.parse_top_level_item(node, source) {
191 file_model.top_level_items.push(item);
192 return;
193 }
194
195 let mut cursor = node.walk();
198 for child in node.children(&mut cursor) {
199 self.visit_node(child, source, file_model);
200 }
201 }
202
203 fn extract_function_from_definition<'a>(
204 &self,
205 node: Node,
206 source: &'a [u8],
207 ) -> Option<(&'a str, bool, bool)> {
208 let func_text = std::str::from_utf8(&source[node.byte_range()]).ok()?;
209 let is_static = func_text.starts_with("static") || func_text.contains("\nstatic ");
210 let is_inline = func_text.contains("inline ");
211
212 let declarator = node.child_by_field_name("declarator")?;
213 let name = self.extract_declarator_name(declarator, source)?;
214
215 Some((name, is_static, is_inline))
216 }
217
218 pub(super) fn find_function_declarator<'a>(&self, node: Node<'a>) -> Option<Node<'a>> {
219 if node.kind() == "function_declarator" {
220 return Some(node);
221 }
222
223 if let Some(declarator) = node.child_by_field_name("declarator")
225 && let Some(found) = self.find_function_declarator(declarator)
226 {
227 return Some(found);
228 }
229
230 let mut cursor = node.walk();
232 for child in node.children(&mut cursor) {
233 if let Some(found) = self.find_function_declarator(child) {
234 return Some(found);
235 }
236 }
237
238 None
239 }
240
241 pub(super) fn extract_comment_text(
242 &self,
243 node: Node,
244 source: &[u8],
245 ) -> Option<(CommentKind, String)> {
246 let text = std::str::from_utf8(&source[node.byte_range()]).ok()?;
247
248 if text.starts_with("//") {
249 Some((CommentKind::Line, text.to_string()))
250 } else if text.starts_with("/*") && text.ends_with("*/") {
251 Some((CommentKind::Block, text.to_string()))
252 } else {
253 None
254 }
255 }
256}
257
258impl Default for Parser {
259 fn default() -> Self {
260 Self::new().expect("Failed to create parser")
261 }
262}