gobject_ast/parser/
mod.rs1mod expression;
2mod gobject;
3mod statement;
4mod top_level;
5
6use std::{
7 fs,
8 path::{Path, PathBuf},
9 sync::Arc,
10};
11
12use anyhow::{Context, Result};
13use ignore::WalkBuilder;
14use tree_sitter::{Node, Parser as TSParser};
15
16use crate::model::*;
17
18pub struct Parser {
19 parser: TSParser,
20 current_file: Option<std::path::PathBuf>,
21 current_source: Arc<Vec<u8>>,
22}
23
24impl Parser {
25 pub fn new() -> Result<Self> {
26 let mut parser = TSParser::new();
27 parser
28 .set_language(&tree_sitter_c_gobject::LANGUAGE.into())
29 .context("Failed to load C grammar")?;
30
31 Ok(Self {
32 parser,
33 current_file: None,
34 current_source: Arc::new(Vec::new()),
35 })
36 }
37
38 fn node_location(&self, node: Node) -> SourceLocation {
40 SourceLocation::new(
41 node.start_position().row + 1,
42 node.start_position().column + 1,
43 node.start_byte(),
44 node.end_byte(),
45 Arc::clone(&self.current_source),
46 )
47 }
48
49 fn is_expression_node(node: &Node) -> bool {
51 matches!(
52 node.kind(),
53 "call_expression"
54 | "g_allocation_call"
55 | "likelihood_call"
56 | "va_arg_expression"
57 | "assignment_expression"
58 | "binary_expression"
59 | "unary_expression"
60 | "pointer_expression"
61 | "parenthesized_expression"
62 | "identifier"
63 | "field_expression"
64 | "string_literal"
65 | "number_literal"
66 | "null"
67 | "NULL"
68 | "true"
69 | "TRUE"
70 | "false"
71 | "FALSE"
72 | "cast_expression"
73 | "conditional_expression"
74 | "sizeof_expression"
75 | "alignof_expression"
76 | "subscript_expression"
77 | "initializer_list"
78 | "char_literal"
79 | "update_expression"
80 | "concatenated_string"
81 | "compound_literal_expression"
82 | "comma_expression"
83 | "offsetof_expression"
84 | "gnu_asm_expression"
85 | "compound_statement"
86 | "comment"
87 | "objc_message_expr"
88 )
89 }
90
91 pub fn parse_directory(&mut self, path: &Path) -> Result<Project> {
92 let mut project = Project::new();
93
94 for entry in WalkBuilder::new(path)
97 .hidden(false)
98 .git_ignore(true)
99 .git_global(true)
100 .git_exclude(true)
101 .require_git(false)
102 .build()
103 .filter_map(std::result::Result::ok)
104 .filter(|e| {
105 e.path()
106 .extension()
107 .is_some_and(|ext| ext == "h" || ext == "c")
108 })
109 {
110 let (file_path, model) = self.parse_file_to_model(entry.path())?;
111 project.files.insert(file_path, model);
112 }
113
114 project.resolve_all_gobject_types();
115 Ok(project)
116 }
117
118 pub fn parse_file(&mut self, path: &Path) -> Result<Project> {
119 let (path, model) = self.parse_file_to_model(path)?;
120 let mut project = Project::new();
121 project.files.insert(path, model);
122 project.resolve_all_gobject_types();
123 Ok(project)
124 }
125
126 pub fn parse_file_to_model(&mut self, path: &Path) -> Result<(PathBuf, FileModel)> {
127 let _file_span = tracing::warn_span!("file", path = %path.display()).entered();
128 self.current_file = Some(path.to_path_buf());
129 let source = Arc::new(fs::read(path)?);
130 self.current_source = Arc::clone(&source);
131 let tree = self
132 .parser
133 .parse(source.as_slice(), None)
134 .context("Failed to parse file")?;
135
136 let mut file_model = FileModel::new(path.to_path_buf());
137
138 self.visit_node(tree.root_node(), source.as_slice(), &mut file_model);
139
140 file_model.source = source;
141
142 Ok((path.to_path_buf(), file_model))
143 }
144
145 fn find_export_macros_in_declaration(
146 &self,
147 decl_node: Node,
148 source: &[u8],
149 ) -> Vec<ExportMacro> {
150 let mut result = Vec::new();
151 let mut cursor = decl_node.walk();
152
153 for child in decl_node.children(&mut cursor) {
154 if child.kind() == "macro_modifier" {
155 let text = std::str::from_utf8(&source[child.byte_range()]).unwrap_or("");
156 result.push(ExportMacro::parse(text.trim()));
157 }
158 }
159
160 result
161 }
162
163 fn extract_gobject_from_macro_modifier(
166 &self,
167 node: Node,
168 source: &[u8],
169 ) -> Option<GObjectType> {
170 let export_macros: Vec<ExportMacro> = {
172 let mut cursor = node.walk();
173 node.children(&mut cursor)
174 .filter(|c| c.kind() == "gobject_export_macro")
175 .filter_map(|c| std::str::from_utf8(&source[c.byte_range()]).ok())
176 .map(|s| ExportMacro::parse(s.trim()))
177 .collect()
178 };
179
180 let full_text = std::str::from_utf8(&source[node.byte_range()]).ok()?;
182 let macro_name = full_text.split('(').next()?.trim();
183 let macro_name = macro_name.split_whitespace().last()?;
185
186 let mut gobject_type =
187 self.extract_gobject_from_identifier(node, node, source, macro_name)?;
188 gobject_type.export_macros = export_macros;
189 Some(gobject_type)
190 }
191
192 fn visit_node(&self, node: Node, source: &[u8], file_model: &mut FileModel) {
193 stacker::maybe_grow(32 * 1024, 1024 * 1024, || {
194 self.visit_node_inner(node, source, file_model);
195 });
196 }
197
198 fn visit_node_inner(&self, node: Node, source: &[u8], file_model: &mut FileModel) {
199 if let Some(item) = self.parse_top_level_item(node, source) {
203 if item.has_doc()
208 && let Some(prev) = node.prev_named_sibling()
209 && prev.kind() == "comment"
210 {
211 let prev_byte = prev.start_byte();
212 if let Some(last) = file_model.top_level_items.last()
213 && last.is_comment_at_byte(prev_byte)
214 {
215 file_model.top_level_items.pop();
216 }
217 }
218 file_model.top_level_items.push(item);
219 return;
220 }
221
222 if node.kind() == "ERROR" {
225 let mut cursor = node.walk();
226 for child in node.children(&mut cursor) {
227 self.visit_node(child, source, file_model);
228 }
229 return;
230 }
231
232 let mut cursor = node.walk();
235 for child in node.children(&mut cursor) {
236 self.visit_node(child, source, file_model);
237 }
238 }
239
240 fn extract_function_from_definition<'a>(
241 &self,
242 node: Node,
243 source: &'a [u8],
244 ) -> Option<(&'a str, bool, bool)> {
245 let func_text = std::str::from_utf8(&source[node.byte_range()]).ok()?;
246 let is_static = func_text.starts_with("static") || func_text.contains("\nstatic ");
247 let is_inline = func_text.contains("inline ");
248
249 let name = if let Some(declarator) = node.child_by_field_name("declarator") {
250 match self.extract_declarator_name(declarator, source) {
251 Some(n) if !n.is_empty() => n,
252 _ => self.extract_name_from_macro_type_specifier(node, source)?,
253 }
254 } else if let Some(func_decl) = self.find_function_declarator(node) {
255 self.extract_declarator_name(func_decl, source)?
256 } else {
257 self.extract_name_from_macro_type_specifier(node, source)?
258 };
259
260 Some((name, is_static, is_inline))
261 }
262
263 fn extract_name_from_macro_type_specifier<'a>(
268 &self,
269 node: Node,
270 source: &'a [u8],
271 ) -> Option<&'a str> {
272 let mut cursor = node.walk();
273 let macro_spec = node
274 .children(&mut cursor)
275 .find(|c| c.kind() == "macro_type_specifier")?;
276 let id = macro_spec.child(0).filter(|c| c.kind() == "identifier")?;
277 let name = std::str::from_utf8(&source[id.byte_range()]).ok()?;
278 if name.is_empty() { None } else { Some(name) }
279 }
280
281 pub(super) fn find_function_declarator<'a>(&self, node: Node<'a>) -> Option<Node<'a>> {
282 if node.kind() == "function_declarator" {
283 return Some(node);
284 }
285
286 if let Some(declarator) = node.child_by_field_name("declarator")
288 && let Some(found) = self.find_function_declarator(declarator)
289 {
290 return Some(found);
291 }
292
293 let mut cursor = node.walk();
295 for child in node.children(&mut cursor) {
296 if let Some(found) = self.find_function_declarator(child) {
297 return Some(found);
298 }
299 }
300
301 None
302 }
303
304 pub(super) fn extract_comment_text(
305 &self,
306 node: Node,
307 source: &[u8],
308 ) -> Option<(CommentKind, String)> {
309 let text = std::str::from_utf8(&source[node.byte_range()]).ok()?;
310
311 if text.starts_with("//") {
312 Some((CommentKind::Line, text.to_string()))
313 } else if text.starts_with("/*") && text.ends_with("*/") {
314 Some((CommentKind::Block, text.to_string()))
315 } else {
316 None
317 }
318 }
319}
320
321impl Default for Parser {
322 fn default() -> Self {
323 Self::new().expect("Failed to create parser")
324 }
325}