Skip to main content

gobject_ast/model/
top_level.rs

1use clap::ValueEnum;
2use serde::{Serialize, Serializer, ser::SerializeMap};
3
4use crate::model::{
5    Comment, Expression, FunctionDeclItem, FunctionDefItem, GObjectType, SourceLocation, Statement,
6    TypeDefItem, VariableDecl,
7};
8
9/// Coarse kind of a top-level item, useful for filtering without
10/// pattern-matching.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, ValueEnum)]
12#[serde(rename_all = "snake_case")]
13pub enum TopLevelItemKind {
14    FunctionDefinition,
15    FunctionDeclaration,
16    Typedef,
17    Struct,
18    Enum,
19    Include,
20    Define,
21    GObjectType,
22    Conditional,
23    GObjectDeclsBlock,
24    Declaration,
25    Expression,
26    Other,
27}
28
29/// Represents a top-level item in a C file
30#[derive(Debug, Clone)]
31pub enum TopLevelItem {
32    /// Preprocessor directive (#define, #include, etc.)
33    Preprocessor(PreprocessorDirective),
34    /// Type definition (typedef, enum, struct)
35    TypeDefinition(TypeDefItem),
36    /// Function declaration (forward declaration)
37    FunctionDeclaration(FunctionDeclItem),
38    /// Function definition (with body)
39    FunctionDefinition(FunctionDefItem),
40    /// Standalone variable declaration
41    Declaration(Box<VariableDecl>),
42    /// Standalone expression statement
43    Expression(Box<Expression>),
44    /// Standalone GTK-Doc comment not attached to any declaration
45    Comment(Comment),
46}
47
48impl TopLevelItem {
49    /// The name associated with this item, if any.
50    pub fn name(&self) -> Option<&str> {
51        match self {
52            Self::FunctionDefinition(f) => Some(&f.name),
53            Self::FunctionDeclaration(f) => Some(&f.name),
54            Self::TypeDefinition(td) => match td {
55                TypeDefItem::Typedef { name, .. } | TypeDefItem::Struct { name, .. } => Some(name),
56                TypeDefItem::Enum(enum_info) => enum_info.name.as_deref(),
57            },
58            Self::Preprocessor(PreprocessorDirective::Include { path, .. }) => Some(path),
59            Self::Preprocessor(PreprocessorDirective::Define { name, .. }) => Some(name),
60            Self::Preprocessor(PreprocessorDirective::GObjectType(gobject_type)) => {
61                Some(&gobject_type.type_name)
62            }
63            Self::Declaration(decl) => Some(&decl.name),
64            Self::Comment(_) => None,
65            _ => None,
66        }
67    }
68
69    /// The coarse kind of this item.
70    pub fn kind(&self) -> TopLevelItemKind {
71        match self {
72            Self::FunctionDefinition(_) => TopLevelItemKind::FunctionDefinition,
73            Self::FunctionDeclaration(_) => TopLevelItemKind::FunctionDeclaration,
74            Self::TypeDefinition(TypeDefItem::Typedef { .. }) => TopLevelItemKind::Typedef,
75            Self::TypeDefinition(TypeDefItem::Struct { .. }) => TopLevelItemKind::Struct,
76            Self::TypeDefinition(TypeDefItem::Enum(_)) => TopLevelItemKind::Enum,
77            Self::Preprocessor(PreprocessorDirective::Include { .. }) => TopLevelItemKind::Include,
78            Self::Preprocessor(PreprocessorDirective::Define { .. }) => TopLevelItemKind::Define,
79            Self::Preprocessor(PreprocessorDirective::GObjectType { .. }) => {
80                TopLevelItemKind::GObjectType
81            }
82            Self::Preprocessor(PreprocessorDirective::Conditional { .. }) => {
83                TopLevelItemKind::Conditional
84            }
85            Self::Preprocessor(PreprocessorDirective::GObjectDeclsBlock { .. }) => {
86                TopLevelItemKind::GObjectDeclsBlock
87            }
88            Self::Declaration(_) => TopLevelItemKind::Declaration,
89            Self::Expression(_) => TopLevelItemKind::Expression,
90            Self::Comment(_) => TopLevelItemKind::Other,
91            Self::Preprocessor(_) => TopLevelItemKind::Other,
92        }
93    }
94
95    pub fn has_doc(&self) -> bool {
96        match self {
97            Self::FunctionDefinition(f) => f.doc.is_some(),
98            Self::FunctionDeclaration(f) => f.doc.is_some(),
99            Self::TypeDefinition(td) => match td {
100                TypeDefItem::Struct { doc, .. } | TypeDefItem::Typedef { doc, .. } => doc.is_some(),
101                TypeDefItem::Enum(e) => e.doc.is_some(),
102            },
103            _ => false,
104        }
105    }
106
107    pub(crate) fn is_comment_at_byte(&self, byte: usize) -> bool {
108        matches!(self, Self::Comment(c) if c.location.start_byte == byte)
109    }
110}
111
112impl Serialize for TopLevelItem {
113    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
114        match self {
115            Self::Preprocessor(directive) => directive.serialize(s),
116            Self::TypeDefinition(v) => {
117                let mut m = s.serialize_map(Some(1))?;
118                m.serialize_entry("type_definition", v)?;
119                m.end()
120            }
121            Self::FunctionDeclaration(v) => {
122                let mut m = s.serialize_map(Some(1))?;
123                m.serialize_entry("function_declaration", v)?;
124                m.end()
125            }
126            Self::FunctionDefinition(v) => {
127                let mut m = s.serialize_map(Some(1))?;
128                m.serialize_entry("function_definition", v)?;
129                m.end()
130            }
131            Self::Declaration(v) => {
132                let mut m = s.serialize_map(Some(1))?;
133                m.serialize_entry("declaration", v)?;
134                m.end()
135            }
136            Self::Expression(v) => {
137                let mut m = s.serialize_map(Some(1))?;
138                m.serialize_entry("expression", v)?;
139                m.end()
140            }
141            Self::Comment(v) => {
142                let mut m = s.serialize_map(Some(1))?;
143                m.serialize_entry("comment", v)?;
144                m.end()
145            }
146        }
147    }
148}
149
150#[derive(Debug, Clone, Serialize)]
151#[serde(rename_all = "snake_case")]
152pub enum PragmaKind {
153    /// #pragma once
154    Once,
155    /// #pragma GCC/clang diagnostic push
156    DiagnosticPush,
157    /// #pragma GCC/clang diagnostic pop
158    DiagnosticPop,
159    /// #pragma GCC/clang diagnostic ignored "-Wwarning-name"
160    DiagnosticIgnored { warning: String },
161    /// Other pragma directive
162    Other {
163        name: String,
164        arguments: Option<String>,
165    },
166}
167
168#[derive(Debug, Clone, Serialize)]
169#[serde(rename_all = "snake_case")]
170pub enum PreprocessorDirective {
171    Include {
172        path: String,
173        is_system: bool,
174        location: SourceLocation,
175    },
176    Define {
177        name: String,
178        value: Option<DefineValue>,
179        location: SourceLocation,
180    },
181    Call {
182        directive: String,
183        location: SourceLocation,
184    },
185    Pragma {
186        kind: PragmaKind,
187        location: SourceLocation,
188    },
189    /// GObject type declaration/definition (G_DECLARE_*, G_DEFINE_*)
190    GObjectType(Box<GObjectType>),
191    /// G_DEFINE_AUTOPTR_CLEANUP_FUNC (Type, cleanup_func)
192    AutoptrCleanupFunc {
193        type_name: String,
194        cleanup_function: String,
195        location: SourceLocation,
196    },
197    /// G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (Type, cleanup_func)
198    AutoCleanupClearFunc {
199        type_name: String,
200        cleanup_function: String,
201        location: SourceLocation,
202    },
203    /// Macro call with code block (e.g., G_DEFINE_BOXED_TYPE_WITH_CODE)
204    /// Contains the macro name and parsed statements from the code block
205    MacroWithCode {
206        macro_name: String,
207        arguments: Vec<String>,
208        code_statements: Vec<Statement>,
209        location: SourceLocation,
210    },
211    Conditional {
212        kind: ConditionalKind,
213        condition: Option<String>,
214        body: Vec<TopLevelItem>,
215        location: SourceLocation,
216    },
217    /// G_BEGIN_DECLS ... G_END_DECLS block
218    GObjectDeclsBlock {
219        body: Vec<TopLevelItem>,
220        location: SourceLocation,
221    },
222}
223
224impl PreprocessorDirective {
225    pub fn location(&self) -> &SourceLocation {
226        match self {
227            Self::Include { location, .. }
228            | Self::Define { location, .. }
229            | Self::Call { location, .. }
230            | Self::Pragma { location, .. }
231            | Self::AutoptrCleanupFunc { location, .. }
232            | Self::AutoCleanupClearFunc { location, .. }
233            | Self::MacroWithCode { location, .. }
234            | Self::Conditional { location, .. }
235            | Self::GObjectDeclsBlock { location, .. } => location,
236            Self::GObjectType(gt) => &gt.location,
237        }
238    }
239}
240
241#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
242#[serde(rename_all = "snake_case")]
243pub enum ConditionalKind {
244    Ifdef,
245    Ifndef,
246    If,
247    Elif,
248    Else,
249}
250
251#[derive(Debug, Clone, Serialize)]
252#[serde(rename_all = "snake_case")]
253pub enum DefineValue {
254    StringLiteral(String),
255    Number(String),
256    Identifier(String),
257    Expression(String),
258}
259
260impl DefineValue {
261    pub fn parse(raw: &str) -> Self {
262        let trimmed = raw.trim();
263        if trimmed.starts_with('"') && trimmed.ends_with('"') {
264            return Self::StringLiteral(trimmed[1..trimmed.len() - 1].to_owned());
265        }
266        if trimmed
267            .bytes()
268            .all(|b| b.is_ascii_digit() || b == b'x' || b == b'X' || b == b'-')
269            && !trimmed.is_empty()
270        {
271            return Self::Number(trimmed.to_owned());
272        }
273        if trimmed
274            .bytes()
275            .all(|b| b.is_ascii_alphanumeric() || b == b'_')
276            && !trimmed.is_empty()
277        {
278            return Self::Identifier(trimmed.to_owned());
279        }
280        Self::Expression(trimmed.to_owned())
281    }
282
283    pub fn as_raw_str(&self) -> &str {
284        match self {
285            Self::StringLiteral(s)
286            | Self::Number(s)
287            | Self::Identifier(s)
288            | Self::Expression(s) => s,
289        }
290    }
291}