Skip to main content

gobject_ast/parser/
mod.rs

1mod 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    /// Helper to create SourceLocation from a tree-sitter Node
36    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    /// Check if a tree-sitter node is an expression
46    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        // Parse all files (.h and .c)
87        // WalkBuilder respects .gitignore by default
88        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        // With the fixed grammar, macro_modifier nodes are now properly parsed
143        // Just walk children and extract macro_modifier nodes
144        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    /// Extract GObject type from a gobject_type_macro or macro_modifier node
157    /// The grammar now properly parses argument_list with identifier children
158    fn extract_gobject_from_macro_modifier(
159        &self,
160        node: Node,
161        source: &[u8],
162    ) -> Option<GObjectType> {
163        // Collect export macros from gobject_export_macro children
164        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        // Get macro name from the text (before parentheses)
175        let full_text = std::str::from_utf8(&source[node.byte_range()]).ok()?;
176        let macro_name = full_text.split('(').next()?.trim();
177        // Strip any leading export macro prefix to get the actual macro name
178        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        // Try to parse this node as a top-level item. If successful, don't
188        // recurse — children are handled inside parse_top_level_item itself
189        // (e.g. via parse_conditional_body for #ifdef blocks).
190        if let Some(item) = self.parse_top_level_item(node, source) {
191            file_model.top_level_items.push(item);
192            return;
193        }
194
195        // Only recurse when the node wasn't recognized as a top-level item
196        // (translation_unit, unrecognized wrapper nodes, etc.)
197        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        // For pointer/abstract declarators, look in the declarator field
224        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        // Recursively search children
231        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}